Commit db48a4be authored by Dimitrios Gogos's avatar Dimitrios Gogos
Browse files

feat: update logging configuration to use JSON

parent 5183de2a
Loading
Loading
Loading
Loading
+2 −1
Changes for src/open_exposure_gateway/core/logging.py: 2 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -26,7 +26,8 @@ def configure_logging(log_level: str = "INFO") -> None:
    formatter = structlog.stdlib.ProcessorFormatter(
        processors=[
            structlog.stdlib.ProcessorFormatter.remove_processors_meta,
            structlog.dev.ConsoleRenderer(),
            structlog.processors.format_exc_info,
            structlog.processors.JSONRenderer(),
        ],
        foreign_pre_chain=shared_processors,
    )
+2 −0
Changes for src/open_exposure_gateway/main.py: 2 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -452,8 +452,10 @@ async def default_lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
    await db_engine.dispose()
    logger.info("Database engine disposed")


_PROBE_PATHS = frozenset({"/platform/healthz", "/platform/readyz"})


def create_app(lifespan: Optional[Lifespan[FastAPI]] = None) -> FastAPI:
    """Build the application.

+51 −0
Changes for tests/unit/test_logging.py: 51 added lines, 0 removed lines.
Original line number Diff line number Diff line
"""Unit tests for configure_logging: every line is one JSON object."""

import json
import logging
from collections.abc import Iterator
from typing import Any

import pytest
import structlog

from open_exposure_gateway.core.logging import configure_logging


@pytest.fixture()
def output(capsys: pytest.CaptureFixture[str]) -> Iterator[pytest.CaptureFixture[str]]:
    # Configured by each test: pytest swaps sys.stdout between fixture setup and the test.
    root = logging.getLogger()
    handlers, level = root.handlers[:], root.level
    yield capsys
    structlog.reset_defaults()
    structlog.contextvars.clear_contextvars()
    root.handlers[:] = handlers
    root.setLevel(level)


def _line(output: pytest.CaptureFixture[str]) -> dict[str, Any]:
    line: dict[str, Any] = json.loads(output.readouterr().out)
    return line


def test_line_is_json_with_bound_caller(output: pytest.CaptureFixture[str]) -> None:
    configure_logging("INFO")
    structlog.contextvars.bind_contextvars(tenant_id="acme-labs")
    structlog.get_logger("test").info("http_request", status=201)

    assert {
        "event": "http_request",
        "status": 201,
        "level": "info",
        "tenant_id": "acme-labs",
    }.items() <= _line(output).items()


def test_traceback_is_kept(output: pytest.CaptureFixture[str]) -> None:
    configure_logging("INFO")
    try:
        raise ValueError("boom")
    except ValueError:
        structlog.get_logger("test").exception("Unhandled exception")

    assert "ValueError: boom" in _line(output)["exception"]