From 6341b81ff8de4be77d3394c6153c0fbe21021498 Mon Sep 17 00:00:00 2001 From: Marcel Schwarz Date: Thu, 25 Jan 2024 15:08:31 +0100 Subject: [PATCH] Initial version --- .gitignore | 2 + .idea/.gitignore | 8 ++ .idea/icaotix_python_logging.iml | 8 ++ .../inspectionProfiles/profiles_settings.xml | 6 + .idea/misc.xml | 7 + .idea/modules.xml | 8 ++ .idea/vcs.xml | 6 + README.md | 3 +- pyproject.toml | 18 +++ src/icaotix_python_logging/__init__.py | 0 src/icaotix_python_logging/logger.py | 122 ++++++++++++++++++ 11 files changed, 187 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 .idea/icaotix_python_logging.iml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 pyproject.toml create mode 100644 src/icaotix_python_logging/__init__.py create mode 100644 src/icaotix_python_logging/logger.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7444713 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +dist/ +*.egg-info/ \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/icaotix_python_logging.iml b/.idea/icaotix_python_logging.iml new file mode 100644 index 0000000..d0876a7 --- /dev/null +++ b/.idea/icaotix_python_logging.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..db8786c --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..2bd7cf4 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index 13feb91..4322bc2 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,3 @@ -# icaotix_python_logging +# icaotix python logging +This lib helps in setting up a json logger for stdout for basically all projects diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..b653c12 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,18 @@ +[project] +name = "icaotix_python_logging" +version = "0.0.1" +authors = [ + { name = "Marcel Schwarz", email = "admin@icaotix.de" }, +] +description = "A small package which helps to setup a logger" +readme = "README.md" +requires-python = ">=3.9" +classifiers = [ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", +] + +[project.urls] +Homepage = "https://git.icaotix.de/icaotix/icaotix_python_logging" +Issues = "https://git.icaotix.de/icaotix/icaotix_python_logging/issues" \ No newline at end of file diff --git a/src/icaotix_python_logging/__init__.py b/src/icaotix_python_logging/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/icaotix_python_logging/logger.py b/src/icaotix_python_logging/logger.py new file mode 100644 index 0000000..91f13b6 --- /dev/null +++ b/src/icaotix_python_logging/logger.py @@ -0,0 +1,122 @@ +# Logging +import atexit +import datetime +import datetime as dt +import json +import logging +import logging.config +import logging.handlers +import queue +import sys +from typing import override + +LOG_RECORD_BUILTIN_ATTRS = { + "args", + "asctime", + "created", + "exc_info", + "exc_text", + "filename", + "funcName", + "levelname", + "levelno", + "lineno", + "module", + "msecs", + "message", + "msg", + "name", + "pathname", + "process", + "processName", + "relativeCreated", + "stack_info", + "thread", + "threadName", + "taskName", +} + + +class JSONFormatter(logging.Formatter): + def __init__( + self, + *, + fmt_keys: dict[str, str] | None = None, + ): + super().__init__() + self.fmt_keys = fmt_keys if fmt_keys is not None else {} + + @override + def format(self, record: logging.LogRecord) -> str: + message = self._prepare_log_dict(record) + return json.dumps(message, default=str) + + def _prepare_log_dict(self, record: logging.LogRecord): + always_fields = { + "message": record.getMessage(), + "timestamp": dt.datetime.fromtimestamp( + record.created, tz=dt.timezone.utc + ).isoformat(), + } + if record.exc_info is not None: + always_fields["exc_info"] = self.formatException(record.exc_info) + + if record.stack_info is not None: + always_fields["stack_info"] = self.formatStack(record.stack_info) + + message = { + key: msg_val + if (msg_val := always_fields.pop(val, None)) is not None + else getattr(record, val) + for key, val in self.fmt_keys.items() + } + message.update(always_fields) + + for key, val in record.__dict__.items(): + if key not in LOG_RECORD_BUILTIN_ATTRS: + if message.get("extra") is None: + message["extra"] = dict() + message["extra"][key] = val + + return message + + +def get_logger(name: str) -> logging.Logger: + json_formatter = JSONFormatter(fmt_keys={ + "level": "levelname", + "message": "message", + "timestamp": "timestamp", + "logger": "name", + "pathname": "pathname", + "module": "module", + "function": "funcName", + "process_id": "process", + "line": "lineno", + "thread_name": "threadName" + }) + stdout_handler = logging.StreamHandler(sys.stdout) + stdout_handler.setFormatter(json_formatter) + + queue_handler_queue = queue.Queue() + queue_handler = logging.handlers.QueueHandler(queue_handler_queue) + queue_handler.listener = logging.handlers.QueueListener(queue_handler_queue, stdout_handler, + respect_handler_level=True) + queue_handler.listener.start() + atexit.register(queue_handler.listener.stop) + + logger = logging.getLogger(name) # __name__ is a common choice + logger.setLevel(logging.DEBUG) + logger.handlers = [queue_handler] + + return logger + + +def main(): + log = get_logger(__name__) + log.info("Was geht\n los da rein", extra={"asd": 1}) + log.debug("Was geht\n los da rein", + extra={"asd": 1, "laaa": "", "da": datetime.datetime.now(tz=datetime.UTC), "set": {12, 45}}) + + +if __name__ == '__main__': + main()