From 44a6e0dfedf97d44d354210ab1f483548580c440 Mon Sep 17 00:00:00 2001 From: Giovanni Tataranni Date: Thu, 19 Sep 2024 15:46:02 +0200 Subject: [PATCH] tools: add logfmt option for frr-reload.py Add the option of printing logs in logfmt format. Additional machine readable information can be printed via the `extra` argument. Example: ```python log.debug("exit context"), extra={"line": line, "ctx_keys": ctx_keys}) log.error(f"Failed to execute command {' '.join(cmd)}", extra={"cmd": cmd}) ``` Signed-off-by: Giovanni Tataranni --- tools/frr-reload.py | 77 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 63 insertions(+), 14 deletions(-) diff --git a/tools/frr-reload.py b/tools/frr-reload.py index 94327eb083..ee6f2456af 100755 --- a/tools/frr-reload.py +++ b/tools/frr-reload.py @@ -14,6 +14,7 @@ This program from __future__ import print_function, unicode_literals import argparse +import datetime import logging import os, os.path import random @@ -1978,6 +1979,50 @@ def compare_context_objects(newconf, running): return (lines_to_add, lines_to_del) +class LogFmtFormatter(logging.Formatter): + def format(self, record: logging.LogRecord) -> str: + """ + Creates log messages with key=value pairs, in logfmt format. + """ + # Escape double quotes in the message, replace newlines with '\n' + msg = record.getMessage().replace('"', '\\"').replace("\n", "\\n").strip() + # Format the time in RFC 3339 format + timestamp = datetime.datetime.fromtimestamp( + record.created, datetime.timezone.utc + ) + asctime = timestamp.astimezone().isoformat(timespec="seconds") + # Create logfmt style log message, ignore default fields + logfmt = f'ts={asctime} level={record.levelname} msg="{msg}"' + default_fields = [ + "args", + "asctime", + "created", + "exc_info", + "exc_text", + "filename", + "funcName", + "levelname", + "levelno", + "lineno", + "module", + "msecs", + "msg", + "name", + "pathname", + "process", + "processName", + "relativeCreated", + "stack_info", + "thread", + "threadName", + ] + for key, value in vars(record).items(): + if key in default_fields: + continue + logfmt += f" {key}={value}" + return logfmt + + if __name__ == "__main__": # Command line options parser = argparse.ArgumentParser( @@ -2045,15 +2090,24 @@ if __name__ == "__main__": action="store_true", help="Used by topotest to not delete debug or log file commands", ) + parser.add_argument( + "--logfmt", + action="store_true", + help="Use logfmt as log format", + default=False, + ) args = parser.parse_args() # Logging # For --test log to stdout # For --reload log to /var/log/frr/frr-reload.log - if args.test or args.stdout: - logging.basicConfig(format="%(asctime)s %(levelname)5s: %(message)s") - + # If --logfmt, use the logfmt format + formatter = logging.Formatter("%(asctime)s %(levelname)5s: %(message)s") + handler = logging.StreamHandler() + if args.logfmt: + formatter = LogFmtFormatter() + elif args.test or args.stdout: # Color the errors and warnings in red logging.addLevelName( logging.ERROR, "\033[91m %s\033[0m" % logging.getLevelName(logging.ERROR) @@ -2061,20 +2115,15 @@ if __name__ == "__main__": logging.addLevelName( logging.WARNING, "\033[91m%s\033[0m" % logging.getLevelName(logging.WARNING) ) - - elif args.reload: + if args.reload: if not os.path.isdir("/var/log/frr/"): os.makedirs("/var/log/frr/", mode=0o0755) - - logging.basicConfig( - filename="/var/log/frr/frr-reload.log", - format="%(asctime)s %(levelname)5s: %(message)s", - ) - - # argparse should prevent this from happening but just to be safe... - else: - raise Exception("Must specify --reload or --test") + handler = logging.FileHandler("/var/log/frr/frr-reload.log") + if args.stdout: + handler = logging.StreamHandler(sys.stdout) + handler.setFormatter(formatter) log = logging.getLogger(__name__) + log.addHandler(handler) if args.debug: log.setLevel(logging.DEBUG) -- 2.39.5