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()