elven-logs-interceptor-python 0.1.16__tar.gz → 0.1.17__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.
Files changed (62) hide show
  1. {elven_logs_interceptor_python-0.1.16 → elven_logs_interceptor_python-0.1.17}/PKG-INFO +11 -7
  2. {elven_logs_interceptor_python-0.1.16 → elven_logs_interceptor_python-0.1.17}/README.md +10 -6
  3. {elven_logs_interceptor_python-0.1.16 → elven_logs_interceptor_python-0.1.17}/pyproject.toml +1 -1
  4. {elven_logs_interceptor_python-0.1.16 → elven_logs_interceptor_python-0.1.17}/src/logs_interceptor/application/config_service.py +12 -0
  5. {elven_logs_interceptor_python-0.1.16 → elven_logs_interceptor_python-0.1.17}/src/logs_interceptor/utils.py +104 -13
  6. {elven_logs_interceptor_python-0.1.16 → elven_logs_interceptor_python-0.1.17}/.gitignore +0 -0
  7. {elven_logs_interceptor_python-0.1.16 → elven_logs_interceptor_python-0.1.17}/ARCHITECTURE.md +0 -0
  8. {elven_logs_interceptor_python-0.1.16 → elven_logs_interceptor_python-0.1.17}/src/logs_interceptor/__init__.py +0 -0
  9. {elven_logs_interceptor_python-0.1.16 → elven_logs_interceptor_python-0.1.17}/src/logs_interceptor/application/__init__.py +0 -0
  10. {elven_logs_interceptor_python-0.1.16 → elven_logs_interceptor_python-0.1.17}/src/logs_interceptor/application/log_service.py +0 -0
  11. {elven_logs_interceptor_python-0.1.16 → elven_logs_interceptor_python-0.1.17}/src/logs_interceptor/config.py +0 -0
  12. {elven_logs_interceptor_python-0.1.16 → elven_logs_interceptor_python-0.1.17}/src/logs_interceptor/domain/__init__.py +0 -0
  13. {elven_logs_interceptor_python-0.1.16 → elven_logs_interceptor_python-0.1.17}/src/logs_interceptor/domain/entities.py +0 -0
  14. {elven_logs_interceptor_python-0.1.16 → elven_logs_interceptor_python-0.1.17}/src/logs_interceptor/domain/interfaces.py +0 -0
  15. {elven_logs_interceptor_python-0.1.16 → elven_logs_interceptor_python-0.1.17}/src/logs_interceptor/domain/value_objects.py +0 -0
  16. {elven_logs_interceptor_python-0.1.16 → elven_logs_interceptor_python-0.1.17}/src/logs_interceptor/infrastructure/__init__.py +0 -0
  17. {elven_logs_interceptor_python-0.1.16 → elven_logs_interceptor_python-0.1.17}/src/logs_interceptor/infrastructure/buffer/__init__.py +0 -0
  18. {elven_logs_interceptor_python-0.1.16 → elven_logs_interceptor_python-0.1.17}/src/logs_interceptor/infrastructure/buffer/memory_buffer.py +0 -0
  19. {elven_logs_interceptor_python-0.1.16 → elven_logs_interceptor_python-0.1.17}/src/logs_interceptor/infrastructure/circuit_breaker/__init__.py +0 -0
  20. {elven_logs_interceptor_python-0.1.16 → elven_logs_interceptor_python-0.1.17}/src/logs_interceptor/infrastructure/circuit_breaker/circuit_breaker.py +0 -0
  21. {elven_logs_interceptor_python-0.1.16 → elven_logs_interceptor_python-0.1.17}/src/logs_interceptor/infrastructure/compression/__init__.py +0 -0
  22. {elven_logs_interceptor_python-0.1.16 → elven_logs_interceptor_python-0.1.17}/src/logs_interceptor/infrastructure/compression/base.py +0 -0
  23. {elven_logs_interceptor_python-0.1.16 → elven_logs_interceptor_python-0.1.17}/src/logs_interceptor/infrastructure/compression/brotli_compressor.py +0 -0
  24. {elven_logs_interceptor_python-0.1.16 → elven_logs_interceptor_python-0.1.17}/src/logs_interceptor/infrastructure/compression/factory.py +0 -0
  25. {elven_logs_interceptor_python-0.1.16 → elven_logs_interceptor_python-0.1.17}/src/logs_interceptor/infrastructure/compression/gzip_compressor.py +0 -0
  26. {elven_logs_interceptor_python-0.1.16 → elven_logs_interceptor_python-0.1.17}/src/logs_interceptor/infrastructure/compression/noop_compressor.py +0 -0
  27. {elven_logs_interceptor_python-0.1.16 → elven_logs_interceptor_python-0.1.17}/src/logs_interceptor/infrastructure/context/__init__.py +0 -0
  28. {elven_logs_interceptor_python-0.1.16 → elven_logs_interceptor_python-0.1.17}/src/logs_interceptor/infrastructure/context/context_provider.py +0 -0
  29. {elven_logs_interceptor_python-0.1.16 → elven_logs_interceptor_python-0.1.17}/src/logs_interceptor/infrastructure/dlq/__init__.py +0 -0
  30. {elven_logs_interceptor_python-0.1.16 → elven_logs_interceptor_python-0.1.17}/src/logs_interceptor/infrastructure/dlq/file_dlq.py +0 -0
  31. {elven_logs_interceptor_python-0.1.16 → elven_logs_interceptor_python-0.1.17}/src/logs_interceptor/infrastructure/dlq/memory_dlq.py +0 -0
  32. {elven_logs_interceptor_python-0.1.16 → elven_logs_interceptor_python-0.1.17}/src/logs_interceptor/infrastructure/filter/__init__.py +0 -0
  33. {elven_logs_interceptor_python-0.1.16 → elven_logs_interceptor_python-0.1.17}/src/logs_interceptor/infrastructure/filter/log_filter.py +0 -0
  34. {elven_logs_interceptor_python-0.1.16 → elven_logs_interceptor_python-0.1.17}/src/logs_interceptor/infrastructure/interceptors/__init__.py +0 -0
  35. {elven_logs_interceptor_python-0.1.16 → elven_logs_interceptor_python-0.1.17}/src/logs_interceptor/infrastructure/interceptors/runtime_interceptor.py +0 -0
  36. {elven_logs_interceptor_python-0.1.16 → elven_logs_interceptor_python-0.1.17}/src/logs_interceptor/infrastructure/internal_capture_guard.py +0 -0
  37. {elven_logs_interceptor_python-0.1.16 → elven_logs_interceptor_python-0.1.17}/src/logs_interceptor/infrastructure/log_noise_filter.py +0 -0
  38. {elven_logs_interceptor_python-0.1.16 → elven_logs_interceptor_python-0.1.17}/src/logs_interceptor/infrastructure/log_record_extra.py +0 -0
  39. {elven_logs_interceptor_python-0.1.16 → elven_logs_interceptor_python-0.1.17}/src/logs_interceptor/infrastructure/memory/__init__.py +0 -0
  40. {elven_logs_interceptor_python-0.1.16 → elven_logs_interceptor_python-0.1.17}/src/logs_interceptor/infrastructure/memory/memory_tracker.py +0 -0
  41. {elven_logs_interceptor_python-0.1.16 → elven_logs_interceptor_python-0.1.17}/src/logs_interceptor/infrastructure/metrics/__init__.py +0 -0
  42. {elven_logs_interceptor_python-0.1.16 → elven_logs_interceptor_python-0.1.17}/src/logs_interceptor/infrastructure/metrics/metrics_collector.py +0 -0
  43. {elven_logs_interceptor_python-0.1.16 → elven_logs_interceptor_python-0.1.17}/src/logs_interceptor/infrastructure/transport/__init__.py +0 -0
  44. {elven_logs_interceptor_python-0.1.16 → elven_logs_interceptor_python-0.1.17}/src/logs_interceptor/infrastructure/transport/loki_json_transport.py +0 -0
  45. {elven_logs_interceptor_python-0.1.16 → elven_logs_interceptor_python-0.1.17}/src/logs_interceptor/infrastructure/transport/loki_protobuf_transport.py +0 -0
  46. {elven_logs_interceptor_python-0.1.16 → elven_logs_interceptor_python-0.1.17}/src/logs_interceptor/infrastructure/transport/otlp_json_transport.py +0 -0
  47. {elven_logs_interceptor_python-0.1.16 → elven_logs_interceptor_python-0.1.17}/src/logs_interceptor/infrastructure/transport/resilient_transport.py +0 -0
  48. {elven_logs_interceptor_python-0.1.16 → elven_logs_interceptor_python-0.1.17}/src/logs_interceptor/infrastructure/transport/transport_factory.py +0 -0
  49. {elven_logs_interceptor_python-0.1.16 → elven_logs_interceptor_python-0.1.17}/src/logs_interceptor/infrastructure/workers/__init__.py +0 -0
  50. {elven_logs_interceptor_python-0.1.16 → elven_logs_interceptor_python-0.1.17}/src/logs_interceptor/infrastructure/workers/worker_pool.py +0 -0
  51. {elven_logs_interceptor_python-0.1.16 → elven_logs_interceptor_python-0.1.17}/src/logs_interceptor/integrations/__init__.py +0 -0
  52. {elven_logs_interceptor_python-0.1.16 → elven_logs_interceptor_python-0.1.17}/src/logs_interceptor/integrations/celery.py +0 -0
  53. {elven_logs_interceptor_python-0.1.16 → elven_logs_interceptor_python-0.1.17}/src/logs_interceptor/integrations/django.py +0 -0
  54. {elven_logs_interceptor_python-0.1.16 → elven_logs_interceptor_python-0.1.17}/src/logs_interceptor/integrations/fastapi.py +0 -0
  55. {elven_logs_interceptor_python-0.1.16 → elven_logs_interceptor_python-0.1.17}/src/logs_interceptor/integrations/flask.py +0 -0
  56. {elven_logs_interceptor_python-0.1.16 → elven_logs_interceptor_python-0.1.17}/src/logs_interceptor/integrations/logging_handler.py +0 -0
  57. {elven_logs_interceptor_python-0.1.16 → elven_logs_interceptor_python-0.1.17}/src/logs_interceptor/integrations/loguru.py +0 -0
  58. {elven_logs_interceptor_python-0.1.16 → elven_logs_interceptor_python-0.1.17}/src/logs_interceptor/integrations/structlog.py +0 -0
  59. {elven_logs_interceptor_python-0.1.16 → elven_logs_interceptor_python-0.1.17}/src/logs_interceptor/preload.py +0 -0
  60. {elven_logs_interceptor_python-0.1.16 → elven_logs_interceptor_python-0.1.17}/src/logs_interceptor/presentation/__init__.py +0 -0
  61. {elven_logs_interceptor_python-0.1.16 → elven_logs_interceptor_python-0.1.17}/src/logs_interceptor/presentation/factory.py +0 -0
  62. {elven_logs_interceptor_python-0.1.16 → elven_logs_interceptor_python-0.1.17}/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.16
