Skip to content

Commit

Permalink
Merge pull request #39 from singnet/pbc-31-das-logging-module
Browse files Browse the repository at this point in the history
#31 DAS Logging Module
  • Loading branch information
Pedrobc89 authored Jul 3, 2024
2 parents 1055692 + 522538b commit 8181a42
Show file tree
Hide file tree
Showing 6 changed files with 452 additions and 1 deletion.
195 changes: 194 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,195 @@
*token
notebooks/.ipynb_checkpoints/

### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
.pybuilder/
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version

# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock

# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock

# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/#use-with-ide
.pdm.toml

# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/

# Celery stuff
celerybeat-schedule
celerybeat.pid

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/

# pytype static type analyzer
.pytype/

# Cython debug symbols
cython_debug/

# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

### Python Patch ###
# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
poetry.toml

# ruff
.ruff_cache/

# LSP config files
pyrightconfig.json

### Vim ###
# Swap
[._]*.s[a-v][a-z]
!*.svg # comment out if you don't need vector files
[._]*.sw[a-p]
[._]s[a-rt-v][a-z]
[._]ss[a-gi-z]
[._]sw[a-p]

# Session
Session.vim
Sessionx.vim

# Temporary
.netrwhist
*~
# Auto-generated tag files
tags
# Persistent undo
[._]*.un~

Empty file added python/__init__.py
Empty file.
91 changes: 91 additions & 0 deletions python/commons/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# DasLogger

DasLogger is a custom Python logger that supports both file and stream logging.
It provides an easy-to-use interface for logging messages to a file and
standard error.

## Features

- Logs messages to both a file and `stderr`
- Customizable log levels
- Custom exception logging
- Automatic handling of uncaught exceptions
- Child logger support

## Installation

TODO: Add installation instructions to README.md once we have this
built/deployed to a package

## Usage

### Basic Usage

```python
from das_logging import log

# Log an info level message
log.info("This is an info message")

# Log an exception with warning level
log.das_exception(Exception("This is an error message"))
```

### Creating a Child Logger

```python
from das_logging import log

# Create a child logger
child_logger = log.getChild("child")

# Log an info level message with the child logger
child_logger.info("This is an info message from the child logger")
```

## Configuration

The logger can be configured by modifying the default values in the module:

- `DEFAULT_LOG_FILE`: The file where logs will be written (default: `/tmp/das.log`)
- `DEFAULT_LOGGER_NAME`: The name of the logger (default: `das_logger`)
- `DEFAULT_LOG_LEVEL`: The default log level (default: `logging.WARNING`)

## Example

Here's a complete example demonstrating the usage of DasLogger:

```python
from das_logging import log

# Log messages of various levels
log.debug("This is a debug message") # Not logged by default
log.info("This is an info message")
log.warning("This is a warning message")
log.error("This is an error message")
log.critical("This is a critical message")
log.fatal("This is a fatal message")

# logs an exception with traceback
log.exception(Exception("This is an exception message"))

# Log an exception with custom DAS formatting
try:
raise ValueError("An example exception")
except Exception as e:
log.das_exception(e)

# Create and use a child logger
child = log.getChild("child")
child.setLevel(logging.DEBUG)
child.debug("This is an info message from the child logger")
```

## Exception Handling

DasLogger automatically handles uncaught exceptions by logging them and then
exiting the program with a status code of 1. This is achieved by setting
`sys.excepthook` to the logger's custom exception handler.

>Note: The automatic handling of uncaught exceptions will not work in iPython
shells (including Jupyter notebooks).
Empty file added python/commons/__init__.py
Empty file.
99 changes: 99 additions & 0 deletions python/commons/das_logging.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import logging
import sys
from types import TracebackType

DEFAULT_LOG_FILE = "/tmp/das.log"
DEFAULT_LOGGER_NAME = "das_logger"
DEFAULT_LOG_LEVEL = logging.WARNING


class DasLogger(logging.Logger):
"""
Custom Logger class that supports file and stream logging.
Future work will include a suport for queueing.
Example:
from das_logging import log
log.info("test") -- outputs an info level log message to file /tmp/das.log and also to stderr
# if you want to have a child logger you can do so like this:
from das_logging import log
child = log.getChild("child")
child.info("test")
"""

# Export constants from logging
DEBUG = logging.DEBUG
INFO = logging.INFO
WARNING = logging.WARNING
ERROR = logging.ERROR
CRITICAL = logging.CRITICAL
FATAL = logging.FATAL

def __init__(self, name: str, level: int = DEFAULT_LOG_LEVEL) -> None:
"""
Args:
name: name of the logger
level: level of the logger
"""
super().__init__(name, level)

self.formatter = logging.Formatter(
fmt="%(asctime)s %(levelname)s %(module)s:%(threadName)s %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
self._setup_stream_handler()
self._setup_file_handler()

sys.excepthook = self._uncaught_exc_handler

def _setup_stream_handler(self) -> None:
"""
Initializes and sets up the stream handler.
"""
stream_handler = logging.StreamHandler(sys.stderr)
stream_handler.setFormatter(self.formatter)
self.addHandler(stream_handler)

def _setup_file_handler(self):
"""
Initializes and sets up the file handler.
"""
file_handler = logging.FileHandler(DEFAULT_LOG_FILE)
file_handler.setFormatter(self.formatter)
self.addHandler(file_handler)

def das_exception(self, msg: str, *args, exc_info: bool = True, **kwargs):
"""
Convenience method for logging an WARNING with exception information.
Args:
msg: message to log
*args: positional arguments
exc_info: include exception information, defaulted to True
**kwargs: keyword arguments
"""
self.warning(msg, *args, exc_info=exc_info, **kwargs)

def _uncaught_exc_handler(
self,
exc_type: type[BaseException],
exc_value: BaseException,
exc_traceback: TracebackType | None,
):
if issubclass(exc_type, KeyboardInterrupt):
return sys.__excepthook__(exc_type, exc_value, exc_traceback)

self.error(
"An uncaught exception occurred: ",
exc_info=(exc_type, exc_value, exc_traceback)
)


logging.setLoggerClass(DasLogger)
log: DasLogger = logging.getLogger(DEFAULT_LOGGER_NAME) # type: ignore

if __name__ == "__main__":
log.warning("test")
Loading

0 comments on commit 8181a42

Please sign in to comment.