From 1e4e7eb17f93e84922ab58e87750556efa666621 Mon Sep 17 00:00:00 2001 From: matteozanoni Date: Fri, 2 Feb 2024 10:18:40 +0100 Subject: [PATCH 1/3] Add optional `default_extra` param to formatter initializer. This is used to specify extra parameters that are valid for all log messages formatted with the formatter. It is usefull to avoid repeting `extra={...}` in every log call. --- src/logfmter/formatter.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/logfmter/formatter.py b/src/logfmter/formatter.py index f2e61d2..06c33f7 100644 --- a/src/logfmter/formatter.py +++ b/src/logfmter/formatter.py @@ -143,12 +143,18 @@ def __init__( self, keys: List[str] = ["at"], mapping: Dict[str, str] = {"at": "levelname"}, + default_extra: Optional[Dict[str, str]] = None, datefmt: Optional[str] = None, ): self.keys = [self.normalize_key(key) for key in keys] self.mapping = { self.normalize_key(key): value for key, value in mapping.items() } + self.default_extra = ( + {self.normalize_key(key): value for key, value in default_extra.items()} + if default_extra is not None + else {} + ) self.datefmt = datefmt def format(self, record: logging.LogRecord) -> str: @@ -163,6 +169,7 @@ def format(self, record: logging.LogRecord) -> str: else: params = {"msg": record.getMessage()} + params.update(self.default_extra) params.update(self.get_extra(record)) tokens = [] From 7eb50e2fda890069d325f3681872db65cc2e124e Mon Sep 17 00:00:00 2001 From: Matteo Zanoni <56115369+matteo-zanoni@users.noreply.github.com> Date: Fri, 1 Mar 2024 14:33:17 +0100 Subject: [PATCH 2/3] Update documentation regarding `default_extra` The _old_ way described in the docs is now deprecated since the `default_extra` parameter achieves the same outcome in a more clean and explicit way. --- README.md | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 0ed0d0a..30c022e 100644 --- a/README.md +++ b/README.md @@ -213,18 +213,13 @@ logging.error({"example": True}) # at=ERROR example=yes **Default Key/Value Pairs** -Instead of providing key/value pairs at each log call, you can override -the log record factory to provide defaults: +Instead of providing key/value pairs at each log call, you can setup +the formatter to provide defaults: ```py -_record_factory = logging.getLogRecordFactory() - -def record_factory(*args, **kwargs): - record = _record_factory(*args, **kwargs) - record.trace_id = 123 - return record +from logfmter import Logfmter -logging.setLogRecordFactory(record_factory) +formatter = Logfmter(default_extra={"trace_id": 123}) ``` This will cause all logs to have the `trace_id=123` pair regardless of including From c5cd08bc713277d28c39c54ac3cc2a1ea63e1f39 Mon Sep 17 00:00:00 2001 From: Matteo Zanoni <56115369+matteo-zanoni@users.noreply.github.com> Date: Fri, 1 Mar 2024 14:50:03 +0100 Subject: [PATCH 3/3] add tests for default_extra --- tests/test_formatter.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/tests/test_formatter.py b/tests/test_formatter.py index c646473..cbfa46c 100644 --- a/tests/test_formatter.py +++ b/tests/test_formatter.py @@ -266,3 +266,34 @@ def test_extra_keys(record): assert ( Logfmter(keys=["at", "attr"]).format(record) == "at=INFO msg=alpha attr=value" ) + + +@pytest.mark.parametrize( + "default_extra,record,expected", + [ + # When a parameter is present in the default_extra it should be emitted + ( + {"extra1": "from_formatter"}, + {"levelname": "INFO", "msg": "test"}, + "at=INFO msg=test extra1=from_formatter", + ), + # When a parameter is present in the default_extra and also in the record + # the record value should take precedence + ( + {"extra1": "from_formatter"}, + {"levelname": "INFO", "extra1": "from_record", "msg": "test"}, + "at=INFO msg=test extra1=from_record", + ), + ], +) +def test_format_default_extra(default_extra, record, expected): + """ + If the `default_extra` parameter is specified in the formatter then all log + records must include such parameters. + """ + + # Generate a real `logging.LogRecord` from the provided dictionary. + record = logging.makeLogRecord(record) + formatter = Logfmter(keys=["at", "extra1"], default_extra=default_extra) + + assert formatter.format(record) == expected