diff --git a/ecs_logging/_stdlib.py b/ecs_logging/_stdlib.py index da0358c..76e03ed 100644 --- a/ecs_logging/_stdlib.py +++ b/ecs_logging/_stdlib.py @@ -72,15 +72,16 @@ class StdlibFormatter(logging.Formatter): converter = time.gmtime def __init__( - self, - fmt=None, - datefmt=None, - style="%", - validate=None, - stack_trace_limit=None, - exclude_fields=(), + self, # type: Any + fmt=None, # type: Optional[str] + datefmt=None, # type: Optional[str] + style="%", # type: str + validate=None, # type: Optional[bool] + stack_trace_limit=None, # type: Optional[int] + extra=None, # type: Optional[Dict[str, Any]] + exclude_fields=(), # type: Sequence[str] ): - # type: (Any, Optional[str], Optional[str], str, Optional[bool], Optional[int], Sequence[str]) -> None + # type: (...) -> None """Initialize the ECS formatter. :param int stack_trace_limit: @@ -89,6 +90,8 @@ def __init__( Setting this to zero will suppress stack traces. This setting doesn't affect ``LogRecord.stack_info`` because this attribute is typically already pre-formatted. + :param Optional[Dict[str, Any]] extra: + Specifies the collection of meta-data fields to add to all records. :param Sequence[str] exclude_fields: Specifies any fields that should be suppressed from the resulting fields, expressed with dot notation:: @@ -129,6 +132,7 @@ def __init__( ): raise TypeError("'exclude_fields' must be a sequence of strings") + self._extra = extra self._exclude_fields = frozenset(exclude_fields) self._stack_trace_limit = stack_trace_limit @@ -218,6 +222,10 @@ def format_to_ecs(self, record): # since they can be defined as 'extras={"http": {"method": "GET"}}' extra_keys = set(available).difference(self._LOGRECORD_DICT) extras = flatten_dict({key: available[key] for key in extra_keys}) + # Merge in any global extra's + if self._extra is not None: + for field, value in self._extra.items(): + merge_dicts(de_dot(field, value), extras) # Pop all Elastic APM extras and add them # to standard tracing ECS fields. diff --git a/tests/test_stdlib_formatter.py b/tests/test_stdlib_formatter.py index 0eb6f33..2aca1b0 100644 --- a/tests/test_stdlib_formatter.py +++ b/tests/test_stdlib_formatter.py @@ -26,7 +26,6 @@ import ecs_logging from .compat import StringIO - requires_py3 = pytest.mark.skipif( sys.version_info[0] < 3, reason="Test requires Python 3.x+" ) @@ -63,6 +62,19 @@ def test_record_formatted(spec_validator): ) +def test_extra_global_is_merged(spec_validator): + formatter = ecs_logging.StdlibFormatter( + exclude_fields=["process"], extra={"environment": "dev"} + ) + + assert spec_validator(formatter.format(make_record())) == ( + '{"@timestamp":"2020-03-20T14:12:46.123Z","log.level":"debug","message":"1: hello","ecs":{"version":"1.6.0"},' + '"environment":"dev",' + '"log":{"logger":"logger-name","origin":{"file":{"line":10,"name":"file.py"},"function":"test_function"},' + '"original":"1: hello"}}' + ) + + def test_can_be_overridden(spec_validator): class CustomFormatter(ecs_logging.StdlibFormatter): def format_to_ecs(self, record):