elven-logs-interceptor-python 0.1.3__tar.gz → 0.1.4__tar.gz
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.
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/PKG-INFO +1 -1
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/pyproject.toml +1 -1
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/src/logs_interceptor/application/log_service.py +3 -3
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/src/logs_interceptor/infrastructure/interceptors/runtime_interceptor.py +22 -0
- elven_logs_interceptor_python-0.1.4/src/logs_interceptor/integrations/loguru.py +93 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/src/logs_interceptor/presentation/factory.py +3 -3
- elven_logs_interceptor_python-0.1.4/tests/unit/test_loguru_sink.py +114 -0
- elven_logs_interceptor_python-0.1.4/tests/unit/test_runtime_interceptor.py +53 -0
- elven_logs_interceptor_python-0.1.3/src/logs_interceptor/integrations/loguru.py +0 -36
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/.github/workflows/ci.yml +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/.github/workflows/publish.yml +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/.gitignore +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/ARCHITECTURE.md +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/Makefile +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/README.md +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/examples/basic_app.py +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/examples/fastapi_integration.py +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/examples/full_config_reference.py +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/examples/high_volume.py +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/examples/tracking_usage.py +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/scripts/publish.sh +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/src/logs_interceptor/__init__.py +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/src/logs_interceptor/application/__init__.py +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/src/logs_interceptor/application/config_service.py +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/src/logs_interceptor/config.py +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/src/logs_interceptor/domain/__init__.py +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/src/logs_interceptor/domain/entities.py +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/src/logs_interceptor/domain/interfaces.py +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/src/logs_interceptor/domain/value_objects.py +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/src/logs_interceptor/infrastructure/__init__.py +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/src/logs_interceptor/infrastructure/buffer/__init__.py +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/src/logs_interceptor/infrastructure/buffer/memory_buffer.py +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/src/logs_interceptor/infrastructure/circuit_breaker/__init__.py +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/src/logs_interceptor/infrastructure/circuit_breaker/circuit_breaker.py +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/src/logs_interceptor/infrastructure/compression/__init__.py +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/src/logs_interceptor/infrastructure/compression/base.py +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/src/logs_interceptor/infrastructure/compression/brotli_compressor.py +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/src/logs_interceptor/infrastructure/compression/factory.py +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/src/logs_interceptor/infrastructure/compression/gzip_compressor.py +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/src/logs_interceptor/infrastructure/compression/noop_compressor.py +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/src/logs_interceptor/infrastructure/context/__init__.py +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/src/logs_interceptor/infrastructure/context/context_provider.py +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/src/logs_interceptor/infrastructure/dlq/__init__.py +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/src/logs_interceptor/infrastructure/dlq/file_dlq.py +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/src/logs_interceptor/infrastructure/dlq/memory_dlq.py +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/src/logs_interceptor/infrastructure/filter/__init__.py +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/src/logs_interceptor/infrastructure/filter/log_filter.py +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/src/logs_interceptor/infrastructure/interceptors/__init__.py +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/src/logs_interceptor/infrastructure/memory/__init__.py +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/src/logs_interceptor/infrastructure/memory/memory_tracker.py +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/src/logs_interceptor/infrastructure/metrics/__init__.py +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/src/logs_interceptor/infrastructure/metrics/metrics_collector.py +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/src/logs_interceptor/infrastructure/transport/__init__.py +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/src/logs_interceptor/infrastructure/transport/loki_json_transport.py +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/src/logs_interceptor/infrastructure/transport/loki_protobuf_transport.py +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/src/logs_interceptor/infrastructure/transport/resilient_transport.py +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/src/logs_interceptor/infrastructure/transport/transport_factory.py +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/src/logs_interceptor/infrastructure/workers/__init__.py +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/src/logs_interceptor/infrastructure/workers/worker_pool.py +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/src/logs_interceptor/integrations/__init__.py +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/src/logs_interceptor/integrations/celery.py +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/src/logs_interceptor/integrations/django.py +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/src/logs_interceptor/integrations/fastapi.py +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/src/logs_interceptor/integrations/flask.py +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/src/logs_interceptor/integrations/logging_handler.py +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/src/logs_interceptor/integrations/structlog.py +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/src/logs_interceptor/preload.py +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/src/logs_interceptor/presentation/__init__.py +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/src/logs_interceptor/types.py +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/src/logs_interceptor/utils.py +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/test-apps/elven-live-demo/.env.example +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/test-apps/elven-live-demo/README.md +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/test-apps/elven-live-demo/app.py +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/test-apps/elven-live-demo/run.sh +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/test-apps/elven-observability-smoke/.env.example +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/test-apps/elven-observability-smoke/README.md +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/test-apps/elven-observability-smoke/app.py +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/test-apps/elven-observability-smoke/run.sh +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/tests/integration/test_api.py +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/tests/unit/test_circuit_breaker_extra.py +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/tests/unit/test_config_service.py +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/tests/unit/test_core_components.py +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/tests/unit/test_env_config.py +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/tests/unit/test_log_filter_extra.py +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/tests/unit/test_log_service_unit.py +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/tests/unit/test_loki_json_transport.py +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/tests/unit/test_memory_buffer_extra.py +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/tests/unit/test_protobuf_transport_safety.py +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/tests/unit/test_resilient_transport.py +0 -0
- {elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/tests/unit/test_utils_extra.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "elven-logs-interceptor-python"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.4"
|
|
8
8
|
description = "Production-grade logs interceptor for Python with Loki transport, resilience, batching, and framework integrations."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -22,11 +22,11 @@ try:
|
|
|
22
22
|
except Exception: # pragma: no cover
|
|
23
23
|
pass
|
|
24
24
|
|
|
25
|
+
otel_trace: Any | None
|
|
25
26
|
try:
|
|
26
|
-
from opentelemetry import trace as
|
|
27
|
+
from opentelemetry import trace as otel_trace
|
|
27
28
|
except Exception: # pragma: no cover - optional dependency
|
|
28
|
-
|
|
29
|
-
otel_trace: Any = _otel_trace
|
|
29
|
+
otel_trace = None
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
@dataclass(slots=True)
|
|
@@ -11,6 +11,18 @@ from ...domain.interfaces import ILogger
|
|
|
11
11
|
from ...types import LogLevel
|
|
12
12
|
from ...utils import safe_stringify
|
|
13
13
|
|
|
14
|
+
_IGNORED_LOGGER_PREFIXES = (
|
|
15
|
+
"httpcore",
|
|
16
|
+
"httpx",
|
|
17
|
+
"urllib3",
|
|
18
|
+
"hpack",
|
|
19
|
+
"h2",
|
|
20
|
+
"h11",
|
|
21
|
+
"opentelemetry",
|
|
22
|
+
"logs_interceptor",
|
|
23
|
+
"elven_unified_observability",
|
|
24
|
+
)
|
|
25
|
+
|
|
14
26
|
|
|
15
27
|
class _BridgeLoggingHandler(logging.Handler):
|
|
16
28
|
def __init__(self, logger: ILogger) -> None:
|
|
@@ -19,6 +31,9 @@ class _BridgeLoggingHandler(logging.Handler):
|
|
|
19
31
|
|
|
20
32
|
def emit(self, record: logging.LogRecord) -> None:
|
|
21
33
|
try:
|
|
34
|
+
if record.name.startswith(_IGNORED_LOGGER_PREFIXES):
|
|
35
|
+
return
|
|
36
|
+
|
|
22
37
|
level = self._map_level(record.levelno)
|
|
23
38
|
msg = record.getMessage()
|
|
24
39
|
context = {
|
|
@@ -56,12 +71,17 @@ class RuntimeInterceptor:
|
|
|
56
71
|
self._original_print = builtins.print
|
|
57
72
|
self._original_excepthook = sys.excepthook
|
|
58
73
|
self._root_logger = logging.getLogger()
|
|
74
|
+
self._original_root_level = self._root_logger.level
|
|
59
75
|
self._bridge_handler = _BridgeLoggingHandler(logger)
|
|
60
76
|
self._original_handlers: list[logging.Handler] = []
|
|
61
77
|
|
|
62
78
|
def enable(self) -> None:
|
|
63
79
|
if self._enabled:
|
|
64
80
|
return
|
|
81
|
+
|
|
82
|
+
self._original_print = builtins.print
|
|
83
|
+
self._original_excepthook = sys.excepthook
|
|
84
|
+
self._original_root_level = self._root_logger.level
|
|
65
85
|
self._enabled = True
|
|
66
86
|
|
|
67
87
|
self._patch_print()
|
|
@@ -80,6 +100,8 @@ class RuntimeInterceptor:
|
|
|
80
100
|
except ValueError:
|
|
81
101
|
pass
|
|
82
102
|
|
|
103
|
+
self._root_logger.setLevel(self._original_root_level)
|
|
104
|
+
|
|
83
105
|
if not self._preserve_original:
|
|
84
106
|
self._root_logger.handlers = self._original_handlers
|
|
85
107
|
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Mapping
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from ..domain.interfaces import ILogger
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class LoguruSink:
|
|
10
|
+
def __init__(self, logger: ILogger, exclude_prefixes: list[str] | None = None) -> None:
|
|
11
|
+
self.logger = logger
|
|
12
|
+
self._exclude_prefixes = tuple(
|
|
13
|
+
prefix.lower().strip()
|
|
14
|
+
for prefix in (exclude_prefixes or [])
|
|
15
|
+
if prefix and prefix.strip()
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
def __call__(self, message: Any) -> None:
|
|
19
|
+
try:
|
|
20
|
+
record = getattr(message, "record", None)
|
|
21
|
+
if not isinstance(record, Mapping):
|
|
22
|
+
self.logger.info(str(message), {"source": "loguru"})
|
|
23
|
+
return
|
|
24
|
+
|
|
25
|
+
if self._should_ignore(record):
|
|
26
|
+
return
|
|
27
|
+
|
|
28
|
+
level = self._resolve_level(record)
|
|
29
|
+
extra = record.get("extra")
|
|
30
|
+
normalized_extra = dict(extra) if isinstance(extra, Mapping) else extra
|
|
31
|
+
|
|
32
|
+
self.logger.log(
|
|
33
|
+
level, # type: ignore[arg-type]
|
|
34
|
+
str(record.get("message", "")),
|
|
35
|
+
{
|
|
36
|
+
"source": "loguru",
|
|
37
|
+
"logger_name": record.get("name"),
|
|
38
|
+
"module": record.get("module"),
|
|
39
|
+
"function": record.get("function"),
|
|
40
|
+
"line": record.get("line"),
|
|
41
|
+
"extra": normalized_extra,
|
|
42
|
+
},
|
|
43
|
+
)
|
|
44
|
+
except Exception:
|
|
45
|
+
return
|
|
46
|
+
|
|
47
|
+
@staticmethod
|
|
48
|
+
def _resolve_level(record: Mapping[str, Any]) -> str:
|
|
49
|
+
raw_level = record.get("level")
|
|
50
|
+
if isinstance(raw_level, Mapping):
|
|
51
|
+
level = str(raw_level.get("name", "INFO")).lower()
|
|
52
|
+
level_number = raw_level.get("no")
|
|
53
|
+
else:
|
|
54
|
+
level = str(getattr(raw_level, "name", "INFO")).lower()
|
|
55
|
+
level_number = getattr(raw_level, "no", None)
|
|
56
|
+
|
|
57
|
+
if level == "warning":
|
|
58
|
+
return "warn"
|
|
59
|
+
if level == "critical":
|
|
60
|
+
return "fatal"
|
|
61
|
+
if level not in {"debug", "info", "warn", "error", "fatal"}:
|
|
62
|
+
if not level and isinstance(level_number, (int, float)):
|
|
63
|
+
if level_number >= 50:
|
|
64
|
+
return "fatal"
|
|
65
|
+
if level_number >= 40:
|
|
66
|
+
return "error"
|
|
67
|
+
if level_number >= 30:
|
|
68
|
+
return "warn"
|
|
69
|
+
if level_number >= 20:
|
|
70
|
+
return "info"
|
|
71
|
+
return "debug"
|
|
72
|
+
return "info"
|
|
73
|
+
return level
|
|
74
|
+
|
|
75
|
+
def _should_ignore(self, record: Mapping[str, Any]) -> bool:
|
|
76
|
+
if not self._exclude_prefixes:
|
|
77
|
+
return False
|
|
78
|
+
|
|
79
|
+
name = str(record.get("name") or "").lower()
|
|
80
|
+
module = str(record.get("module") or "").lower()
|
|
81
|
+
extra = record.get("extra")
|
|
82
|
+
extra_logger_name = ""
|
|
83
|
+
if isinstance(extra, Mapping):
|
|
84
|
+
extra_logger_name = str(extra.get("logger_name") or "").lower()
|
|
85
|
+
|
|
86
|
+
for prefix in self._exclude_prefixes:
|
|
87
|
+
if (
|
|
88
|
+
name.startswith(prefix)
|
|
89
|
+
or module.startswith(prefix)
|
|
90
|
+
or extra_logger_name.startswith(prefix)
|
|
91
|
+
):
|
|
92
|
+
return True
|
|
93
|
+
return False
|
|
@@ -15,11 +15,11 @@ from ..infrastructure.filter import LogFilter
|
|
|
15
15
|
from ..infrastructure.interceptors import RuntimeInterceptor
|
|
16
16
|
from ..infrastructure.transport import TransportFactory
|
|
17
17
|
|
|
18
|
+
otel_trace: Any | None
|
|
18
19
|
try:
|
|
19
|
-
from opentelemetry import trace as
|
|
20
|
+
from opentelemetry import trace as otel_trace
|
|
20
21
|
except Exception: # pragma: no cover - optional dependency
|
|
21
|
-
|
|
22
|
-
otel_trace: Any = _otel_trace
|
|
22
|
+
otel_trace = None
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
@dataclass(slots=True)
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from logs_interceptor.integrations.loguru import LoguruSink
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class _Logger:
|
|
7
|
+
def __init__(self) -> None:
|
|
8
|
+
self.calls: list[tuple[str, str, dict[str, object] | None]] = []
|
|
9
|
+
|
|
10
|
+
def info(self, message, context=None):
|
|
11
|
+
self.calls.append(("info", message, context))
|
|
12
|
+
|
|
13
|
+
def log(self, level, message, context=None):
|
|
14
|
+
self.calls.append((level, message, context))
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class _Message:
|
|
18
|
+
def __init__(self, record):
|
|
19
|
+
self.record = record
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class _Level:
|
|
23
|
+
def __init__(self, name: str, no: int) -> None:
|
|
24
|
+
self.name = name
|
|
25
|
+
self.no = no
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_loguru_sink_records_logger_name_and_metadata() -> None:
|
|
29
|
+
logger = _Logger()
|
|
30
|
+
sink = LoguruSink(logger)
|
|
31
|
+
|
|
32
|
+
sink(
|
|
33
|
+
_Message(
|
|
34
|
+
{
|
|
35
|
+
"name": "service.api",
|
|
36
|
+
"module": "app",
|
|
37
|
+
"function": "handler",
|
|
38
|
+
"line": 42,
|
|
39
|
+
"message": "hello",
|
|
40
|
+
"level": {"name": "INFO"},
|
|
41
|
+
"extra": {"request_id": "req-1"},
|
|
42
|
+
}
|
|
43
|
+
)
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
assert logger.calls == [
|
|
47
|
+
(
|
|
48
|
+
"info",
|
|
49
|
+
"hello",
|
|
50
|
+
{
|
|
51
|
+
"source": "loguru",
|
|
52
|
+
"logger_name": "service.api",
|
|
53
|
+
"module": "app",
|
|
54
|
+
"function": "handler",
|
|
55
|
+
"line": 42,
|
|
56
|
+
"extra": {"request_id": "req-1"},
|
|
57
|
+
},
|
|
58
|
+
)
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def test_loguru_sink_excludes_configured_prefixes() -> None:
|
|
63
|
+
logger = _Logger()
|
|
64
|
+
sink = LoguruSink(logger, exclude_prefixes=["httpx", "httpcore"])
|
|
65
|
+
|
|
66
|
+
sink(
|
|
67
|
+
_Message(
|
|
68
|
+
{
|
|
69
|
+
"name": "httpx",
|
|
70
|
+
"module": "client",
|
|
71
|
+
"function": "send",
|
|
72
|
+
"line": 1,
|
|
73
|
+
"message": "request",
|
|
74
|
+
"level": {"name": "DEBUG"},
|
|
75
|
+
"extra": {},
|
|
76
|
+
}
|
|
77
|
+
)
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
assert logger.calls == []
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def test_loguru_sink_supports_loguru_record_level_objects() -> None:
|
|
84
|
+
logger = _Logger()
|
|
85
|
+
sink = LoguruSink(logger)
|
|
86
|
+
|
|
87
|
+
sink(
|
|
88
|
+
_Message(
|
|
89
|
+
{
|
|
90
|
+
"name": "service.api",
|
|
91
|
+
"module": "app",
|
|
92
|
+
"function": "handler",
|
|
93
|
+
"line": 42,
|
|
94
|
+
"message": "hello",
|
|
95
|
+
"level": _Level("WARNING", 30),
|
|
96
|
+
"extra": {"request_id": "req-1"},
|
|
97
|
+
}
|
|
98
|
+
)
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
assert logger.calls == [
|
|
102
|
+
(
|
|
103
|
+
"warn",
|
|
104
|
+
"hello",
|
|
105
|
+
{
|
|
106
|
+
"source": "loguru",
|
|
107
|
+
"logger_name": "service.api",
|
|
108
|
+
"module": "app",
|
|
109
|
+
"function": "handler",
|
|
110
|
+
"line": 42,
|
|
111
|
+
"extra": {"request_id": "req-1"},
|
|
112
|
+
},
|
|
113
|
+
)
|
|
114
|
+
]
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
from logs_interceptor.infrastructure.interceptors.runtime_interceptor import RuntimeInterceptor
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class _Logger:
|
|
9
|
+
def __init__(self) -> None:
|
|
10
|
+
self.records: list[tuple[str, str, dict[str, object] | None]] = []
|
|
11
|
+
|
|
12
|
+
def log(self, level, message, context=None):
|
|
13
|
+
self.records.append((level, message, context))
|
|
14
|
+
|
|
15
|
+
def info(self, message, context=None):
|
|
16
|
+
self.records.append(("info", message, context))
|
|
17
|
+
|
|
18
|
+
def fatal(self, message, context=None):
|
|
19
|
+
self.records.append(("fatal", message, context))
|
|
20
|
+
|
|
21
|
+
def flush(self):
|
|
22
|
+
return None
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_runtime_interceptor_restores_root_logger_level() -> None:
|
|
26
|
+
root = logging.getLogger()
|
|
27
|
+
original_level = root.level
|
|
28
|
+
root.setLevel(logging.WARNING)
|
|
29
|
+
|
|
30
|
+
interceptor = RuntimeInterceptor(_Logger())
|
|
31
|
+
interceptor.enable()
|
|
32
|
+
|
|
33
|
+
assert root.level == logging.DEBUG
|
|
34
|
+
|
|
35
|
+
interceptor.restore()
|
|
36
|
+
|
|
37
|
+
assert root.level == logging.WARNING
|
|
38
|
+
root.setLevel(original_level)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_runtime_interceptor_ignores_known_noisy_logger_prefixes() -> None:
|
|
42
|
+
logger = _Logger()
|
|
43
|
+
interceptor = RuntimeInterceptor(logger)
|
|
44
|
+
interceptor.enable()
|
|
45
|
+
|
|
46
|
+
noisy_logger = logging.getLogger("elven_unified_observability.runtime")
|
|
47
|
+
noisy_logger.warning("internal warning")
|
|
48
|
+
noisy_otel_logger = logging.getLogger("opentelemetry.sdk.trace")
|
|
49
|
+
noisy_otel_logger.info("trace noise")
|
|
50
|
+
|
|
51
|
+
assert logger.records == []
|
|
52
|
+
|
|
53
|
+
interceptor.restore()
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from typing import Any
|
|
4
|
-
|
|
5
|
-
from ..domain.interfaces import ILogger
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class LoguruSink:
|
|
9
|
-
def __init__(self, logger: ILogger) -> None:
|
|
10
|
-
self.logger = logger
|
|
11
|
-
|
|
12
|
-
def __call__(self, message: Any) -> None:
|
|
13
|
-
record = getattr(message, "record", None)
|
|
14
|
-
if record is None:
|
|
15
|
-
self.logger.info(str(message), {"source": "loguru"})
|
|
16
|
-
return
|
|
17
|
-
|
|
18
|
-
level = str(record.get("level", {}).get("name", "INFO")).lower()
|
|
19
|
-
if level == "warning":
|
|
20
|
-
level = "warn"
|
|
21
|
-
if level == "critical":
|
|
22
|
-
level = "fatal"
|
|
23
|
-
if level not in {"debug", "info", "warn", "error", "fatal"}:
|
|
24
|
-
level = "info"
|
|
25
|
-
|
|
26
|
-
self.logger.log(
|
|
27
|
-
level, # type: ignore[arg-type]
|
|
28
|
-
str(record.get("message", "")),
|
|
29
|
-
{
|
|
30
|
-
"source": "loguru",
|
|
31
|
-
"module": record.get("module"),
|
|
32
|
-
"function": record.get("function"),
|
|
33
|
-
"line": record.get("line"),
|
|
34
|
-
"extra": record.get("extra"),
|
|
35
|
-
},
|
|
36
|
-
)
|
{elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/.github/workflows/ci.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/examples/basic_app.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/examples/high_volume.py
RENAMED
|
File without changes
|
|
File without changes
|
{elven_logs_interceptor_python-0.1.3 → elven_logs_interceptor_python-0.1.4}/scripts/publish.sh
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|