Skip to content

Commit d4c1828

Browse files
committed
feat: add auto_init to add_global_context
1 parent 434372b commit d4c1828

File tree

3 files changed

+55
-4
lines changed

3 files changed

+55
-4
lines changed

docs/howto.md

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,27 @@ with add_global_context({"user_id": user_id, "request_id": request_id}):
4545
```
4646

4747

48+
## Using the automatic init
49+
50+
If you don't want to customise the initialization you can let `add_global_context` automatically handle the init and shutdown for you:
51+
52+
```python
53+
import logging
54+
55+
from logging_with_context.global_context import add_global_context
56+
57+
58+
def main():
59+
logging.basicConfig(level=logging.INFO) # Or any other way to setup logging.
60+
with add_global_context({"user_id": 10}):
61+
# Here the context is automatically initialized.
62+
# It'll also be automatically shutdown once this context manager finishes.
63+
```
64+
65+
4866
## Using the init/shutdown API
4967

50-
In case you can't use the context manager, you can use the manual initialization and shutdown API:
68+
In case you want to customise the initialization but can't use the context manager, you can use the manual initialization and shutdown API:
5169

5270
```python
5371
import logging

src/logging_with_context/global_context.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
__global_context_var: ContextVar[dict[str, Any]] = ContextVar(
1717
"global_context", default={}
1818
)
19+
__global_context_initialized: ContextVar[bool] = ContextVar(
20+
"global_context_initialized", default=False
21+
)
1922

2023

2124
def _get_loggers_to_process(loggers: Optional[Sequence[Logger]] = None) -> list[Logger]:
@@ -30,6 +33,7 @@ def init_global_context(loggers: Optional[Sequence[Logger]] = None) -> None:
3033
loggers: The loggers to attach the global context; if not loggers are specified
3134
it will use the root logger.
3235
"""
36+
__global_context_initialized.set(True)
3337
filter_with_context = FilterWithContextVar(__global_context_var)
3438
for logger in _get_loggers_to_process(loggers):
3539
for handler in logger.handlers:
@@ -44,6 +48,7 @@ def shutdown_global_context(loggers: Optional[Sequence[Logger]] = None) -> None:
4448
loggers: The loggers that were used when calling `init_global_context`; by
4549
default the root logger.
4650
"""
51+
__global_context_initialized.set(False)
4752
for logger in _get_loggers_to_process(loggers):
4853
for handler in logger.handlers:
4954
for filter_ in handler.filters:
@@ -74,20 +79,36 @@ def global_context_initialized(
7479

7580

7681
@contextmanager
77-
def add_global_context(context: dict[str, Any]) -> Generator[None, None, None]:
82+
def add_global_context(
83+
context: dict[str, Any], *, auto_init: bool = True
84+
) -> Generator[None, None, None]:
7885
"""
7986
Add values to the global context to be attached to all the log messages.
8087
8188
The values will be removed from the global context once the context manager exists.
8289
8390
Parameters:
8491
context: A key/value mapping with the values to add to the global context.
92+
auto_init: Indicate if the global context should be automatically initialized
93+
if it isn't.
94+
95+
If `True`, the context will be also automatically shutdown before exiting.
96+
97+
If the global context is already initialized it'll do nothing.
98+
99+
Keyword-only argument.
85100
86101
Returns:
87102
A context manager that manages the life of the values.
88103
"""
104+
auto_initialized = False
105+
if not __global_context_initialized.get() and auto_init:
106+
init_global_context()
107+
auto_initialized = True
89108
token = __global_context_var.set(__global_context_var.get() | context)
90109
try:
91110
yield
92111
finally:
93112
__global_context_var.reset(token)
113+
if auto_initialized:
114+
shutdown_global_context()

tests/logging_with_context/test_global_context.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ def test_add_global_context_ok(caplog: pytest.LogCaptureFixture):
2424

2525
def test_add_global_context_without_init_ignored_ok(caplog: pytest.LogCaptureFixture):
2626
logger = logging.getLogger(__name__)
27-
with add_global_context({"key": "value"}):
27+
with add_global_context({"key": "value"}, auto_init=False):
2828
with caplog.at_level(logging.INFO):
2929
logger.info("Test message")
3030
assert len(caplog.records) == 1
@@ -36,13 +36,25 @@ def test_add_global_context_after_shutdown_ignored_ok(caplog: pytest.LogCaptureF
3636
logger = logging.getLogger(__name__)
3737
with global_context_initialized():
3838
pass
39-
with add_global_context({"key": "value"}), caplog.at_level(logging.INFO):
39+
with (
40+
add_global_context({"key": "value"}, auto_init=False),
41+
caplog.at_level(logging.INFO),
42+
):
4043
logger.info("Test message")
4144
assert len(caplog.records) == 1
4245
result = caplog.records[0]
4346
assert not hasattr(result, "key")
4447

4548

49+
def test_add_global_context_auto_init_ok(caplog: pytest.LogCaptureFixture):
50+
logger = logging.getLogger(__name__)
51+
with add_global_context({"key": "value"}), caplog.at_level(logging.INFO):
52+
logger.info("Test message")
53+
assert len(caplog.records) == 1
54+
result = caplog.records[0]
55+
assert result.key == "value" # type: ignore
56+
57+
4658
def test_add_global_context_multithread(caplog: pytest.LogCaptureFixture):
4759
def worker(value: int) -> None:
4860
with add_global_context({"value": value}):

0 commit comments

Comments
 (0)