diff --git a/tools/frr-reload.py b/tools/frr-reload.py index 94327eb0833d..37b30e5928e0 100755 --- a/tools/frr-reload.py +++ b/tools/frr-reload.py @@ -14,6 +14,7 @@ from __future__ import print_function, unicode_literals import argparse +import datetime import logging import os, os.path import random @@ -1978,6 +1979,51 @@ 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' + record.msg = record.getMessage().replace('"', '\\"') + record.msg = record.msg.replace("\n", "\\n") + # Format the time in RFC 3339 format + timestamp = datetime.datetime.fromtimestamp( + record.created, datetime.timezone.utc + ) + record.asctime = timestamp.astimezone().isoformat(timespec="seconds") + # Create logfmt style log message, ignore default fields + logfmt = f'ts={record.asctime} level={record.levelname} msg="{record.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 +2091,24 @@ def compare_context_objects(newconf, running): 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 +2116,15 @@ def compare_context_objects(newconf, running): 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)