3
+ Version: 0.1.17
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
@@ -123,12 +123,16 @@ logging.getLogger(__name__).info(
123
123
 
124
124
  Supported aliases: `warning(...)`, `exception(...)` and `critical(...)`.
125
125
 
126
- Sensitive fields such as `cpf`, `cnpj`, `cpfCnpj`, `password`, `token` and `authorization` are
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.
126
+ Sensitive fields such as `cpf`, `cnpj`, `cpfCnpj`, `documento`,
127
+ `numeroDocumento`, `taxId`, `apiKey`, `clientSecret`, `senha`, `password`,
128
+ `token`, `authorization`, `cartao/cartão`, `chavePix` and related credential
129
+ fields are redacted by default. In messages, the library redacts only the
130
+ sensitive fragment/value and preserves the rest of the log. In structured
131
+ context, sensitive keys redact the full value. The key matcher normalizes
132
+ common `snake_case`, `camelCase`, `kebab-case`, dotted variants, and accents.
133
+ Set `LOGS_FILTER_SANITIZE=false`, `LOGS_SANITIZE=false`, or
134
+ `UO_LOGS_SANITIZE=false` only when raw sensitive data is explicitly required
135
+ and approved.
132
136
 
133
137
  ## Environment Variables
134
138
 
@@ -62,12 +62,16 @@ logging.getLogger(__name__).info(
62
62
 
63
63
  Supported aliases: `warning(...)`, `exception(...)` and `critical(...)`.
64
64
 
65
- Sensitive fields such as `cpf`, `cnpj`, `cpfCnpj`, `password`, `token` and `authorization` are
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.
65
+ Sensitive fields such as `cpf`, `cnpj`, `cpfCnpj`, `documento`,
66
+ `numeroDocumento`, `taxId`, `apiKey`, `clientSecret`, `senha`, `password`,
67
+ `token`, `authorization`, `cartao/cartão`, `chavePix` and related credential
68
+ fields are redacted by default. In messages, the library redacts only the
69
+ sensitive fragment/value and preserves the rest of the log. In structured
70
+ context, sensitive keys redact the full value. The key matcher normalizes
71
+ common `snake_case`, `camelCase`, `kebab-case`, dotted variants, and accents.
72
+ Set `LOGS_FILTER_SANITIZE=false`, `LOGS_SANITIZE=false`, or
73
+ `UO_LOGS_SANITIZE=false` only when raw sensitive data is explicitly required
74
+ and approved.
71
75
 
72
76
  ## Environment Variables
73
77
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "elven-logs-interceptor-python"
7
- version = "0.1.16"
7
+ version = "0.1.17"
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"
@@ -24,13 +24,25 @@ from ..types import LogLevel
24
24
  DEFAULT_LEVELS: list[LogLevel] = ["debug", "info", "warn", "error", "fatal"]
25
25
  DEFAULT_SENSITIVE_PATTERNS = [
26
26
  r"password",
27
+ r"senha",
27
28
  r"token",
28
29
  r"secret",
30
+ r"segredo",
29
31
  r"api[_-]?key",
32
+ r"chave[_-]?api",
30
33
  r"authorization",
34
+ r"credential",
31
35
  r"credit[_-]?card",
36
+ r"cartao",
32
37
  r"ssn",
33
38
  r"cpf[_-]?cnpj",
39
+ r"cnpj[_-]?cpf",
40
+ r"tax[_-]?id",
41
+ r"document[_-]?number",
42
+ r"documento",
43
+ r"numero[_-]?documento",
44
+ r"passaporte",
45
+ r"passport",
34
46
  r"cnpj",
35
47
  r"cpf",
36
48
  ]
@@ -8,6 +8,7 @@ import re
8
8
  import secrets
9
9
  import sys
10
10
  import traceback
11
+ import unicodedata
11
12
  from collections.abc import Iterable, Mapping
12
13
  from dataclasses import asdict, fields, is_dataclass
13
14
  from datetime import datetime
@@ -39,6 +40,58 @@ except Exception: # pragma: no cover - optional dependency
39
40
  TRUE_VALUES = {"1", "true", "yes", "on"}
40
41
  FALSE_VALUES = {"0", "false", "no", "off"}
41
42
 
43
+ _SENSITIVE_KEY_TOKENS = (
44
+ "password",
45
+ "passwd",
46
+ "passphrase",
47
+ "pwd",
48
+ "senha",
49
+ "senhas",
50
+ "token",
51
+ "jwt",
52
+ "secret",
53
+ "segredo",
54
+ "clientsecret",
55
+ "privatekey",
56
+ "apikey",
57
+ "chaveapi",
58
+ "chavesecreta",
59
+ "chaveacesso",
60
+ "chavepix",
61
+ "accesskey",
62
+ "authorization",
63
+ "credentials",
64
+ "credential",
65
+ "credenciais",
66
+ "credencial",
67
+ "cpfcnpj",
68
+ "cnpjcpf",
69
+ "cpf",
70
+ "cnpj",
71
+ "taxid",
72
+ "taxidentifier",
73
+ "documentnumber",
74
+ "documentid",
75
+ "documento",
76
+ "numerodocumento",
77
+ "documentoidentificacao",
78
+ "passport",
79
+ "passaporte",
80
+ "ssn",
81
+ "creditcard",
82
+ "cardnumber",
83
+ "cartao",
84
+ "numerocartao",
85
+ "cvv",
86
+ "cvc",
87
+ "pixkey",
88
+ "bankaccount",
89
+ "accountnumber",
90
+ "numeroconta",
91
+ "iban",
92
+ "email",
93
+ )
94
+
42
95
 
43
96
  def parse_bool(value: str | None, default: bool) -> bool:
44
97
  if value is None:
@@ -395,10 +448,7 @@ def detect_sensitive_data(text: str, patterns: Iterable[str]) -> bool:
395
448
  def redact_sensitive_text(text: str, sensitive_patterns: Iterable[str]) -> str:
396
449
  """Redact sensitive values while preserving the surrounding log message."""
397
450
  patterns = list(sensitive_patterns)
398
- redacted = text
399
-
400
- for pattern in patterns:
401
- redacted = _redact_key_value_pattern(redacted, pattern)
451
+ redacted = _redact_sensitive_key_values(text, patterns)
402
452
 
403
453
  common_replacements: tuple[tuple[re.Pattern[str], str], ...] = (
404
454
  (
@@ -436,31 +486,70 @@ def redact_sensitive_text(text: str, sensitive_patterns: Iterable[str]) -> str:
436
486
  return redacted
437
487
 
438
488
 
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)",
489
+ def _redact_sensitive_key_values(text: str, sensitive_patterns: Iterable[str]) -> str:
490
+ key_value = re.compile(
491
+ r"(?P<key_quote>[\"']?)(?P<key>[A-Za-z_][\w.\-]*)(?P=key_quote)"
492
+ r"(?P<separator>\s*[:=]\s*)"
493
+ r"(?P<value_quote>[\"'])(?P<value>.*?)(?P=value_quote)",
442
494
  re.IGNORECASE,
443
495
  )
444
- redacted = quoted.sub(r"\g<prefix>\g<quote>[REDACTED]\g<quote>", text)
496
+
497
+ def _replace_key_value(match: re.Match[str]) -> str:
498
+ if not _is_sensitive_key(match.group("key"), sensitive_patterns):
499
+ return match.group(0)
500
+ return (
501
+ f"{match.group('key_quote')}{match.group('key')}{match.group('key_quote')}"
502
+ f"{match.group('separator')}{match.group('value_quote')}[REDACTED]"
503
+ f"{match.group('value_quote')}"
504
+ )
505
+
506
+ redacted = key_value.sub(_replace_key_value, text)
445
507
 
446
508
  unquoted = re.compile(
447
- rf"(?P<prefix>(?:[\"']?{key_pattern}[\"']?)\s*[:=]\s*)(?P<value>[^\s,;&}}\]\"']+)",
509
+ r"(?P<key>[A-Za-z_][\w.\-]*)"
510
+ r"(?P<separator>\s*[:=]\s*)"
511
+ r"(?P<value>[^\s,;&}}\]\"']+)",
448
512
  re.IGNORECASE,
449
513
  )
450
514
 
451
515
  def _replace_unquoted(match: re.Match[str]) -> str:
516
+ if not _is_sensitive_key(match.group("key"), sensitive_patterns):
517
+ return match.group(0)
452
518
  value = match.group("value")
453
519
  if value.lower() in {"bearer", "basic"} or value == "[REDACTED]":
454
520
  return match.group(0)
455
- return f"{match.group('prefix')}[REDACTED]"
521
+ return f"{match.group('key')}{match.group('separator')}[REDACTED]"
456
522
 
457
523
  redacted = unquoted.sub(_replace_unquoted, redacted)
458
524
 
459
525
  query = re.compile(
460
- rf"(?P<prefix>[?&](?:{key_pattern})=)(?P<value>[^&#\s]+)",
526
+ r"(?P<prefix>[?&](?P<key>[A-Za-z_][\w.\-]*)=)(?P<value>[^&#\s]+)",
461
527
  re.IGNORECASE,
462
528
  )
463
- return query.sub(r"\g<prefix>[REDACTED]", redacted)
529
+
530
+ def _replace_query(match: re.Match[str]) -> str:
531
+ if not _is_sensitive_key(match.group("key"), sensitive_patterns):
532
+ return match.group(0)
533
+ return f"{match.group('prefix')}[REDACTED]"
534
+
535
+ return query.sub(_replace_query, redacted)
536
+
537
+
538
+ def _is_sensitive_key(key: str, sensitive_patterns: Iterable[str]) -> bool:
539
+ compiled = [re.compile(pattern, re.IGNORECASE) for pattern in sensitive_patterns]
540
+ if any(pattern.search(key) for pattern in compiled):
541
+ return True
542
+ normalized = _normalize_sensitive_key(key)
543
+ return any(token in normalized for token in _SENSITIVE_KEY_TOKENS)
544
+
545
+
546
+ def _normalize_sensitive_key(key: str) -> str:
547
+ ascii_key = (
548
+ unicodedata.normalize("NFKD", key)
549
+ .encode("ascii", "ignore")
550
+ .decode("ascii")
551
+ )
552
+ return re.sub(r"[^a-z0-9]", "", ascii_key.lower())
464
553
 
465
554
 
466
555
  def _looks_like_value_pattern(pattern: str) -> bool:
@@ -490,7 +579,9 @@ def sanitize_data(
490
579
  sanitized: dict[str, Any] = {}
491
580
 
492
581
  for key, value in data.items():
493
- key_sensitive = any(pattern.search(key) for pattern in compiled)
582
+ key_sensitive = any(pattern.search(key) for pattern in compiled) or _is_sensitive_key(
583
+ key, sensitive_patterns
584
+ )
494
585
  if key_sensitive:
495
586
  sanitized[key] = "[REDACTED]"
496
587
  continue