Coverage for src / qdrant_loader_core / logging_processors.py: 79%
33 statements
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-12 09:42 +0000
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-12 09:42 +0000
1"""Logging processors and formatters for structlog."""
3from __future__ import annotations
5import logging
6import re
7from typing import Any
10class CleanFormatter(logging.Formatter):
11 """Formatter that removes ANSI color codes for clean file output."""
13 def format(self, record: logging.LogRecord) -> str:
14 message = super().format(record)
15 try:
16 ansi_escape = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])")
17 return ansi_escape.sub("", message)
18 except Exception:
19 return message
22def redact_processor(
23 logger: Any, method_name: str, event_dict: dict[str, Any]
24) -> dict[str, Any]:
25 """Structlog processor to redact sensitive fields in event_dict."""
26 sensitive_keys = {
27 "api_key",
28 "llm_api_key",
29 "authorization",
30 "Authorization",
31 "token",
32 "access_token",
33 "secret",
34 "password",
35 }
37 def mask(value: str) -> str:
38 try:
39 if not isinstance(value, str) or not value:
40 return "***REDACTED***"
41 if len(value) <= 8:
42 return "***REDACTED***"
43 return value[:2] + "***REDACTED***" + value[-2:]
44 except Exception:
45 return "***REDACTED***"
47 def deep_redact(obj: Any) -> Any:
48 try:
49 if isinstance(obj, dict):
50 return {
51 k: (
52 mask(v)
53 if k in sensitive_keys and isinstance(v, str)
54 else deep_redact(v)
55 )
56 for k, v in obj.items()
57 }
58 if isinstance(obj, list):
59 return [deep_redact(i) for i in obj]
60 return obj
61 except Exception:
62 return obj
64 return deep_redact(event_dict)