elven-logs-interceptor-python 0.1.13__tar.gz → 0.1.15__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.13 → elven_logs_interceptor_python-0.1.15}/PKG-INFO +14 -8
- {elven_logs_interceptor_python-0.1.13 → elven_logs_interceptor_python-0.1.15}/README.md +13 -7
- {elven_logs_interceptor_python-0.1.13 → elven_logs_interceptor_python-0.1.15}/pyproject.toml +1 -1
- {elven_logs_interceptor_python-0.1.13 → elven_logs_interceptor_python-0.1.15}/src/logs_interceptor/infrastructure/filter/log_filter.py +3 -3
- {elven_logs_interceptor_python-0.1.13 → elven_logs_interceptor_python-0.1.15}/src/logs_interceptor/utils.py +124 -9
- {elven_logs_interceptor_python-0.1.13 → elven_logs_interceptor_python-0.1.15}/.gitignore +0 -0
- {elven_logs_interceptor_python-0.1.13 → elven_logs_interceptor_python-0.1.15}/ARCHITECTURE.md +0 -0
- {elven_logs_interceptor_python-0.1.13 → elven_logs_interceptor_python-0.1.15}/src/logs_interceptor/__init__.py +0 -0
- {elven_logs_interceptor_python-0.1.13 → elven_logs_interceptor_python-0.1.15}/src/logs_interceptor/application/__init__.py +0 -0
- {elven_logs_interceptor_python-0.1.13 → elven_logs_interceptor_python-0.1.15}/src/logs_interceptor/application/config_service.py +0 -0
- {elven_logs_interceptor_python-0.1.13 → elven_logs_interceptor_python-0.1.15}/src/logs_interceptor/application/log_service.py +0 -0
- {elven_logs_interceptor_python-0.1.13 → elven_logs_interceptor_python-0.1.15}/src/logs_interceptor/config.py +0 -0
- {elven_logs_interceptor_python-0.1.13 → elven_logs_interceptor_python-0.1.15}/src/logs_interceptor/domain/__init__.py +0 -0
- {elven_logs_interceptor_python-0.1.13 → elven_logs_interceptor_python-0.1.15}/src/logs_interceptor/domain/entities.py +0 -0
- {elven_logs_interceptor_python-0.1.13 → elven_logs_interceptor_python-0.1.15}/src/logs_interceptor/domain/interfaces.py +0 -0
- {elven_logs_interceptor_python-0.1.13 → elven_logs_interceptor_python-0.1.15}/src/logs_interceptor/domain/value_objects.py +0 -0
- {elven_logs_interceptor_python-0.1.13 → elven_logs_interceptor_python-0.1.15}/src/logs_interceptor/infrastructure/__init__.py +0 -0
- {elven_logs_interceptor_python-0.1.13 → elven_logs_interceptor_python-0.1.15}/src/logs_interceptor/infrastructure/buffer/__init__.py +0 -0
- {elven_logs_interceptor_python-0.1.13 → elven_logs_interceptor_python-0.1.15}/src/logs_interceptor/infrastructure/buffer/memory_buffer.py +0 -0
- {elven_logs_interceptor_python-0.1.13 → elven_logs_interceptor_python-0.1.15}/src/logs_interceptor/infrastructure/circuit_breaker/__init__.py +0 -0
- {elven_logs_interceptor_python-0.1.13 → elven_logs_interceptor_python-0.1.15}/src/logs_interceptor/infrastructure/circuit_breaker/circuit_breaker.py +0 -0
- {elven_logs_interceptor_python-0.1.13 → elven_logs_interceptor_python-0.1.15}/src/logs_interceptor/infrastructure/compression/__init__.py +0 -0
- {elven_logs_interceptor_python-0.1.13 → elven_logs_interceptor_python-0.1.15}/src/logs_interceptor/infrastructure/compression/base.py +0 -0
- {elven_logs_interceptor_python-0.1.13 → elven_logs_interceptor_python-0.1.15}/src/logs_interceptor/infrastructure/compression/brotli_compressor.py +0 -0
- {elven_logs_interceptor_python-0.1.13 → elven_logs_interceptor_python-0.1.15}/src/logs_interceptor/infrastructure/compression/factory.py +0 -0
- {elven_logs_interceptor_python-0.1.13 → elven_logs_interceptor_python-0.1.15}/src/logs_interceptor/infrastructure/compression/gzip_compressor.py +0 -0
- {elven_logs_interceptor_python-0.1.13 → elven_logs_interceptor_python-0.1.15}/src/logs_interceptor/infrastructure/compression/noop_compressor.py +0 -0
- {elven_logs_interceptor_python-0.1.13 → elven_logs_interceptor_python-0.1.15}/src/logs_interceptor/infrastructure/context/__init__.py +0 -0
- {elven_logs_interceptor_python-0.1.13 → elven_logs_interceptor_python-0.1.15}/src/logs_interceptor/infrastructure/context/context_provider.py +0 -0
- {elven_logs_interceptor_python-0.1.13 → elven_logs_interceptor_python-0.1.15}/src/logs_interceptor/infrastructure/dlq/__init__.py +0 -0
- {elven_logs_interceptor_python-0.1.13 → elven_logs_interceptor_python-0.1.15}/src/logs_interceptor/infrastructure/dlq/file_dlq.py +0 -0
- {elven_logs_interceptor_python-0.1.13 → elven_logs_interceptor_python-0.1.15}/src/logs_interceptor/infrastructure/dlq/memory_dlq.py +0 -0
- {elven_logs_interceptor_python-0.1.13 → elven_logs_interceptor_python-0.1.15}/src/logs_interceptor/infrastructure/filter/__init__.py +0 -0
- {elven_logs_interceptor_python-0.1.13 → elven_logs_interceptor_python-0.1.15}/src/logs_interceptor/infrastructure/interceptors/__init__.py +0 -0
- {elven_logs_interceptor_python-0.1.13 → elven_logs_interceptor_python-0.1.15}/src/logs_interceptor/infrastructure/interceptors/runtime_interceptor.py +0 -0
- {elven_logs_interceptor_python-0.1.13 → elven_logs_interceptor_python-0.1.15}/src/logs_interceptor/infrastructure/internal_capture_guard.py +0 -0
- {elven_logs_interceptor_python-0.1.13 → elven_logs_interceptor_python-0.1.15}/src/logs_interceptor/infrastructure/log_noise_filter.py +0 -0
- {elven_logs_interceptor_python-0.1.13 → elven_logs_interceptor_python-0.1.15}/src/logs_interceptor/infrastructure/log_record_extra.py +0 -0
- {elven_logs_interceptor_python-0.1.13 → elven_logs_interceptor_python-0.1.15}/src/logs_interceptor/infrastructure/memory/__init__.py +0 -0
- {elven_logs_interceptor_python-0.1.13 → elven_logs_interceptor_python-0.1.15}/src/logs_interceptor/infrastructure/memory/memory_tracker.py +0 -0
- {elven_logs_interceptor_python-0.1.13 → elven_logs_interceptor_python-0.1.15}/src/logs_interceptor/infrastructure/metrics/__init__.py +0 -0
- {elven_logs_interceptor_python-0.1.13 → elven_logs_interceptor_python-0.1.15}/src/logs_interceptor/infrastructure/metrics/metrics_collector.py +0 -0
- {elven_logs_interceptor_python-0.1.13 → elven_logs_interceptor_python-0.1.15}/src/logs_interceptor/infrastructure/transport/__init__.py +0 -0
- {elven_logs_interceptor_python-0.1.13 → elven_logs_interceptor_python-0.1.15}/src/logs_interceptor/infrastructure/transport/loki_json_transport.py +0 -0
- {elven_logs_interceptor_python-0.1.13 → elven_logs_interceptor_python-0.1.15}/src/logs_interceptor/infrastructure/transport/loki_protobuf_transport.py +0 -0
- {elven_logs_interceptor_python-0.1.13 → elven_logs_interceptor_python-0.1.15}/src/logs_interceptor/infrastructure/transport/otlp_json_transport.py +0 -0
- {elven_logs_interceptor_python-0.1.13 → elven_logs_interceptor_python-0.1.15}/src/logs_interceptor/infrastructure/transport/resilient_transport.py +0 -0
- {elven_logs_interceptor_python-0.1.13 → elven_logs_interceptor_python-0.1.15}/src/logs_interceptor/infrastructure/transport/transport_factory.py +0 -0
- {elven_logs_interceptor_python-0.1.13 → elven_logs_interceptor_python-0.1.15}/src/logs_interceptor/infrastructure/workers/__init__.py +0 -0
- {elven_logs_interceptor_python-0.1.13 → elven_logs_interceptor_python-0.1.15}/src/logs_interceptor/infrastructure/workers/worker_pool.py +0 -0
- {elven_logs_interceptor_python-0.1.13 → elven_logs_interceptor_python-0.1.15}/src/logs_interceptor/integrations/__init__.py +0 -0
- {elven_logs_interceptor_python-0.1.13 → elven_logs_interceptor_python-0.1.15}/src/logs_interceptor/integrations/celery.py +0 -0
- {elven_logs_interceptor_python-0.1.13 → elven_logs_interceptor_python-0.1.15}/src/logs_interceptor/integrations/django.py +0 -0
- {elven_logs_interceptor_python-0.1.13 → elven_logs_interceptor_python-0.1.15}/src/logs_interceptor/integrations/fastapi.py +0 -0
- {elven_logs_interceptor_python-0.1.13 → elven_logs_interceptor_python-0.1.15}/src/logs_interceptor/integrations/flask.py +0 -0
- {elven_logs_interceptor_python-0.1.13 → elven_logs_interceptor_python-0.1.15}/src/logs_interceptor/integrations/logging_handler.py +0 -0
- {elven_logs_interceptor_python-0.1.13 → elven_logs_interceptor_python-0.1.15}/src/logs_interceptor/integrations/loguru.py +0 -0
- {elven_logs_interceptor_python-0.1.13 → elven_logs_interceptor_python-0.1.15}/src/logs_interceptor/integrations/structlog.py +0 -0
- {elven_logs_interceptor_python-0.1.13 → elven_logs_interceptor_python-0.1.15}/src/logs_interceptor/preload.py +0 -0
- {elven_logs_interceptor_python-0.1.13 → elven_logs_interceptor_python-0.1.15}/src/logs_interceptor/presentation/__init__.py +0 -0
- {elven_logs_interceptor_python-0.1.13 → elven_logs_interceptor_python-0.1.15}/src/logs_interceptor/presentation/factory.py +0 -0
- {elven_logs_interceptor_python-0.1.13 → elven_logs_interceptor_python-0.1.15}/src/logs_interceptor/types.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: elven-logs-interceptor-python
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.15
|
|
4
4
|
Summary: Production-grade logs interceptor for Python with Loki transport, resilience, batching, and framework integrations.
|
|
5
5
|
Author: Elven Observability
|
|
6
6
|
License: MIT
|
|
@@ -124,8 +124,11 @@ logging.getLogger(__name__).info(
|
|
|
124
124
|
Supported aliases: `warning(...)`, `exception(...)` and `critical(...)`.
|
|
125
125
|
|
|
126
126
|
Sensitive fields such as `cpf`, `password`, `token` and `authorization` are
|
|
127
|
-
redacted by default.
|
|
128
|
-
|
|
127
|
+
redacted by default. In messages, the library redacts only the sensitive
|
|
128
|
+
fragment/value and preserves the rest of the log. In structured context,
|
|
129
|
+
sensitive keys redact the full value. Set `LOGS_FILTER_SANITIZE=false`,
|
|
130
|
+
`LOGS_SANITIZE=false`, or `UO_LOGS_SANITIZE=false` only when raw sensitive data
|
|
131
|
+
is explicitly required and approved.
|
|
129
132
|
|
|
130
133
|
## Environment Variables
|
|
131
134
|
|
|
@@ -139,10 +142,13 @@ Required for direct Loki mode:
|
|
|
139
142
|
- `LOGS_APP_NAME`
|
|
140
143
|
|
|
141
144
|
For collector-first mode, set `LOGS_EXPORTER=otlp` (or `collector`) and use
|
|
142
|
-
`OTEL_EXPORTER_OTLP_LOGS_ENDPOINT`.
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
145
|
+
`OTEL_EXPORTER_OTLP_LOGS_ENDPOINT`. If the explicit logs endpoint is missing,
|
|
146
|
+
the library derives `/v1/logs` from `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT`,
|
|
147
|
+
`OTEL_EXPORTER_OTLP_METRICS_ENDPOINT`, or `OTEL_EXPORTER_OTLP_ENDPOINT`.
|
|
148
|
+
In this mode `LOGS_URL`, `LOKI_URL`, `LOGS_TENANT` and `LOGS_TOKEN` are not
|
|
149
|
+
required by the library. `LOGS_URL` is intentionally ignored in collector mode
|
|
150
|
+
to avoid accidentally posting OTLP logs to a direct Loki `/loki/api/v1/push`
|
|
151
|
+
endpoint. Put tenant or auth headers in
|
|
146
152
|
`OTEL_EXPORTER_OTLP_HEADERS`/`LOGS_OTLP_HEADERS` if your collector gateway
|
|
147
153
|
requires them.
|
|
148
154
|
|
|
@@ -182,7 +188,7 @@ Filter:
|
|
|
182
188
|
|
|
183
189
|
- `LOGS_FILTER_LEVELS`
|
|
184
190
|
- `LOGS_FILTER_SAMPLING_RATE`
|
|
185
|
-
- `LOGS_FILTER_SANITIZE`
|
|
191
|
+
- `LOGS_FILTER_SANITIZE` (aliases: `LOGS_SANITIZE`, `UO_LOGS_SANITIZE`)
|
|
186
192
|
- `LOGS_FILTER_MAX_MESSAGE_LENGTH`
|
|
187
193
|
- `LOGS_FILTER_EXCLUDE_LOGGER_PREFIXES` (comma-separated)
|
|
188
194
|
|
|
@@ -63,8 +63,11 @@ logging.getLogger(__name__).info(
|
|
|
63
63
|
Supported aliases: `warning(...)`, `exception(...)` and `critical(...)`.
|
|
64
64
|
|
|
65
65
|
Sensitive fields such as `cpf`, `password`, `token` and `authorization` are
|
|
66
|
-
redacted by default.
|
|
67
|
-
|
|
66
|
+
redacted by default. In messages, the library redacts only the sensitive
|
|
67
|
+
fragment/value and preserves the rest of the log. In structured context,
|
|
68
|
+
sensitive keys redact the full value. Set `LOGS_FILTER_SANITIZE=false`,
|
|
69
|
+
`LOGS_SANITIZE=false`, or `UO_LOGS_SANITIZE=false` only when raw sensitive data
|
|
70
|
+
is explicitly required and approved.
|
|
68
71
|
|
|
69
72
|
## Environment Variables
|
|
70
73
|
|
|
@@ -78,10 +81,13 @@ Required for direct Loki mode:
|
|
|
78
81
|
- `LOGS_APP_NAME`
|
|
79
82
|
|
|
80
83
|
For collector-first mode, set `LOGS_EXPORTER=otlp` (or `collector`) and use
|
|
81
|
-
`OTEL_EXPORTER_OTLP_LOGS_ENDPOINT`.
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
84
|
+
`OTEL_EXPORTER_OTLP_LOGS_ENDPOINT`. If the explicit logs endpoint is missing,
|
|
85
|
+
the library derives `/v1/logs` from `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT`,
|
|
86
|
+
`OTEL_EXPORTER_OTLP_METRICS_ENDPOINT`, or `OTEL_EXPORTER_OTLP_ENDPOINT`.
|
|
87
|
+
In this mode `LOGS_URL`, `LOKI_URL`, `LOGS_TENANT` and `LOGS_TOKEN` are not
|
|
88
|
+
required by the library. `LOGS_URL` is intentionally ignored in collector mode
|
|
89
|
+
to avoid accidentally posting OTLP logs to a direct Loki `/loki/api/v1/push`
|
|
90
|
+
endpoint. Put tenant or auth headers in
|
|
85
91
|
`OTEL_EXPORTER_OTLP_HEADERS`/`LOGS_OTLP_HEADERS` if your collector gateway
|
|
86
92
|
requires them.
|
|
87
93
|
|
|
@@ -121,7 +127,7 @@ Filter:
|
|
|
121
127
|
|
|
122
128
|
- `LOGS_FILTER_LEVELS`
|
|
123
129
|
- `LOGS_FILTER_SAMPLING_RATE`
|
|
124
|
-
- `LOGS_FILTER_SANITIZE`
|
|
130
|
+
- `LOGS_FILTER_SANITIZE` (aliases: `LOGS_SANITIZE`, `UO_LOGS_SANITIZE`)
|
|
125
131
|
- `LOGS_FILTER_MAX_MESSAGE_LENGTH`
|
|
126
132
|
- `LOGS_FILTER_EXCLUDE_LOGGER_PREFIXES` (comma-separated)
|
|
127
133
|
|
{elven_logs_interceptor_python-0.1.13 → elven_logs_interceptor_python-0.1.15}/pyproject.toml
RENAMED
|
@@ -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.15"
|
|
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"
|
|
@@ -5,7 +5,7 @@ import re
|
|
|
5
5
|
from ...config import ResolvedFilterConfig
|
|
6
6
|
from ...domain.entities import LogEntryEntity
|
|
7
7
|
from ...types import LogLevel
|
|
8
|
-
from ...utils import
|
|
8
|
+
from ...utils import redact_sensitive_text, sanitize_data, should_sample
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
class LogFilter:
|
|
@@ -35,8 +35,8 @@ class LogFilter:
|
|
|
35
35
|
if self._config.sanitize and context is not None:
|
|
36
36
|
context = sanitize_data(context, self._config.sensitive_patterns)
|
|
37
37
|
|
|
38
|
-
if self._config.sanitize
|
|
39
|
-
message =
|
|
38
|
+
if self._config.sanitize:
|
|
39
|
+
message = redact_sensitive_text(message, self._config.sensitive_patterns)
|
|
40
40
|
|
|
41
41
|
return LogEntryEntity(
|
|
42
42
|
id=entry.id,
|
|
@@ -82,6 +82,17 @@ def _normalize_logs_exporter(value: str | None) -> str:
|
|
|
82
82
|
return "loki"
|
|
83
83
|
|
|
84
84
|
|
|
85
|
+
def _resolve_logs_exporter(env: Mapping[str, str]) -> str:
|
|
86
|
+
explicit = env.get("LOGS_EXPORTER") or env.get("LOGS_TRANSPORT") or env.get("OTEL_LOGS_EXPORTER")
|
|
87
|
+
if explicit:
|
|
88
|
+
return _normalize_logs_exporter(explicit)
|
|
89
|
+
if env.get("LOGS_URL"):
|
|
90
|
+
return "loki"
|
|
91
|
+
if _resolve_otlp_logs_url(env):
|
|
92
|
+
return "otlp"
|
|
93
|
+
return "loki"
|
|
94
|
+
|
|
95
|
+
|
|
85
96
|
def _parse_kv_csv(raw: str | None) -> dict[str, str]:
|
|
86
97
|
headers: dict[str, str] = {}
|
|
87
98
|
if not raw:
|
|
@@ -99,7 +110,29 @@ def _parse_kv_csv(raw: str | None) -> dict[str, str]:
|
|
|
99
110
|
def _resolve_logs_url(env: Mapping[str, str], exporter: str) -> str:
|
|
100
111
|
if exporter != "otlp":
|
|
101
112
|
return env.get("LOGS_URL", "")
|
|
102
|
-
return env
|
|
113
|
+
return _resolve_otlp_logs_url(env)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _resolve_otlp_logs_url(env: Mapping[str, str]) -> str:
|
|
117
|
+
return (
|
|
118
|
+
env.get("OTEL_EXPORTER_OTLP_LOGS_ENDPOINT")
|
|
119
|
+
or _derive_otlp_logs_endpoint(
|
|
120
|
+
env.get("OTEL_EXPORTER_OTLP_TRACES_ENDPOINT")
|
|
121
|
+
or env.get("OTEL_EXPORTER_OTLP_METRICS_ENDPOINT")
|
|
122
|
+
or env.get("OTEL_EXPORTER_OTLP_ENDPOINT")
|
|
123
|
+
)
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _derive_otlp_logs_endpoint(endpoint: str | None) -> str:
|
|
128
|
+
if not endpoint:
|
|
129
|
+
return ""
|
|
130
|
+
normalized = endpoint.rstrip("/")
|
|
131
|
+
if normalized.endswith("/v1/logs"):
|
|
132
|
+
return normalized
|
|
133
|
+
if normalized.endswith("/v1/traces") or normalized.endswith("/v1/metrics"):
|
|
134
|
+
return normalized.rsplit("/", 1)[0] + "/logs"
|
|
135
|
+
return normalized + "/v1/logs"
|
|
103
136
|
|
|
104
137
|
|
|
105
138
|
def is_debug_enabled() -> bool:
|
|
@@ -359,6 +392,87 @@ def detect_sensitive_data(text: str, patterns: Iterable[str]) -> bool:
|
|
|
359
392
|
return any(pattern.search(text) for pattern in common_patterns)
|
|
360
393
|
|
|
361
394
|
|
|
395
|
+
def redact_sensitive_text(text: str, sensitive_patterns: Iterable[str]) -> str:
|
|
396
|
+
"""Redact sensitive values while preserving the surrounding log message."""
|
|
397
|
+
patterns = list(sensitive_patterns)
|
|
398
|
+
redacted = text
|
|
399
|
+
|
|
400
|
+
for pattern in patterns:
|
|
401
|
+
redacted = _redact_key_value_pattern(redacted, pattern)
|
|
402
|
+
|
|
403
|
+
common_replacements: tuple[tuple[re.Pattern[str], str], ...] = (
|
|
404
|
+
(
|
|
405
|
+
re.compile(r"(Bearer)\s+[A-Za-z0-9\-._~+/=]+", re.IGNORECASE),
|
|
406
|
+
r"\1 [REDACTED]",
|
|
407
|
+
),
|
|
408
|
+
(
|
|
409
|
+
re.compile(r"(Basic)\s+[A-Za-z0-9+/=]+", re.IGNORECASE),
|
|
410
|
+
r"\1 [REDACTED]",
|
|
411
|
+
),
|
|
412
|
+
(
|
|
413
|
+
re.compile(r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b"),
|
|
414
|
+
"[REDACTED]",
|
|
415
|
+
),
|
|
416
|
+
(
|
|
417
|
+
re.compile(r"\b(?:\d{4}[-\s]?){3}\d{4}\b"),
|
|
418
|
+
"[REDACTED]",
|
|
419
|
+
),
|
|
420
|
+
(
|
|
421
|
+
re.compile(r"\b\d{3}-\d{2}-\d{4}\b"),
|
|
422
|
+
"[REDACTED]",
|
|
423
|
+
),
|
|
424
|
+
(
|
|
425
|
+
re.compile(r"\b\d{3}\.\d{3}\.\d{3}-\d{2}\b"),
|
|
426
|
+
"[REDACTED]",
|
|
427
|
+
),
|
|
428
|
+
)
|
|
429
|
+
for common_pattern, replacement in common_replacements:
|
|
430
|
+
redacted = common_pattern.sub(replacement, redacted)
|
|
431
|
+
|
|
432
|
+
for pattern in patterns:
|
|
433
|
+
if _looks_like_value_pattern(pattern):
|
|
434
|
+
redacted = re.sub(pattern, "[REDACTED]", redacted, flags=re.IGNORECASE)
|
|
435
|
+
|
|
436
|
+
return redacted
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
def _redact_key_value_pattern(text: str, key_pattern: str) -> str:
|
|
440
|
+
quoted = re.compile(
|
|
441
|
+
rf"(?P<prefix>(?:[\"']?{key_pattern}[\"']?)\s*[:=]\s*)(?P<quote>[\"'])(?P<value>.*?)(?P=quote)",
|
|
442
|
+
re.IGNORECASE,
|
|
443
|
+
)
|
|
444
|
+
redacted = quoted.sub(r"\g<prefix>\g<quote>[REDACTED]\g<quote>", text)
|
|
445
|
+
|
|
446
|
+
unquoted = re.compile(
|
|
447
|
+
rf"(?P<prefix>(?:[\"']?{key_pattern}[\"']?)\s*[:=]\s*)(?P<value>[^\s,;&}}\]\"']+)",
|
|
448
|
+
re.IGNORECASE,
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
def _replace_unquoted(match: re.Match[str]) -> str:
|
|
452
|
+
value = match.group("value")
|
|
453
|
+
if value.lower() in {"bearer", "basic"} or value == "[REDACTED]":
|
|
454
|
+
return match.group(0)
|
|
455
|
+
return f"{match.group('prefix')}[REDACTED]"
|
|
456
|
+
|
|
457
|
+
redacted = unquoted.sub(_replace_unquoted, redacted)
|
|
458
|
+
|
|
459
|
+
query = re.compile(
|
|
460
|
+
rf"(?P<prefix>[?&](?:{key_pattern})=)(?P<value>[^&#\s]+)",
|
|
461
|
+
re.IGNORECASE,
|
|
462
|
+
)
|
|
463
|
+
return query.sub(r"\g<prefix>[REDACTED]", redacted)
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
def _looks_like_value_pattern(pattern: str) -> bool:
|
|
467
|
+
# Plain words such as "token" and "password" are key indicators by default;
|
|
468
|
+
# redacting those words directly would destroy harmless messages like
|
|
469
|
+
# "Gerando token de acesso". Regex-looking patterns are treated as values.
|
|
470
|
+
keyish_terms = r"password|token|secret|api|key|authorization|credit|card|ssn|cpf"
|
|
471
|
+
if re.search(keyish_terms, pattern, re.IGNORECASE):
|
|
472
|
+
return False
|
|
473
|
+
return bool(re.search(r"\\d|\[0-9|\{\d|[\^$]", pattern))
|
|
474
|
+
|
|
475
|
+
|
|
362
476
|
def sanitize_data(
|
|
363
477
|
data: dict[str, Any],
|
|
364
478
|
sensitive_patterns: Iterable[str],
|
|
@@ -382,16 +496,14 @@ def sanitize_data(
|
|
|
382
496
|
continue
|
|
383
497
|
|
|
384
498
|
if isinstance(value, str):
|
|
385
|
-
sanitized[key] =
|
|
499
|
+
sanitized[key] = redact_sensitive_text(value, sensitive_patterns)
|
|
386
500
|
continue
|
|
387
501
|
|
|
388
502
|
if isinstance(value, list):
|
|
389
503
|
transformed: list[Any] = []
|
|
390
504
|
for item in value:
|
|
391
505
|
if isinstance(item, str):
|
|
392
|
-
transformed.append(
|
|
393
|
-
"[REDACTED]" if detect_sensitive_data(item, sensitive_patterns) else item
|
|
394
|
-
)
|
|
506
|
+
transformed.append(redact_sensitive_text(item, sensitive_patterns))
|
|
395
507
|
elif isinstance(item, dict):
|
|
396
508
|
transformed.append(sanitize_data(item, sensitive_patterns, seen))
|
|
397
509
|
else:
|
|
@@ -534,9 +646,7 @@ def load_config_from_env() -> LogsInterceptorConfig:
|
|
|
534
646
|
if compression_value not in {"none", "gzip", "brotli", "snappy"}:
|
|
535
647
|
compression_value = "gzip"
|
|
536
648
|
|
|
537
|
-
exporter =
|
|
538
|
-
env.get("LOGS_EXPORTER") or env.get("LOGS_TRANSPORT") or env.get("OTEL_LOGS_EXPORTER")
|
|
539
|
-
)
|
|
649
|
+
exporter = _resolve_logs_exporter(env)
|
|
540
650
|
headers = {
|
|
541
651
|
**_parse_kv_csv(env.get("LOGS_HEADERS")),
|
|
542
652
|
**_parse_kv_csv(env.get("LOGS_OTLP_HEADERS")),
|
|
@@ -588,7 +698,12 @@ def load_config_from_env() -> LogsInterceptorConfig:
|
|
|
588
698
|
filter=FilterConfig(
|
|
589
699
|
levels=_env_levels(env.get("LOGS_FILTER_LEVELS")),
|
|
590
700
|
sampling_rate=parse_float_range(env.get("LOGS_FILTER_SAMPLING_RATE"), 1.0, 0.0, 1.0),
|
|
591
|
-
sanitize=parse_bool(
|
|
701
|
+
sanitize=parse_bool(
|
|
702
|
+
env.get("LOGS_FILTER_SANITIZE")
|
|
703
|
+
or env.get("LOGS_SANITIZE")
|
|
704
|
+
or env.get("UO_LOGS_SANITIZE"),
|
|
705
|
+
True,
|
|
706
|
+
),
|
|
592
707
|
max_message_length=parse_int_range(
|
|
593
708
|
env.get("LOGS_FILTER_MAX_MESSAGE_LENGTH"), 8192, 64, 1_000_000
|
|
594
709
|
),
|
|
File without changes
|
{elven_logs_interceptor_python-0.1.13 → elven_logs_interceptor_python-0.1.15}/ARCHITECTURE.md
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
|