voltwire-fastapi-logging 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,35 @@
1
+ __version__ = "0.0.0"
2
+
3
+ from typing import Any, Protocol
4
+
5
+ from pydantic import BaseModel
6
+
7
+ from voltwire.fastapi.logging._constants import log_context
8
+ from voltwire.fastapi.logging.middleware import ContextEnricher
9
+
10
+ __all__ = ["ContextEnricher", "log_context", "integration_logger", "log_banner", "IntegrationLogContext", "AppRuntimeSettings"]
11
+
12
+
13
+ class IntegrationLogContext(BaseModel):
14
+ integration: str
15
+ message: str
16
+
17
+
18
+ class AppRuntimeSettings(Protocol):
19
+ app_name: str
20
+ version: str
21
+ environment: str
22
+
23
+
24
+ def log_banner(settings: AppRuntimeSettings):
25
+ print(
26
+ f"Application: '{settings.app_name}' Version: '{settings.version}' Environment: '{settings.environment}'",
27
+ flush=True,
28
+ )
29
+
30
+
31
+ def integration_logger(context: IntegrationLogContext, **extra: Any):
32
+ from loguru import logger
33
+
34
+ context_dict = {**context.model_dump(), **extra}
35
+ return logger.contextualize(**context_dict)
@@ -0,0 +1,11 @@
1
+ from typing import Any
2
+
3
+ from loguru import logger
4
+
5
+ APP_LOG_FORMAT = (
6
+ "<level>{level}</level>: <green>{time:YYYY-MM-DDTHH:mm:s:SSS!UTC}</green> "
7
+ "| {process}:{thread} | {name}:{line} - {message} | {extra}"
8
+ )
9
+
10
+ def log_context(**extra: Any):
11
+ return logger.contextualize(**extra)
@@ -0,0 +1,81 @@
1
+ import logging
2
+ import sys
3
+ from dataclasses import dataclass
4
+ from typing import Protocol
5
+
6
+ from loguru import logger
7
+
8
+ from voltwire.fastapi.logging._constants import APP_LOG_FORMAT
9
+
10
+
11
+ class LogSettings(Protocol):
12
+ level: str
13
+
14
+
15
+ @dataclass
16
+ class DefaultLogSettings:
17
+ level: str = "INFO"
18
+
19
+
20
+ # Loggers known to attach their own handlers directly (bypassing propagation
21
+ # to root) rather than relying on the standard logging tree.
22
+ _DIRECTLY_HANDLED_LOGGERS = (
23
+ "uvicorn",
24
+ "uvicorn.access",
25
+ "uvicorn.error",
26
+ "gunicorn",
27
+ "gunicorn.access",
28
+ "gunicorn.error",
29
+ "sqlalchemy",
30
+ "sqlalchemy.engine",
31
+ )
32
+
33
+
34
+ class InterceptHandler(logging.Handler):
35
+ """Forwards stdlib `logging` records into loguru.
36
+
37
+ Installed on the root logger so any code using plain
38
+ `logging.getLogger(__name__)` (this codebase, other voltwire
39
+ packages, or third-party dependencies) is routed through loguru
40
+ without those callers needing to know loguru exists.
41
+ """
42
+
43
+ def emit(self, record: logging.LogRecord) -> None:
44
+ try:
45
+ level = logger.level(record.levelname).name
46
+ except ValueError:
47
+ level = record.levelno
48
+
49
+ frame, depth = logging.currentframe(), 0
50
+ while frame and (depth == 0 or frame.f_code.co_filename == logging.__file__):
51
+ frame = frame.f_back
52
+ depth += 1
53
+
54
+ logger.opt(depth=depth, exception=record.exc_info).log(
55
+ level, record.getMessage()
56
+ )
57
+
58
+
59
+ def setup_logging(settings: LogSettings):
60
+
61
+ # ensure all python and loguru loggers are clean
62
+ logger.remove()
63
+ root = logging.getLogger()
64
+ for handler in root.handlers[:]:
65
+ root.removeHandler(handler)
66
+
67
+ logging.basicConfig(handlers=[InterceptHandler()], level=0, force=True)
68
+
69
+ # some libraries attach handlers directly to their own named logger
70
+ # instead of propagating to root - strip those so they route through
71
+ # the intercept handler too.
72
+ for name in _DIRECTLY_HANDLED_LOGGERS:
73
+ named_logger = logging.getLogger(name)
74
+ named_logger.handlers = []
75
+ named_logger.propagate = True
76
+
77
+ logger.add(
78
+ sys.stdout,
79
+ format=APP_LOG_FORMAT,
80
+ level=settings.level,
81
+ )
@@ -0,0 +1,55 @@
1
+ from dataclasses import dataclass, field
2
+ from typing import Protocol, Callable, Awaitable, Any
3
+
4
+ from loguru import logger
5
+ from starlette.middleware.base import BaseHTTPMiddleware
6
+ from starlette.requests import Request
7
+
8
+ from voltwire.fastapi.logging._constants import log_context
9
+
10
+ ContextEnricher = Callable[[Request, dict[str, Any]], Awaitable[None]]
11
+
12
+
13
+ class LoggingMiddlewareSettings(Protocol):
14
+ context_enrichers: list[ContextEnricher]
15
+
16
+ @dataclass
17
+ class DefaultLoggingMiddlewareSettings:
18
+ context_enrichers: list[ContextEnricher] = field(default_factory=list)
19
+
20
+ class LoggingContextMiddleware(BaseHTTPMiddleware):
21
+ """
22
+ This middleware enriches every log entry with request context.
23
+ - Adds method and path to all logs within the request scope
24
+ - Executes custom context enrichers to add additional context
25
+
26
+ Custom context enrichers can be registered via settings:
27
+ async def custom_enricher(request: Request, context: dict[str, Any]) -> None:
28
+ context["custom_field"] = "custom_value"
29
+
30
+ settings = DefaultLoggingMiddlewareSettings(
31
+ context_enrichers=[custom_enricher]
32
+ )
33
+ app.add_middleware(LoggingContextMiddleware, settings=settings)
34
+ """
35
+
36
+ def __init__(self, app, settings: LoggingMiddlewareSettings):
37
+ super().__init__(app)
38
+ self.settings = settings
39
+
40
+ async def dispatch(self, request: Request, call_next):
41
+ request_context = {"method": request.method, "path": request.url.path}
42
+
43
+ # Execute custom context enrichers
44
+ for enricher in self.settings.context_enrichers:
45
+ try:
46
+ await enricher(request, request_context)
47
+ except Exception as e:
48
+ logger.warning(f"Context enricher {enricher.__name__} failed: {str(e)}")
49
+
50
+ # Process the request within loguru context
51
+ with log_context(**request_context):
52
+ logger.trace(f"Request started: {request.method} {request.url.path}")
53
+ response = await call_next(request)
54
+ logger.trace(f"Request completed: {request.method} {request.url.path} - {response.status_code}")
55
+ return response
@@ -0,0 +1,40 @@
1
+ Metadata-Version: 2.5
2
+ Name: voltwire-fastapi-logging
3
+ Version: 0.0.1
4
+ Summary: Logging utilities for FastAPI apps
5
+ Author-email: Hermann Steidel <hsteidel.software@gmail.com>
6
+ License-Expression: MIT
7
+ Requires-Python: <4.0,>=3.13
8
+ Requires-Dist: loguru>=0.7.0
9
+ Requires-Dist: pydantic>=2.0.0
10
+ Requires-Dist: starlette>=0.27.0
11
+ Description-Content-Type: text/markdown
12
+
13
+ <img src="https://raw.githubusercontent.com/hsteidel/voltwire/main/assets/icons/fastapi-logging.svg" alt="" width="56" height="56" align="left">
14
+
15
+ # voltwire-fastapi-logging
16
+
17
+ Logging utilities for FastAPI apps: request-context-enriching middleware.
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ pip install voltwire-fastapi-logging
23
+ ```
24
+
25
+ ## Quickstart
26
+
27
+ ```python
28
+ from voltwire.fastapi.logging import setup_logging, DefaultLogSettings
29
+
30
+ setup_logging(DefaultLogSettings(enable_json=False, level="INFO"))
31
+ ```
32
+
33
+ Add `LoggingContextMiddleware` to enrich every log entry within a request with a correlation
34
+ ID, method, and path:
35
+
36
+ ```python
37
+ from voltwire.fastapi.logging import LoggingContextMiddleware, DefaultLoggingMiddlewareSettings
38
+
39
+ app.add_middleware(LoggingContextMiddleware, settings=DefaultLoggingMiddlewareSettings())
40
+ ```
@@ -0,0 +1,7 @@
1
+ voltwire/fastapi/logging/__init__.py,sha256=gHFrqH3OIg4-YueYIWOfv4yGJcsUwEYUc5WPfn2AjZA,920
2
+ voltwire/fastapi/logging/_constants.py,sha256=Ntzvv3fLW9ZsYv_yB4uHJXGSOg0ivGbTcXBN5_Dg5SU,290
3
+ voltwire/fastapi/logging/base.py,sha256=cKqyGid75Wek7zRaPyKMvw_8KIc49gY62LcMFgSdB9I,2212
4
+ voltwire/fastapi/logging/middleware.py,sha256=7k1aT52aTtRuVkpyG-DJPtjh-0rOZZKvwgwLBlfNG0E,2163
5
+ voltwire_fastapi_logging-0.0.1.dist-info/METADATA,sha256=lrjFI89ITQcxZHMZfPAcfePOvZo_3AUYsWx3zmw6Dtw,1161
6
+ voltwire_fastapi_logging-0.0.1.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
7
+ voltwire_fastapi_logging-0.0.1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any