From 161cc768a3817e89c56e898bd787a92486a9d150 Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Tue, 11 Feb 2025 10:08:41 +0300 Subject: [PATCH 1/3] Fix logging exclude endpoints for FastAPI --- .../instruments/logging_instrument.py | 8 ++++++ microbootstrap/middlewares/fastapi.py | 3 ++- tests/instruments/test_logging.py | 27 ++++++++++++++++--- 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/microbootstrap/instruments/logging_instrument.py b/microbootstrap/instruments/logging_instrument.py index ae6ca77..591929d 100644 --- a/microbootstrap/instruments/logging_instrument.py +++ b/microbootstrap/instruments/logging_instrument.py @@ -3,6 +3,7 @@ import logging.handlers import time import typing +import typing_extensions import urllib.parse import pydantic @@ -128,6 +129,13 @@ class LoggingConfig(BaseInstrumentConfig): logging_exclude_endpoints: list[str] = pydantic.Field(default_factory=lambda: ["/health/", "/metrics"]) logging_turn_off_middleware: bool = False + @pydantic.model_validator(mode="after") + def remove_trailing_slashes_from_logging_exclude_endpoints(self) -> typing_extensions.Self: + self.logging_exclude_endpoints = [ + one_endpoint.removesuffix("/") for one_endpoint in self.logging_exclude_endpoints + ] + return self + class LoggingInstrument(Instrument[LoggingConfig]): instrument_name = "Logging" diff --git a/microbootstrap/middlewares/fastapi.py b/microbootstrap/middlewares/fastapi.py index 038b8a5..541a4ec 100644 --- a/microbootstrap/middlewares/fastapi.py +++ b/microbootstrap/middlewares/fastapi.py @@ -17,8 +17,9 @@ async def dispatch( request: fastapi.Request, call_next: RequestResponseEndpoint, ) -> fastapi.Response: + request_path: typing.Final = request.url.path.removesuffix("/") should_log: typing.Final = not any( - exclude_endpoint in str(request.url) for exclude_endpoint in exclude_endpoints + exclude_endpoint == request_path for exclude_endpoint in exclude_endpoints ) start_time: typing.Final = time.perf_counter_ns() try: diff --git a/tests/instruments/test_logging.py b/tests/instruments/test_logging.py index 7eeb382..732c7d3 100644 --- a/tests/instruments/test_logging.py +++ b/tests/instruments/test_logging.py @@ -1,9 +1,11 @@ import logging import typing from io import StringIO +from unittest import mock import fastapi import litestar +import pytest from fastapi.testclient import TestClient as FastAPITestClient from litestar.testing import TestClient as LitestarTestClient from opentelemetry import trace @@ -133,18 +135,37 @@ def test_memory_logger_factory_error() -> None: assert error_message in test_stream.getvalue() -def test_fastapi_logging_bootstrap_working(minimal_logging_config: LoggingConfig) -> None: - logging_instrument: typing.Final = FastApiLoggingInstrument(minimal_logging_config) - +def test_fastapi_logging_bootstrap_working( + monkeypatch: pytest.MonkeyPatch, minimal_logging_config: LoggingConfig +) -> None: fastapi_application: typing.Final = fastapi.FastAPI() @fastapi_application.get("/test-handler") async def test_handler() -> str: return "Ok" + logging_instrument: typing.Final = FastApiLoggingInstrument(minimal_logging_config) logging_instrument.bootstrap() logging_instrument.bootstrap_after(fastapi_application) + monkeypatch.setattr("microbootstrap.middlewares.fastapi.fill_log_message", fill_log_mock := mock.Mock()) with FastAPITestClient(app=fastapi_application) as test_client: test_client.get("/test-handler?test-query=1") test_client.get("/test-handler") + + assert fill_log_mock.call_count == 2 # noqa: PLR2004 + + +def test_fastapi_logging_bootstrap_ignores_health( + monkeypatch: pytest.MonkeyPatch, minimal_logging_config: LoggingConfig +) -> None: + fastapi_application: typing.Final = fastapi.FastAPI() + logging_instrument: typing.Final = FastApiLoggingInstrument(minimal_logging_config) + logging_instrument.bootstrap() + logging_instrument.bootstrap_after(fastapi_application) + monkeypatch.setattr("microbootstrap.middlewares.fastapi.fill_log_message", fill_log_mock := mock.Mock()) + + with FastAPITestClient(app=fastapi_application) as test_client: + test_client.get("/health") + + assert fill_log_mock.call_count == 0 From 7764e6f9639b9fb635c9156443b4cfcbe3b23769 Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Tue, 11 Feb 2025 10:12:34 +0300 Subject: [PATCH 2/3] Fix logging exclude endpoints for Litestar --- microbootstrap/middlewares/litestar.py | 5 ++--- tests/instruments/test_logging.py | 22 +++++++++++++++++++++- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/microbootstrap/middlewares/litestar.py b/microbootstrap/middlewares/litestar.py index 3411dc6..1a30e75 100644 --- a/microbootstrap/middlewares/litestar.py +++ b/microbootstrap/middlewares/litestar.py @@ -27,9 +27,8 @@ async def __call__( start_time: typing.Final[int] = time.perf_counter_ns() async def log_message_wrapper(message: litestar.types.Message) -> None: - should_log: typing.Final = not any( - exclude_endpoint in str(request.url) for exclude_endpoint in exclude_endpoints - ) + request_path = request.url.path.removesuffix("/") + should_log: typing.Final = not any(one_endpoint == request_path for one_endpoint in exclude_endpoints) if message["type"] == "http.response.start" and should_log: log_level: str = "info" if message["status"] < HTTP_500_INTERNAL_SERVER_ERROR else "exception" fill_log_message(log_level, request, message["status"], start_time) diff --git a/tests/instruments/test_logging.py b/tests/instruments/test_logging.py index 732c7d3..62343ef 100644 --- a/tests/instruments/test_logging.py +++ b/tests/instruments/test_logging.py @@ -53,7 +53,9 @@ def test_litestar_logging_bootstrap(minimal_logging_config: LoggingConfig) -> No assert len(bootsrap_result["middleware"]) == 1 -def test_litestar_logging_bootstrap_working(minimal_logging_config: LoggingConfig) -> None: +def test_litestar_logging_bootstrap_working( + monkeypatch: pytest.MonkeyPatch, minimal_logging_config: LoggingConfig +) -> None: logging_instrument: typing.Final = LitestarLoggingInstrument(minimal_logging_config) @litestar.get("/test-handler") @@ -65,11 +67,29 @@ async def error_handler() -> str: route_handlers=[error_handler], **logging_instrument.bootstrap_before(), ) + monkeypatch.setattr("microbootstrap.middlewares.litestar.fill_log_message", fill_log_mock := mock.Mock()) with LitestarTestClient(app=litestar_application) as test_client: test_client.get("/test-handler?test-query=1") test_client.get("/test-handler") + assert fill_log_mock.call_count == 2 # noqa: PLR2004 + +def test_litestar_logging_bootstrap_ignores_health( + monkeypatch: pytest.MonkeyPatch, minimal_logging_config: LoggingConfig +) -> None: + logging_instrument: typing.Final = LitestarLoggingInstrument(minimal_logging_config) + logging_instrument.bootstrap() + litestar_application: typing.Final = litestar.Litestar( + **logging_instrument.bootstrap_before() + ) + monkeypatch.setattr("microbootstrap.middlewares.litestar.fill_log_message", fill_log_mock := mock.Mock()) + + with LitestarTestClient(app=litestar_application) as test_client: + test_client.get("/health") + + assert fill_log_mock.call_count == 0 + def test_litestar_logging_bootstrap_tracer_injection(minimal_logging_config: LoggingConfig) -> None: trace.set_tracer_provider(TracerProvider()) From 85affec13bf8bafe9d997ddb43a1eaeaead72424 Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Tue, 11 Feb 2025 10:13:38 +0300 Subject: [PATCH 3/3] Fix formatting --- microbootstrap/instruments/logging_instrument.py | 2 +- tests/instruments/test_logging.py | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/microbootstrap/instruments/logging_instrument.py b/microbootstrap/instruments/logging_instrument.py index 591929d..ba14d4c 100644 --- a/microbootstrap/instruments/logging_instrument.py +++ b/microbootstrap/instruments/logging_instrument.py @@ -3,11 +3,11 @@ import logging.handlers import time import typing -import typing_extensions import urllib.parse import pydantic import structlog +import typing_extensions from opentelemetry import trace from microbootstrap.instruments.base import BaseInstrumentConfig, Instrument diff --git a/tests/instruments/test_logging.py b/tests/instruments/test_logging.py index 62343ef..b29ef35 100644 --- a/tests/instruments/test_logging.py +++ b/tests/instruments/test_logging.py @@ -75,14 +75,13 @@ async def error_handler() -> str: assert fill_log_mock.call_count == 2 # noqa: PLR2004 + def test_litestar_logging_bootstrap_ignores_health( monkeypatch: pytest.MonkeyPatch, minimal_logging_config: LoggingConfig ) -> None: logging_instrument: typing.Final = LitestarLoggingInstrument(minimal_logging_config) logging_instrument.bootstrap() - litestar_application: typing.Final = litestar.Litestar( - **logging_instrument.bootstrap_before() - ) + litestar_application: typing.Final = litestar.Litestar(**logging_instrument.bootstrap_before()) monkeypatch.setattr("microbootstrap.middlewares.litestar.fill_log_message", fill_log_mock := mock.Mock()) with LitestarTestClient(app=litestar_application) as test_client: