Skip to content

StructlogFormatter conforms to structlog.typing.Processor #147

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 13 additions & 11 deletions ecs_logging/_stdlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@

from typing import Any, Callable, Dict, Optional, Sequence, Union

try:
from typing import Literal # type: ignore
except ImportError:
from typing_extensions import Literal # type: ignore
if sys.version_info >= (3, 8):
from typing import Literal
else:
from typing_extensions import Literal


# Load the attributes of a LogRecord so if some are
Expand Down Expand Up @@ -105,13 +105,15 @@ def __init__(

exclude_keys=["error"]
"""
_kwargs = {}
if validate is not None:
# validate was introduced in py3.8 so we need to only provide it if the user provided it
_kwargs["validate"] = validate
super().__init__( # type: ignore[call-arg]
fmt=fmt, datefmt=datefmt, style=style, **_kwargs # type: ignore[arg-type]
)
# validate was introduced in py3.8 so we need to only provide it if the user provided it
if sys.version_info >= (3, 8) and validate is not None:
super().__init__(
fmt=fmt, datefmt=datefmt, style=style, validate=validate,
)
else:
super().__init__(
fmt=fmt, datefmt=datefmt, style=style,
)

if stack_trace_limit is not None:
if not isinstance(stack_trace_limit, int):
Expand Down
16 changes: 12 additions & 4 deletions ecs_logging/_structlog.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,13 @@

import time
import datetime
from typing import Any, Dict
import sys
from typing import Any

if sys.version_info >= (3, 9):
from collections.abc import MutableMapping
else:
from typing import MutableMapping

from ._meta import ECS_VERSION
from ._utils import json_dumps, normalize_dict
Expand All @@ -26,7 +32,7 @@
class StructlogFormatter:
"""ECS formatter for the ``structlog`` module"""

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

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

def format_to_ecs(self, event_dict: Dict[str, Any]) -> Dict[str, Any]:
def format_to_ecs(
self, event_dict: MutableMapping[str, Any]
) -> MutableMapping[str, Any]:
if "@timestamp" not in event_dict:
event_dict["@timestamp"] = (
datetime.datetime.fromtimestamp(
Expand All @@ -55,5 +63,5 @@ def format_to_ecs(self, event_dict: Dict[str, Any]) -> Dict[str, Any]:
event_dict.setdefault("ecs.version", ECS_VERSION)
return event_dict

def _json_dumps(self, value: Dict[str, Any]) -> str:
def _json_dumps(self, value: MutableMapping[str, Any]) -> str:
return json_dumps(value=value)
16 changes: 12 additions & 4 deletions ecs_logging/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,14 @@
import collections.abc
import json
import functools
import sys
from typing import Any, Dict, Mapping

if sys.version_info >= (3, 9):
from collections.abc import MutableMapping
else:
from typing import MutableMapping


__all__ = [
"normalize_dict",
Expand Down Expand Up @@ -53,9 +59,9 @@ def flatten_dict(value: Mapping[str, Any]) -> Dict[str, Any]:
return top_level


def normalize_dict(value: Dict[str, Any]) -> Dict[str, Any]:
def normalize_dict(value: MutableMapping[str, Any]) -> MutableMapping[str, Any]:
"""Expands all dotted names to nested dictionaries"""
if not isinstance(value, dict):
if not isinstance(value, MutableMapping):
return value
keys = list(value.keys())
for key in keys:
Expand All @@ -78,7 +84,9 @@ def de_dot(dot_string: str, msg: Any) -> Dict[str, Any]:
return ret


def merge_dicts(from_: Dict[Any, Any], into: Dict[Any, Any]) -> Dict[Any, Any]:
def merge_dicts(
from_: Mapping[Any, Any], into: MutableMapping[Any, Any]
) -> MutableMapping[Any, Any]:
"""Merge deeply nested dictionary structures.
When called has side-effects within 'destination'.
"""
Expand All @@ -98,7 +106,7 @@ def merge_dicts(from_: Dict[Any, Any], into: Dict[Any, Any]) -> Dict[Any, Any]:
return into


def json_dumps(value: Dict[str, Any]) -> str:
def json_dumps(value: MutableMapping[str, Any]) -> str:

# Ensure that the first three fields are '@timestamp',
# 'log.level', and 'message' per ECS spec
Expand Down
1 change: 0 additions & 1 deletion noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,5 @@ def lint(session):
"mypy",
"--strict",
"--show-error-codes",
"--no-warn-unused-ignores",
"ecs_logging/",
)
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ develop = [
"mock",
"structlog",
"elastic-apm",
"mypy",
]

[tool.flit.metadata.urls]
Expand Down
14 changes: 14 additions & 0 deletions tests/test_typing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import pathlib
import subprocess
import sys

import pytest

_THIS_DIR = pathlib.Path(__file__).parent


@pytest.mark.parametrize("file", (_THIS_DIR / "typing").glob("*.py"))
def test_type_check(file):
subprocess.check_call(
[sys.executable, "-m", "mypy", "--strict", "--config-file=", file]
)
34 changes: 34 additions & 0 deletions tests/typing/structlog_usage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import sys
from typing import List, Tuple

import ecs_logging
import structlog
from structlog.typing import Processor


shared_processors: Tuple[Processor, ...] = (
structlog.contextvars.merge_contextvars,
structlog.processors.add_log_level,
structlog.processors.StackInfoRenderer(),
structlog.dev.set_exc_info,
structlog.processors.TimeStamper(fmt="iso", utc=True),
)

processors: List[Processor]
if sys.stderr.isatty():
processors = [
*shared_processors,
structlog.dev.ConsoleRenderer(),
]
else:
processors = [
*shared_processors,
structlog.processors.dict_tracebacks,
ecs_logging.StructlogFormatter(),
]

structlog.configure(
processors=processors,
logger_factory=structlog.PrintLoggerFactory(),
cache_logger_on_first_use=True,
)