-
Notifications
You must be signed in to change notification settings - Fork 32
⚗️Introduce asynchronous logging facilities (🚨) #8064
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
Changes from all commits
31451c9
2805704
e861d0d
41815e2
3820a52
8f06009
5111d4c
03d05a5
d987930
d3f87d1
9244c37
70c5835
7efa167
9843048
9eef0e4
8f88d44
d58c88b
023bc57
1b419b1
d946a65
ccf5d33
716cfcd
3e242f2
c4cdf2f
fcacc20
3430474
dcebbc1
2594842
c6f8e11
fd6fbf8
b7134f3
c270e82
40cafe9
cce07aa
0964b1a
80afced
ea564b3
2df7185
e9a37da
652d04f
0ae5828
47dcfe9
fce15f7
b439f0e
c7e00d6
78265ce
ce03687
ef55858
deb9fa4
b5509d0
093f4c4
0f4eda1
e610fd1
3dca72a
25496b1
cf97990
5389664
020a1d7
4871eb4
8d8649d
1f42def
7e375dd
4b6105a
94e762c
cd09591
668a16f
c395d7c
327549d
d24dc1c
5c0e2bf
011da28
c7c50a8
ddc62a6
61f059a
68a0b6a
9dc0f18
2e3a070
eefcc3e
29eee79
11dbf96
530472d
3c70f80
f610b1a
79ac626
0de3c63
5fa9a1e
a1ed743
7f27fca
9775a8f
1179058
4ef2c72
555a87c
b17ccdb
a82f485
f14cabd
628b50e
04a2d3b
10e660d
dcec94d
83f830a
43c334c
dc0223e
5a5fced
575ce89
ed3894b
7486db2
59b20d7
55aca25
6800114
c4e234d
3ec8bc7
0631674
72f0b73
395e99b
ce18732
8cb3c06
15914d5
94e79a2
ecc9ab0
9d584fa
7ed8fe5
8a014e9
f053156
1667911
d7b3caf
a7fb29b
1d141ef
fae4400
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| # In conftest.py or test_logging_utils.py | ||
| import contextlib | ||
| import logging | ||
| from collections.abc import Iterator | ||
| from contextlib import contextmanager | ||
|
|
||
| import pytest | ||
| from pytest_mock import MockerFixture | ||
| from servicelib.logging_utils import async_loggers | ||
|
|
||
|
|
||
| @pytest.fixture(autouse=True) | ||
| def preserve_caplog_for_async_logging(mocker: MockerFixture) -> None: | ||
| # Patch async_loggers to preserve caplog handlers, | ||
| # and pytest logs in general as pytest captures logs in a special way | ||
| # that is not compatible with the queue handler used in async logging. | ||
| original_setup = async_loggers | ||
|
|
||
| @contextmanager | ||
| def patched_async_loggers(**kwargs) -> Iterator[None]: | ||
| # Find caplog's handler in root logger | ||
| root_logger = logging.getLogger() | ||
| caplog_handlers = [ | ||
| h for h in root_logger.handlers if "LogCaptureHandler" in f"{type(h)}" | ||
| ] | ||
|
|
||
| with original_setup(**kwargs): | ||
| # After setup, restore caplog handlers alongside queue handler | ||
| for handler in caplog_handlers: | ||
| if handler not in root_logger.handlers: | ||
| root_logger.addHandler(handler) | ||
| yield | ||
|
|
||
| methods_to_patch = [ | ||
| "servicelib.logging_utils.async_loggers", | ||
| "servicelib.fastapi.logging_lifespan.async_loggers", | ||
| "tests.test_logging_utils.async_loggers", | ||
| ] | ||
| for method in methods_to_patch: | ||
| with contextlib.suppress(AttributeError, ModuleNotFoundError): | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what is this suppress for? shouldnt will allow to fail if e.g. we change the name of themodules etc?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah I need to rework that one. I tried to cover all cases, but I see that this might backfire. will think about it further |
||
| # Patch the method to use our patched version | ||
| mocker.patch(method, patched_async_loggers) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| import logging | ||
| from collections.abc import AsyncIterator, Awaitable, Callable | ||
| from contextlib import AsyncExitStack | ||
|
|
||
| from fastapi import FastAPI | ||
| from settings_library.tracing import TracingSettings | ||
|
|
||
| from ..logging_utils import ( | ||
| LogLevelInt, | ||
| async_loggers, | ||
| log_context, | ||
| ) | ||
| from ..logging_utils_filtering import LoggerName, MessageSubstring | ||
| from .lifespan_utils import Lifespan | ||
|
|
||
| _logger = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| def create_logging_lifespan( | ||
| *, | ||
| log_format_local_dev_enabled: bool, | ||
| logger_filter_mapping: dict[LoggerName, list[MessageSubstring]], | ||
| tracing_settings: TracingSettings | None, | ||
| log_base_level: LogLevelInt, | ||
| noisy_loggers: tuple[str, ...] | None, | ||
| ) -> Lifespan: | ||
| """Returns a FastAPI-compatible lifespan handler to set up async logging.""" | ||
| exit_stack = AsyncExitStack() | ||
| exit_stack.enter_context( | ||
| async_loggers( | ||
| log_base_level=log_base_level, | ||
| noisy_loggers=noisy_loggers, | ||
| log_format_local_dev_enabled=log_format_local_dev_enabled, | ||
| logger_filter_mapping=logger_filter_mapping, | ||
| tracing_settings=tracing_settings, | ||
| ) | ||
| ) | ||
|
|
||
| async def _logging_lifespan(app: FastAPI) -> AsyncIterator[None]: | ||
| assert app is not None, "app must be provided" | ||
| yield | ||
| with log_context(_logger, logging.INFO, "Re-enable Blocking logger"): | ||
| await exit_stack.aclose() | ||
|
|
||
| return _logging_lifespan | ||
|
|
||
|
|
||
| def create_logging_shutdown_event( | ||
| *, | ||
| log_format_local_dev_enabled: bool, | ||
| logger_filter_mapping: dict[LoggerName, list[MessageSubstring]], | ||
| tracing_settings: TracingSettings | None, | ||
| log_base_level: LogLevelInt, | ||
| noisy_loggers: tuple[str, ...] | None, | ||
| ) -> Callable[[], Awaitable[None]]: | ||
| """retruns a fastapi-compatible shutdown event handler to be used with old style lifespan | ||
| handlers. This is useful for applications that do not use the new async lifespan | ||
| handlers introduced in fastapi 0.100.0. | ||
|
|
||
| Note: This function is for backwards compatibility only and will be removed in the future. | ||
| setup_logging_lifespan should be used instead for new style lifespan handlers. | ||
| """ | ||
| exit_stack = AsyncExitStack() | ||
| exit_stack.enter_context( | ||
| async_loggers( | ||
| log_base_level=log_base_level, | ||
| noisy_loggers=noisy_loggers, | ||
| log_format_local_dev_enabled=log_format_local_dev_enabled, | ||
| logger_filter_mapping=logger_filter_mapping, | ||
| tracing_settings=tracing_settings, | ||
| ) | ||
| ) | ||
|
|
||
| async def _on_shutdown_event() -> None: | ||
| with log_context(_logger, logging.INFO, "Re-enable Blocking logger"): | ||
| await exit_stack.aclose() | ||
|
|
||
| return _on_shutdown_event |
Uh oh!
There was an error while loading. Please reload this page.