Skip to content

Commit

Permalink
Add add_extra Processor function
Browse files Browse the repository at this point in the history
This processor function will add extra attributres from LogRecord
objects to the event_dict, and can be useful for making values passed
in the extra parameter of the logging module's log methods pass through
to output as additional properties.

Fixes hynek#209
  • Loading branch information
aucampia committed Dec 5, 2021
1 parent cad14e0 commit e3bf171
Show file tree
Hide file tree
Showing 6 changed files with 80 additions and 1 deletion.
3 changes: 3 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ Changes:

- Added the ``structlog.processors.LogfmtRenderer`` processor to render log lines using the `logfmt <https://brandur.org/logfmt>`_ format.
`#376 <https://github.com/hynek/structlog/pull/376>`_
- Added ``structlog.stdlib.add_extra`` processor that adds extra ``logging.LogRecord`` keys to the event dictionary.
This function is useful for adding data passed in the ``extra`` parameter of the ``logging`` module's log methods to the event dictionary.
`#377 <https://github.com/hynek/structlog/pull/377>`_


----
Expand Down
2 changes: 2 additions & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,8 @@ Please see :doc:`thread-local` for details.

.. autofunction:: add_logger_name

.. autofunction:: add_extra

.. autoclass:: PositionalArgumentsFormatter

.. autoclass:: ProcessorFormatter
Expand Down
9 changes: 9 additions & 0 deletions docs/standard-library.rst
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,11 @@ Processors
`add_logger_name`:
Adds the name of the logger to the event dictionary under the key ``logger``.

`add_extra`:
Add extra `logging.LogRecord` keys to the event dictionary.

This function is useful for adding data passed in the ``extra`` parameter of the `logging` module's log methods to the event dictionary.

:func:`~structlog.stdlib.add_log_level`:
Adds the log level to the event dictionary under the key ``level``.

Expand Down Expand Up @@ -413,6 +418,10 @@ For example, to use the standard library's `logging.config.dictConfig` to log co
# Add the log level and a timestamp to the event_dict if the log entry
# is not from structlog.
structlog.stdlib.add_log_level,
# Add extra LogRecord keys to the event dictionary so that values
# passed in the extra parameter of log methods are added to the event
# dictionary.
structlog.stdlib.add_extra,
timestamper,
]
Expand Down
24 changes: 24 additions & 0 deletions src/structlog/stdlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -664,6 +664,30 @@ def add_logger_name(
return event_dict


_BLANK_LOGRECORD = logging.LogRecord(
"name", 0, "pathname", 0, "msg", tuple(), None
)


def add_extra(
logger: logging.Logger, method_name: str, event_dict: EventDict
) -> EventDict:
"""
Add extra `logging.LogRecord` keys to the event dictionary.
This function is useful for adding data passed in the ``extra`` parameter
of the `logging` module's log methods to the event dictionary.
.. versionadded:: 21.5.0
"""
record: Optional[logging.LogRecord] = event_dict.get("_record")
if record is not None:
for key, value in record.__dict__.items():
if key not in _BLANK_LOGRECORD.__dict__:
event_dict[key] = value
return event_dict


def render_to_log_kwargs(
_: logging.Logger, __: str, event_dict: EventDict
) -> EventDict:
Expand Down
41 changes: 41 additions & 0 deletions tests/test_stdlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
import os
import sys

from io import StringIO

import pytest

from pretend import call_recorder, stub
Expand All @@ -31,6 +33,7 @@
PositionalArgumentsFormatter,
ProcessorFormatter,
_FixedFindCallerLogger,
add_extra,
add_log_level,
add_log_level_number,
add_logger_name,
Expand Down Expand Up @@ -472,6 +475,44 @@ def test_logger_name_added_with_record(self, log_record):
assert name == event_dict["logger"]


class TestAddExtra:
def test_add_extra(self, log_record):
"""
Extra attributes of a LogRecord are added to the event dict.
"""
extra = {"x_this": "is", "x_the": 3, "x_extra": "values"}
record: logging.LogRecord = log_record()
record.__dict__.update(extra)
event_dict = {"_record": record}
event_dict_out = add_extra(None, None, event_dict)
assert event_dict_out == {**event_dict, **extra}

def test_add_extra_e2e(self):
"""
Values passed in the extra parameter of log methods are added as
members of JSON output objects.
"""
logger = logging.Logger(sys._getframe().f_code.co_name)
string_io = StringIO()
handler = logging.StreamHandler(string_io)
formatter = ProcessorFormatter(
foreign_pre_chain=[add_extra],
processors=[JSONRenderer()],
)
handler.setFormatter(formatter)
handler.setLevel(0)
logger.addHandler(handler)
logger.setLevel(0)
extra = {"with": "values", "from": "extra"}
logger.info("Some %s", "text", extra=extra)
out_item_set = set(json.loads(string_io.getvalue()).items())
expected_item_set = set({"event": "Some text", **extra}.items())
assert expected_item_set.issubset(out_item_set), (
f"expected_item_set={expected_item_set} should"
f"be a subset of out_item_set={out_item_set}"
)


class TestRenderToLogKW:
def test_default(self):
"""
Expand Down
2 changes: 1 addition & 1 deletion tests/typing_examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ def bytes_dumps(

formatter = structlog.stdlib.ProcessorFormatter(
processor=structlog.dev.ConsoleRenderer(),
foreign_pre_chain=shared_processors,
foreign_pre_chain=[*structlog.stdlib.add_extra, shared_processors],
)


Expand Down

0 comments on commit e3bf171

Please sign in to comment.