hynek / structlog

Simple, powerful, and fast logging for Python.
https://www.structlog.org/
Other
3.55k stars 223 forks source link

Add a helper function for systemd's SyslogLevelPrefix #249

Open paravoid opened 4 years ago

paravoid commented 4 years ago

From systemd.exec(5):

SyslogLevelPrefix=

Takes a boolean argument. If true and StandardOutput= or StandardError= are set to syslog, kmsg or journal, log lines written by the executed process that are prefixed with a log level will be passed on to syslog with this log level set but the prefix removed. If set to false, the interpretation of these prefixes is disabled and the logged lines are passed on as-is. For details about this prefixing see sd-daemon(3). Defaults to true.

i.e. for the simple case where a service emits output to stdout, and systemd picks that up, it may be helpful to add a prefix with the syslog log level. systemd will also pick that up, strip it from its output, and use it as the log level, by default.

There are two parts to this:

  1. Adding the syslog log level to event_dict, i.e. "4" (or syslog.LOG_WARNING) for "warn"/"warning", etc. Python's logging.handlers.SysLogHandler has code for that, and python-systemd is trying to do something more clever to cover custom log levels. Neither are reusable, but should be simple to implement. It should be similar to the existing add_log_level_number processor.
  2. A simple processor that prepends the event message with said level. I'm guessing that will be more controversial though. Alternatively, it could also be an argument to PrintLogger.

The "syslog" log level will also be useful in case one e.g. wants to write a processor using the stdlib syslog module (i.e. without involving logging).

paravoid commented 4 years ago

(1) would probably be something like that. Let me know what you think :)

diff --git a/src/structlog/stdlib.py b/src/structlog/stdlib.py
index edaeccd..03e5fe6 100644
--- a/src/structlog/stdlib.py
+++ b/src/structlog/stdlib.py
@@ -343,6 +343,18 @@ _NAME_TO_LEVEL = {
     "notset": NOTSET,
 }

+# see syslog(3) and logging.handler.SysLogHandler for a similar mapping
+_NAME_TO_SYSLOG_LEVEL = {
+    "critical":  2,  # LOG_CRIT
+    "error":     3,  # LOG_ERR
+    "exception": 3,  # LOG_ERR
+    "warn":      4,  # LOG_WARNING
+    "warning":   4,  # LOG_WARNING
+    "info":      6,  # LOG_INFO
+    "debug":     7,  # LOG_DEBUG
+    "notset":    7,  # LOG_DEBUG
+}
+
 _LEVEL_TO_NAME = {
     v: k
     for k, v in _NAME_TO_LEVEL.items()
@@ -406,6 +418,14 @@ def add_log_level_number(logger, method_name, event_dict):
     return event_dict

+def add_syslog_level(logger, method_name, event_dict):
+    """
+    Add the syslog level (0-7) to the event dict.
+    """
+    event_dict["syslog_level"] = _NAME_TO_SYSLOG_LEVEL[method_name]
+    return event_dict
+
+
 def add_logger_name(logger, method_name, event_dict):
     """
     Add the logger name to the event dict.
hynek commented 4 years ago

Looks useful I guess? 🤔 Any idea how to test that?