Skip to content

Commit 054f4ec

Browse files
committed
StructlogFormatter conforms to structlog.typing.Processor
Closes #146
1 parent eb2d53d commit 054f4ec

File tree

5 files changed

+73
-8
lines changed

5 files changed

+73
-8
lines changed

ecs_logging/_structlog.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,13 @@
1717

1818
import time
1919
import datetime
20-
from typing import Any, Dict
20+
import sys
21+
from typing import Any
22+
23+
if sys.version_info >= (3, 9):
24+
from collections.abc import MutableMapping
25+
else:
26+
from typing import MutableMapping
2127

2228
from ._meta import ECS_VERSION
2329
from ._utils import json_dumps, normalize_dict
@@ -26,7 +32,7 @@
2632
class StructlogFormatter:
2733
"""ECS formatter for the ``structlog`` module"""
2834

29-
def __call__(self, _: Any, name: str, event_dict: Dict[str, Any]) -> str:
35+
def __call__(self, _: Any, name: str, event_dict: MutableMapping[str, Any]) -> str:
3036

3137
# Handle event -> message now so that stuff like `event.dataset` doesn't
3238
# cause problems down the line
@@ -36,7 +42,9 @@ def __call__(self, _: Any, name: str, event_dict: Dict[str, Any]) -> str:
3642
event_dict = self.format_to_ecs(event_dict)
3743
return self._json_dumps(event_dict)
3844

39-
def format_to_ecs(self, event_dict: Dict[str, Any]) -> Dict[str, Any]:
45+
def format_to_ecs(
46+
self, event_dict: MutableMapping[str, Any]
47+
) -> MutableMapping[str, Any]:
4048
if "@timestamp" not in event_dict:
4149
event_dict["@timestamp"] = (
4250
datetime.datetime.fromtimestamp(
@@ -55,5 +63,5 @@ def format_to_ecs(self, event_dict: Dict[str, Any]) -> Dict[str, Any]:
5563
event_dict.setdefault("ecs.version", ECS_VERSION)
5664
return event_dict
5765

58-
def _json_dumps(self, value: Dict[str, Any]) -> str:
66+
def _json_dumps(self, value: MutableMapping[str, Any]) -> str:
5967
return json_dumps(value=value)

ecs_logging/_utils.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,14 @@
1818
import collections.abc
1919
import json
2020
import functools
21+
import sys
2122
from typing import Any, Dict, Mapping
2223

24+
if sys.version_info >= (3, 9):
25+
from collections.abc import MutableMapping
26+
else:
27+
from typing import MutableMapping
28+
2329

2430
__all__ = [
2531
"normalize_dict",
@@ -53,9 +59,9 @@ def flatten_dict(value: Mapping[str, Any]) -> Dict[str, Any]:
5359
return top_level
5460

5561

56-
def normalize_dict(value: Dict[str, Any]) -> Dict[str, Any]:
62+
def normalize_dict(value: MutableMapping[str, Any]) -> MutableMapping[str, Any]:
5763
"""Expands all dotted names to nested dictionaries"""
58-
if not isinstance(value, dict):
64+
if not isinstance(value, MutableMapping):
5965
return value
6066
keys = list(value.keys())
6167
for key in keys:
@@ -78,7 +84,9 @@ def de_dot(dot_string: str, msg: Any) -> Dict[str, Any]:
7884
return ret
7985

8086

81-
def merge_dicts(from_: Dict[Any, Any], into: Dict[Any, Any]) -> Dict[Any, Any]:
87+
def merge_dicts(
88+
from_: Mapping[Any, Any], into: MutableMapping[Any, Any]
89+
) -> MutableMapping[Any, Any]:
8290
"""Merge deeply nested dictionary structures.
8391
When called has side-effects within 'destination'.
8492
"""
@@ -98,7 +106,7 @@ def merge_dicts(from_: Dict[Any, Any], into: Dict[Any, Any]) -> Dict[Any, Any]:
98106
return into
99107

100108

101-
def json_dumps(value: Dict[str, Any]) -> str:
109+
def json_dumps(value: MutableMapping[str, Any]) -> str:
102110

103111
# Ensure that the first three fields are '@timestamp',
104112
# 'log.level', and 'message' per ECS spec

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ develop = [
3333
"mock",
3434
"structlog",
3535
"elastic-apm",
36+
"mypy",
3637
]
3738

3839
[tool.flit.metadata.urls]

tests/test_typing.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import pathlib
2+
import subprocess
3+
import sys
4+
5+
import pytest
6+
7+
_THIS_DIR = pathlib.Path(__file__).parent
8+
9+
10+
@pytest.mark.parametrize("file", (_THIS_DIR / "typing").glob("*.py"))
11+
def test_type_check(file):
12+
subprocess.check_call(
13+
[sys.executable, "-m", "mypy", "--strict", "--config-file=", file]
14+
)

tests/typing/structlog_usage.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import sys
2+
from typing import List, Tuple
3+
4+
import ecs_logging
5+
import structlog
6+
from structlog.typing import Processor
7+
8+
9+
shared_processors: Tuple[Processor, ...] = (
10+
structlog.contextvars.merge_contextvars,
11+
structlog.processors.add_log_level,
12+
structlog.processors.StackInfoRenderer(),
13+
structlog.dev.set_exc_info,
14+
structlog.processors.TimeStamper(fmt="iso", utc=True),
15+
)
16+
17+
processors: List[Processor]
18+
if sys.stderr.isatty():
19+
processors = [
20+
*shared_processors,
21+
structlog.dev.ConsoleRenderer(),
22+
]
23+
else:
24+
processors = [
25+
*shared_processors,
26+
structlog.processors.dict_tracebacks,
27+
ecs_logging.StructlogFormatter(),
28+
]
29+
30+
structlog.configure(
31+
processors=processors,
32+
logger_factory=structlog.PrintLoggerFactory(),
33+
cache_logger_on_first_use=True,
34+
)

0 commit comments

Comments
 (0)