Coverage for stlog/adapter.py: 94%

36 statements  

« prev     ^ index     » next       coverage.py v7.8.2, created at 2026-07-22 09:22 +0000

1from __future__ import annotations 

2 

3import collections 

4import logging 

5import typing 

6 

7from stlog.base import ( 

8 RESERVED_ATTRS, 

9 STLOG_EXTRA_KEY, 

10 check_json_types_or_raise, 

11) 

12from stlog.context import LogContext 

13 

14 

15# ligthly adapted from https://github.com/Mergifyio/daiquiri/blob/main/daiquiri/__init__.py 

16class _KeywordArgumentAdapter(logging.LoggerAdapter): 

17 """Logger adapter to add keyword arguments to log record's extra data. 

18 

19 Keywords passed to the log call are added to the "extra" 

20 dictionary passed to the underlying logger so they are emitted 

21 with the log message and available to the format string. 

22 """ 

23 

24 def process( 

25 self, msg: typing.Any, kwargs: collections.abc.MutableMapping[str, typing.Any] 

26 ) -> tuple[typing.Any, collections.abc.MutableMapping[str, typing.Any]]: 

27 # Make a new extra dictionary combining the values we were 

28 # given when we were constructed and anything from kwargs. 

29 if self.extra is not None: 

30 # kvs passed during getLogger() call 

31 check_json_types_or_raise(self.extra) 

32 extra = dict(self.extra) 

33 if kwargs.get("extra"): 

34 # when you use the "extra" standard kwargs at log() time 

35 extra.update(kwargs.pop("extra")) 

36 # Move any unknown keyword arguments into the extra 

37 # dictionary. 

38 for name in list(kwargs.keys()): 

39 if name in RESERVED_ATTRS: 

40 continue 

41 extra[name] = kwargs.pop(name) 

42 extra[STLOG_EXTRA_KEY] = set(extra.keys()) 

43 kwargs["extra"] = extra 

44 return msg, kwargs 

45 

46 

47class StLogLoggerAdapter(_KeywordArgumentAdapter): 

48 """stlog `LoggerAdapter` with `stlog.LogContext` support. 

49 

50 Attributes: 

51 context: reference to the `stlog.LogContext` static class, so you can 

52 manipulate the (global) execution log context directly from the 

53 logger, e.g. `logger.context.add(foo="bar")`. 

54 """ 

55 

56 context = LogContext 

57 

58 def __init__(self, logger, extra, ignore_global_logging_context: bool = False): 

59 self.ignore_global_logging_context = ignore_global_logging_context 

60 super().__init__(logger, extra) 

61 

62 def process( 

63 self, msg: typing.Any, kwargs: collections.abc.MutableMapping[str, typing.Any] 

64 ) -> tuple[typing.Any, collections.abc.MutableMapping[str, typing.Any]]: 

65 if self.ignore_global_logging_context: 

66 new_kwargs = dict(kwargs) 

67 else: 

68 new_kwargs = {**LogContext._get(), **kwargs} 

69 return super().process(msg, new_kwargs) 

70 

71 def addFilter(self, filter): # noqa: N802 

72 self.logger.addFilter(filter) 

73 

74 def removeFilter(self, filter): # noqa: N802 

75 self.logger.removeFilter(filter) 

76 

77 

78def getLogger(name: str | None = None, **kwargs) -> StLogLoggerAdapter: # noqa: N802 

79 """Return a standard logger (adapted for `stlog` and `stlog.LogContext` support). 

80 

81 You can pass some context key/values in `**kwargs` which will be specific to this logger. 

82 

83 If you want to set more globally available context, use `stlog.LogContext` class. 

84 

85 Args: 

86 name: logger name. 

87 """ 

88 return StLogLoggerAdapter(logging.getLogger(name), kwargs)