elven-logs-interceptor-python 0.1.2__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.
- elven_logs_interceptor_python-0.1.2.dist-info/METADATA +262 -0
- elven_logs_interceptor_python-0.1.2.dist-info/RECORD +56 -0
- elven_logs_interceptor_python-0.1.2.dist-info/WHEEL +4 -0
- logs_interceptor/__init__.py +333 -0
- logs_interceptor/application/__init__.py +27 -0
- logs_interceptor/application/config_service.py +232 -0
- logs_interceptor/application/log_service.py +383 -0
- logs_interceptor/config.py +190 -0
- logs_interceptor/domain/__init__.py +25 -0
- logs_interceptor/domain/entities.py +41 -0
- logs_interceptor/domain/interfaces.py +149 -0
- logs_interceptor/domain/value_objects.py +40 -0
- logs_interceptor/infrastructure/__init__.py +48 -0
- logs_interceptor/infrastructure/buffer/__init__.py +3 -0
- logs_interceptor/infrastructure/buffer/memory_buffer.py +187 -0
- logs_interceptor/infrastructure/circuit_breaker/__init__.py +3 -0
- logs_interceptor/infrastructure/circuit_breaker/circuit_breaker.py +110 -0
- logs_interceptor/infrastructure/compression/__init__.py +14 -0
- logs_interceptor/infrastructure/compression/base.py +20 -0
- logs_interceptor/infrastructure/compression/brotli_compressor.py +27 -0
- logs_interceptor/infrastructure/compression/factory.py +18 -0
- logs_interceptor/infrastructure/compression/gzip_compressor.py +20 -0
- logs_interceptor/infrastructure/compression/noop_compressor.py +14 -0
- logs_interceptor/infrastructure/context/__init__.py +3 -0
- logs_interceptor/infrastructure/context/context_provider.py +44 -0
- logs_interceptor/infrastructure/dlq/__init__.py +4 -0
- logs_interceptor/infrastructure/dlq/file_dlq.py +170 -0
- logs_interceptor/infrastructure/dlq/memory_dlq.py +59 -0
- logs_interceptor/infrastructure/filter/__init__.py +3 -0
- logs_interceptor/infrastructure/filter/log_filter.py +55 -0
- logs_interceptor/infrastructure/interceptors/__init__.py +3 -0
- logs_interceptor/infrastructure/interceptors/runtime_interceptor.py +139 -0
- logs_interceptor/infrastructure/memory/__init__.py +3 -0
- logs_interceptor/infrastructure/memory/memory_tracker.py +95 -0
- logs_interceptor/infrastructure/metrics/__init__.py +3 -0
- logs_interceptor/infrastructure/metrics/metrics_collector.py +104 -0
- logs_interceptor/infrastructure/transport/__init__.py +12 -0
- logs_interceptor/infrastructure/transport/loki_json_transport.py +226 -0
- logs_interceptor/infrastructure/transport/loki_protobuf_transport.py +209 -0
- logs_interceptor/infrastructure/transport/resilient_transport.py +161 -0
- logs_interceptor/infrastructure/transport/transport_factory.py +39 -0
- logs_interceptor/infrastructure/workers/__init__.py +3 -0
- logs_interceptor/infrastructure/workers/worker_pool.py +57 -0
- logs_interceptor/integrations/__init__.py +17 -0
- logs_interceptor/integrations/celery.py +53 -0
- logs_interceptor/integrations/django.py +44 -0
- logs_interceptor/integrations/fastapi.py +53 -0
- logs_interceptor/integrations/flask.py +50 -0
- logs_interceptor/integrations/logging_handler.py +43 -0
- logs_interceptor/integrations/loguru.py +36 -0
- logs_interceptor/integrations/structlog.py +21 -0
- logs_interceptor/preload.py +61 -0
- logs_interceptor/presentation/__init__.py +3 -0
- logs_interceptor/presentation/factory.py +128 -0
- logs_interceptor/types.py +89 -0
- logs_interceptor/utils.py +508 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from ..domain.interfaces import ILogger
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class FlaskExtension:
|
|
10
|
+
def __init__(self, logger: ILogger) -> None:
|
|
11
|
+
self.logger = logger
|
|
12
|
+
|
|
13
|
+
def init_app(self, app: Any) -> None:
|
|
14
|
+
@app.before_request
|
|
15
|
+
def _before_request() -> None:
|
|
16
|
+
from flask import g, request
|
|
17
|
+
|
|
18
|
+
g._logs_interceptor_start_time = time.time()
|
|
19
|
+
request_id = request.headers.get("X-Request-Id") or f"req-{int(time.time() * 1000)}"
|
|
20
|
+
g._logs_interceptor_request_id = request_id
|
|
21
|
+
|
|
22
|
+
@app.after_request
|
|
23
|
+
def _after_request(response: Any) -> Any:
|
|
24
|
+
from flask import g, request
|
|
25
|
+
|
|
26
|
+
start = getattr(g, "_logs_interceptor_start_time", time.time())
|
|
27
|
+
request_id = getattr(g, "_logs_interceptor_request_id", "")
|
|
28
|
+
duration_ms = int((time.time() - start) * 1000)
|
|
29
|
+
status_code = int(getattr(response, "status_code", 200))
|
|
30
|
+
|
|
31
|
+
level = "info"
|
|
32
|
+
if status_code >= 500:
|
|
33
|
+
level = "error"
|
|
34
|
+
elif status_code >= 400:
|
|
35
|
+
level = "warn"
|
|
36
|
+
|
|
37
|
+
self.logger.with_context(
|
|
38
|
+
{"request_id": request_id},
|
|
39
|
+
lambda: self.logger.log(
|
|
40
|
+
level, # type: ignore[arg-type]
|
|
41
|
+
f"{request.method} {request.path}",
|
|
42
|
+
{
|
|
43
|
+
"source": "flask",
|
|
44
|
+
"type": "http_request",
|
|
45
|
+
"status_code": status_code,
|
|
46
|
+
"duration_ms": duration_ms,
|
|
47
|
+
},
|
|
48
|
+
),
|
|
49
|
+
)
|
|
50
|
+
return response
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import traceback as traceback_module
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from ..domain.interfaces import ILogger
|
|
8
|
+
from ..types import LogLevel
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class LoggingHandler(logging.Handler):
|
|
12
|
+
def __init__(self, logger: ILogger) -> None:
|
|
13
|
+
super().__init__()
|
|
14
|
+
self._logger = logger
|
|
15
|
+
|
|
16
|
+
def emit(self, record: logging.LogRecord) -> None:
|
|
17
|
+
try:
|
|
18
|
+
level = self._map_level(record.levelno)
|
|
19
|
+
message = record.getMessage()
|
|
20
|
+
context: dict[str, Any] = {
|
|
21
|
+
"source": "python-logging",
|
|
22
|
+
"logger_name": record.name,
|
|
23
|
+
"module": record.module,
|
|
24
|
+
"function": record.funcName,
|
|
25
|
+
"line": record.lineno,
|
|
26
|
+
}
|
|
27
|
+
if record.exc_info:
|
|
28
|
+
context["exception"] = "".join(traceback_module.format_exception(*record.exc_info))
|
|
29
|
+
self._logger.log(level, message, context)
|
|
30
|
+
except Exception:
|
|
31
|
+
return
|
|
32
|
+
|
|
33
|
+
@staticmethod
|
|
34
|
+
def _map_level(level_no: int) -> LogLevel:
|
|
35
|
+
if level_no >= logging.CRITICAL:
|
|
36
|
+
return "fatal"
|
|
37
|
+
if level_no >= logging.ERROR:
|
|
38
|
+
return "error"
|
|
39
|
+
if level_no >= logging.WARNING:
|
|
40
|
+
return "warn"
|
|
41
|
+
if level_no >= logging.INFO:
|
|
42
|
+
return "info"
|
|
43
|
+
return "debug"
|
|
@@ -0,0 +1,36 @@
|
|
|
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
|
+
)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from ..domain.interfaces import ILogger
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class StructlogProcessor:
|
|
9
|
+
def __init__(self, logger: ILogger) -> None:
|
|
10
|
+
self.logger = logger
|
|
11
|
+
|
|
12
|
+
def __call__(self, _logger: Any, method_name: str, event_dict: dict[str, Any]) -> dict[str, Any]:
|
|
13
|
+
message = str(event_dict.get("event", method_name))
|
|
14
|
+
level = method_name.lower()
|
|
15
|
+
if level not in {"debug", "info", "warn", "error", "fatal"}:
|
|
16
|
+
level = "info"
|
|
17
|
+
|
|
18
|
+
context = dict(event_dict)
|
|
19
|
+
context.pop("event", None)
|
|
20
|
+
self.logger.log(level, message, {"source": "structlog", **context}) # type: ignore[arg-type]
|
|
21
|
+
return event_dict
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import atexit
|
|
4
|
+
import os
|
|
5
|
+
import signal
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
_LOGS_INTERCEPTOR_PRELOADED = False
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _debug(*args: object) -> None:
|
|
12
|
+
if os.getenv("LOGS_DEBUG") == "true" and os.getenv("LOGS_SILENT_ERRORS") != "true":
|
|
13
|
+
print("[logs-interceptor:preload]", *args)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _error(*args: object) -> None:
|
|
17
|
+
if os.getenv("LOGS_SILENT_ERRORS") != "true":
|
|
18
|
+
print("[logs-interceptor:preload]", *args, file=sys.stderr)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _install() -> None:
|
|
22
|
+
global _LOGS_INTERCEPTOR_PRELOADED
|
|
23
|
+
if _LOGS_INTERCEPTOR_PRELOADED:
|
|
24
|
+
return
|
|
25
|
+
|
|
26
|
+
_LOGS_INTERCEPTOR_PRELOADED = True
|
|
27
|
+
|
|
28
|
+
if os.getenv("LOGS_ENABLED") == "false":
|
|
29
|
+
_debug("Disabled by LOGS_ENABLED=false")
|
|
30
|
+
return
|
|
31
|
+
|
|
32
|
+
try:
|
|
33
|
+
os.environ["LOGS_AUTO_INIT"] = "true"
|
|
34
|
+
from . import destroy, is_initialized
|
|
35
|
+
|
|
36
|
+
if is_initialized():
|
|
37
|
+
_debug("Initialized successfully via auto-init gate")
|
|
38
|
+
else:
|
|
39
|
+
_debug("Auto-init did not run (missing required LOGS_* variables)")
|
|
40
|
+
|
|
41
|
+
def _graceful_shutdown(signame: str) -> None:
|
|
42
|
+
_debug(f"Graceful shutdown triggered by {signame}")
|
|
43
|
+
try:
|
|
44
|
+
destroy()
|
|
45
|
+
except Exception as exc:
|
|
46
|
+
_error("Graceful shutdown failed:", exc)
|
|
47
|
+
|
|
48
|
+
def _sigterm_handler(_sig: int, _frame: object) -> None:
|
|
49
|
+
_graceful_shutdown("SIGTERM")
|
|
50
|
+
|
|
51
|
+
def _sigint_handler(_sig: int, _frame: object) -> None:
|
|
52
|
+
_graceful_shutdown("SIGINT")
|
|
53
|
+
|
|
54
|
+
signal.signal(signal.SIGTERM, _sigterm_handler)
|
|
55
|
+
signal.signal(signal.SIGINT, _sigint_handler)
|
|
56
|
+
atexit.register(lambda: _graceful_shutdown("atexit"))
|
|
57
|
+
except Exception as exc:
|
|
58
|
+
_error("Preload failed:", exc)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
_install()
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from ..application.log_service import LogService
|
|
8
|
+
from ..config import ResolvedLogsInterceptorConfig
|
|
9
|
+
from ..domain.interfaces import ILogger
|
|
10
|
+
from ..infrastructure.buffer import MemoryBuffer
|
|
11
|
+
from ..infrastructure.circuit_breaker import CircuitBreaker
|
|
12
|
+
from ..infrastructure.context import ContextVarProvider
|
|
13
|
+
from ..infrastructure.dlq import FileDeadLetterQueue, MemoryDeadLetterQueue
|
|
14
|
+
from ..infrastructure.filter import LogFilter
|
|
15
|
+
from ..infrastructure.interceptors import RuntimeInterceptor
|
|
16
|
+
from ..infrastructure.transport import TransportFactory
|
|
17
|
+
|
|
18
|
+
otel_trace: Any = None
|
|
19
|
+
try:
|
|
20
|
+
from opentelemetry import trace as otel_trace
|
|
21
|
+
except Exception: # pragma: no cover - optional dependency
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(slots=True)
|
|
26
|
+
class RuntimeBundle:
|
|
27
|
+
logger: ILogger
|
|
28
|
+
runtime_interceptor: RuntimeInterceptor | None = None
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class LogsInterceptorFactory:
|
|
32
|
+
@staticmethod
|
|
33
|
+
def create(config: ResolvedLogsInterceptorConfig) -> RuntimeBundle:
|
|
34
|
+
context_provider = ContextVarProvider()
|
|
35
|
+
|
|
36
|
+
dynamic_labels: dict[str, Callable[[], str | int]] = {
|
|
37
|
+
"request_id": lambda: str(context_provider.get("request_id", "")),
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if otel_trace is not None:
|
|
41
|
+
dynamic_labels["trace_id"] = lambda: LogsInterceptorFactory._trace_id()
|
|
42
|
+
dynamic_labels["span_id"] = lambda: LogsInterceptorFactory._span_id()
|
|
43
|
+
|
|
44
|
+
dynamic_labels.update(config.dynamic_labels)
|
|
45
|
+
|
|
46
|
+
circuit_breaker = CircuitBreaker(config.circuit_breaker)
|
|
47
|
+
|
|
48
|
+
dlq: FileDeadLetterQueue | MemoryDeadLetterQueue | None = None
|
|
49
|
+
dlq_cfg = config.dead_letter_queue
|
|
50
|
+
if dlq_cfg and dlq_cfg.enabled is not False:
|
|
51
|
+
dlq_type = dlq_cfg.type or "memory"
|
|
52
|
+
if dlq_type == "file":
|
|
53
|
+
dlq = FileDeadLetterQueue(
|
|
54
|
+
base_path=dlq_cfg.base_path,
|
|
55
|
+
max_size=dlq_cfg.max_size or 1000,
|
|
56
|
+
max_retries=dlq_cfg.max_retries or 3,
|
|
57
|
+
max_file_size_mb=dlq_cfg.max_file_size_mb or 10,
|
|
58
|
+
)
|
|
59
|
+
else:
|
|
60
|
+
dlq = MemoryDeadLetterQueue(dlq_cfg.max_size or 1000)
|
|
61
|
+
|
|
62
|
+
transport = TransportFactory.create(config, circuit_breaker, dlq)
|
|
63
|
+
buffer = MemoryBuffer(config.buffer)
|
|
64
|
+
filter_service = LogFilter(config.filter)
|
|
65
|
+
|
|
66
|
+
logger = LogService(
|
|
67
|
+
filter_service,
|
|
68
|
+
buffer,
|
|
69
|
+
transport,
|
|
70
|
+
context_provider,
|
|
71
|
+
{
|
|
72
|
+
"app_name": config.app_name,
|
|
73
|
+
"version": config.version,
|
|
74
|
+
"environment": config.environment,
|
|
75
|
+
"labels": config.labels,
|
|
76
|
+
"dynamic_labels": dynamic_labels,
|
|
77
|
+
"enable_metrics": config.enable_metrics,
|
|
78
|
+
"max_concurrent_flushes": config.performance.max_concurrent_flushes,
|
|
79
|
+
},
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
runtime_interceptor = None
|
|
83
|
+
if config.intercept_console:
|
|
84
|
+
runtime_interceptor = RuntimeInterceptor(logger, config.preserve_original_console)
|
|
85
|
+
runtime_interceptor.enable()
|
|
86
|
+
|
|
87
|
+
original_destroy = logger.destroy
|
|
88
|
+
|
|
89
|
+
def wrapped_destroy() -> None:
|
|
90
|
+
if runtime_interceptor is not None:
|
|
91
|
+
runtime_interceptor.restore()
|
|
92
|
+
original_destroy()
|
|
93
|
+
|
|
94
|
+
logger.destroy = wrapped_destroy # type: ignore[method-assign]
|
|
95
|
+
|
|
96
|
+
return RuntimeBundle(logger=logger, runtime_interceptor=runtime_interceptor)
|
|
97
|
+
|
|
98
|
+
@staticmethod
|
|
99
|
+
def _trace_id() -> str:
|
|
100
|
+
if otel_trace is None:
|
|
101
|
+
return ""
|
|
102
|
+
try:
|
|
103
|
+
span = otel_trace.get_current_span()
|
|
104
|
+
if span is None:
|
|
105
|
+
return ""
|
|
106
|
+
ctx = span.get_span_context()
|
|
107
|
+
trace_id = getattr(ctx, "trace_id", None)
|
|
108
|
+
if isinstance(trace_id, int):
|
|
109
|
+
return f"{trace_id:032x}"
|
|
110
|
+
return str(trace_id or "")
|
|
111
|
+
except Exception:
|
|
112
|
+
return ""
|
|
113
|
+
|
|
114
|
+
@staticmethod
|
|
115
|
+
def _span_id() -> str:
|
|
116
|
+
if otel_trace is None:
|
|
117
|
+
return ""
|
|
118
|
+
try:
|
|
119
|
+
span = otel_trace.get_current_span()
|
|
120
|
+
if span is None:
|
|
121
|
+
return ""
|
|
122
|
+
ctx = span.get_span_context()
|
|
123
|
+
span_id = getattr(ctx, "span_id", None)
|
|
124
|
+
if isinstance(span_id, int):
|
|
125
|
+
return f"{span_id:016x}"
|
|
126
|
+
return str(span_id or "")
|
|
127
|
+
except Exception:
|
|
128
|
+
return ""
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Any, Literal, TypedDict
|
|
5
|
+
|
|
6
|
+
LogLevel = Literal["debug", "info", "warn", "error", "fatal"]
|
|
7
|
+
CircuitBreakerState = Literal["closed", "open", "half-open"]
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class LogEntry(TypedDict, total=False):
|
|
11
|
+
id: str
|
|
12
|
+
timestamp: str
|
|
13
|
+
level: LogLevel
|
|
14
|
+
message: str
|
|
15
|
+
context: dict[str, Any]
|
|
16
|
+
trace_id: str
|
|
17
|
+
span_id: str
|
|
18
|
+
request_id: str
|
|
19
|
+
labels: dict[str, str]
|
|
20
|
+
metadata: dict[str, Any]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class TransportHealth(TypedDict, total=False):
|
|
24
|
+
healthy: bool
|
|
25
|
+
last_successful_send: float
|
|
26
|
+
consecutive_failures: int
|
|
27
|
+
error_message: str
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class TransportMetrics(TypedDict, total=False):
|
|
31
|
+
total_sends: int
|
|
32
|
+
successful_sends: int
|
|
33
|
+
failed_sends: int
|
|
34
|
+
avg_latency: float
|
|
35
|
+
avg_compression_time: float
|
|
36
|
+
avg_compression_ratio: float
|
|
37
|
+
total_bytes_sent: int
|
|
38
|
+
total_bytes_compressed: int
|
|
39
|
+
retry_attempts: int
|
|
40
|
+
retried_requests: int
|
|
41
|
+
dlq_dropped_entries: int
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class LatencyMetrics(TypedDict):
|
|
45
|
+
p50: float
|
|
46
|
+
p95: float
|
|
47
|
+
p99: float
|
|
48
|
+
avg: float
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class CompressionMetrics(TypedDict):
|
|
52
|
+
avg_ratio: float
|
|
53
|
+
avg_time: float
|
|
54
|
+
total_saved_bytes: int
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class LoggerMetrics(TypedDict, total=False):
|
|
58
|
+
logs_processed: int
|
|
59
|
+
logs_dropped: int
|
|
60
|
+
logs_sanitized: int
|
|
61
|
+
flush_count: int
|
|
62
|
+
error_count: int
|
|
63
|
+
buffer_size: int
|
|
64
|
+
avg_flush_time: float
|
|
65
|
+
last_flush_time: float
|
|
66
|
+
memory_usage: float
|
|
67
|
+
cpu_usage: float
|
|
68
|
+
circuit_breaker_trips: int
|
|
69
|
+
dropped_by_backpressure: int
|
|
70
|
+
dropped_by_dlq: int
|
|
71
|
+
latency: LatencyMetrics
|
|
72
|
+
compression: CompressionMetrics
|
|
73
|
+
throughput: float
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class HealthStatus(TypedDict, total=False):
|
|
77
|
+
healthy: bool
|
|
78
|
+
last_successful_flush: float
|
|
79
|
+
consecutive_errors: int
|
|
80
|
+
buffer_utilization: float
|
|
81
|
+
uptime: float
|
|
82
|
+
memory_usage_mb: float
|
|
83
|
+
circuit_breaker_state: CircuitBreakerState
|
|
84
|
+
last_error: str
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@dataclass(frozen=True)
|
|
88
|
+
class RuntimeState:
|
|
89
|
+
initialized: bool
|