Skip to content

Commit 1bb0cd3

Browse files
committed
chore(telemetry): improve redaction of telemetry error logs
1 parent e2bc7d1 commit 1bb0cd3

File tree

2 files changed

+57
-37
lines changed

2 files changed

+57
-37
lines changed

ddtrace/internal/telemetry/writer.py

Lines changed: 31 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
from ddtrace.internal.endpoints import endpoint_collection
1919
from ddtrace.internal.logger import get_logger
20+
from ddtrace.internal.packages import is_user_code
2021
from ddtrace.internal.utils.http import get_connection
2122
from ddtrace.settings._agent import config as agent_config
2223
from ddtrace.settings._telemetry import config
@@ -523,42 +524,49 @@ def add_log(self, level, message, stack_trace="", tags=None):
523524
# Logs are hashed using the message, level, tags, and stack_trace. This should prevent duplicatation.
524525
self._logs.add(data)
525526

526-
def add_integration_error_log(self, msg: str, exc: BaseException) -> None:
527+
def add_error_log(self, msg: str, exc: BaseException) -> None:
527528
if config.LOG_COLLECTION_ENABLED:
528529
stack_trace = self._format_stack_trace(exc)
530+
529531
self.add_log(
530532
TELEMETRY_LOG_LEVEL.ERROR,
531533
msg,
532534
stack_trace=stack_trace if stack_trace is not None else "",
533535
)
534536

535537
def _format_stack_trace(self, exc: BaseException) -> Optional[str]:
536-
exc_type, exc_value, exc_traceback = type(exc), exc, exc.__traceback__
537-
if exc_traceback:
538-
tb = traceback.extract_tb(exc_traceback)
539-
formatted_tb = ["Traceback (most recent call last):"]
540-
for filename, lineno, funcname, srcline in tb:
541-
if self._should_redact(filename):
542-
formatted_tb.append(" <REDACTED>")
543-
formatted_tb.append(" <REDACTED>")
544-
else:
545-
relative_filename = self._format_file_path(filename)
546-
formatted_line = f' File "{relative_filename}", line {lineno}, in {funcname}\n {srcline}'
547-
formatted_tb.append(formatted_line)
548-
if exc_type:
549-
formatted_tb.append(f"{exc_type.__module__}.{exc_type.__name__}: {exc_value}")
550-
return "\n".join(formatted_tb)
551-
552-
return None
553-
554-
def _should_redact(self, filename: str) -> bool:
555-
return "ddtrace" not in filename
538+
exc_type, _, exc_traceback = type(exc), exc, getattr(exc, "__traceback__", None)
539+
540+
if not exc_traceback:
541+
return None
542+
543+
tb = traceback.extract_tb(exc_traceback)
544+
formatted_tb = ["Traceback (most recent call last):"]
545+
for filename, lineno, funcname, srcline in tb:
546+
if is_user_code(filename):
547+
formatted_tb.append(" <REDACTED>")
548+
formatted_tb.append(" <REDACTED>")
549+
else:
550+
relative_filename = self._format_file_path(filename)
551+
formatted_line = f' File "{relative_filename}", line {lineno}, in {funcname}\n {srcline}'
552+
formatted_tb.append(formatted_line)
553+
if exc_type:
554+
formatted_tb.append(f"{exc_type.__module__}.{exc_type.__name__}: <REDACTED>")
555+
return "\n".join(formatted_tb)
556556

557557
def _format_file_path(self, filename: str) -> str:
558558
try:
559-
return os.path.relpath(filename, start=self.CWD)
559+
if "site-packages" in filename:
560+
return filename.split("site-packages", 1)[1].lstrip("/")
561+
elif "lib/python" in filename:
562+
return (
563+
filename.split("lib/python", 1)[1].split("/", 1)[1]
564+
if "/" in filename.split("lib/python", 1)[1]
565+
else "python_stdlib"
566+
)
567+
return "<REDACTED>"
560568
except ValueError:
561-
return filename
569+
return "<REDACTED>"
562570

563571
def add_gauge_metric(
564572
self, namespace: TELEMETRY_NAMESPACE, name: str, value: float, tags: Optional[MetricTagType] = None

tests/telemetry/test_writer.py

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1089,12 +1089,14 @@ def test_otel_config_telemetry(test_agent_session, run_python_code_in_subprocess
10891089
assert tags == [["config_opentelemetry:otel_logs_exporter"]]
10901090

10911091

1092-
def test_add_integration_error_log(mock_time, telemetry_writer, test_agent_session):
1092+
def test_add_error_log(mock_time, telemetry_writer, test_agent_session):
10931093
"""Test add_integration_error_log functionality with real stack trace"""
10941094
try:
1095-
raise ValueError("Test exception")
1096-
except ValueError as e:
1097-
telemetry_writer.add_integration_error_log("Test error message", e)
1095+
import json
1096+
1097+
json.loads("{invalid: json,}")
1098+
except Exception as e:
1099+
telemetry_writer.add_error_log("Test error message", e)
10981100
telemetry_writer.periodic(force_flush=True)
10991101

11001102
log_events = test_agent_session.get_events("logs")
@@ -1110,9 +1112,14 @@ def test_add_integration_error_log(mock_time, telemetry_writer, test_agent_sessi
11101112
stack_trace = log_entry["stack_trace"]
11111113
expected_lines = [
11121114
"Traceback (most recent call last):",
1113-
" <REDACTED>",
1114-
" <REDACTED>",
1115-
"builtins.ValueError: Test exception",
1115+
"<REDACTED>", # User code gets redacted
1116+
' File "json/__init__.py',
1117+
" return _default_decoder.decode(s)",
1118+
' File "json/decoder.py"',
1119+
" obj, end = self.raw_decode(s, idx=_w(s, 0).end())",
1120+
' File "json/decoder.py"',
1121+
" obj, end = self.scan_once(s, idx)",
1122+
"json.decoder.JSONDecodeError: <REDACTED>",
11161123
]
11171124
for expected_line in expected_lines:
11181125
assert expected_line in stack_trace
@@ -1127,7 +1134,7 @@ def test_add_integration_error_log_with_log_collection_disabled(mock_time, telem
11271134
try:
11281135
raise ValueError("Test exception")
11291136
except ValueError as e:
1130-
telemetry_writer.add_integration_error_log("Test error message", e)
1137+
telemetry_writer.add_error_log("Test error message", e)
11311138
telemetry_writer.periodic(force_flush=True)
11321139

11331140
log_events = test_agent_session.get_events("logs", subprocess=True)
@@ -1137,17 +1144,22 @@ def test_add_integration_error_log_with_log_collection_disabled(mock_time, telem
11371144

11381145

11391146
@pytest.mark.parametrize(
1140-
"filename, is_redacted",
1147+
"filename, result",
11411148
[
1142-
("/path/to/file.py", True),
1143-
("/path/to/ddtrace/contrib/flask/file.py", False),
1144-
("/path/to/dd-trace-something/file.py", True),
1149+
("/path/to/file.py", "<REDACTED>"),
1150+
("/path/to/ddtrace/contrib/flask/file.py", "<REDACTED>"),
1151+
("/path/to/lib/python3.13/site-packages/ddtrace/_trace/tracer.py", "ddtrace/_trace/tracer.py"),
1152+
("/path/to/lib/python3.13/site-packages/requests/api.py", "requests/api.py"),
1153+
(
1154+
"/path/to/[email protected]/3.13.1/Frameworks/Python.framework/Versions/3.13/lib/python3.13/json/__init__.py",
1155+
"json/__init__.py",
1156+
),
11451157
],
11461158
)
1147-
def test_redact_filename(filename, is_redacted):
1159+
def test_redact_filename(filename, result):
11481160
"""Test file redaction logic"""
11491161
writer = TelemetryWriter(is_periodic=False)
1150-
assert writer._should_redact(filename) == is_redacted
1162+
assert writer._format_file_path(filename) == result
11511163

11521164

11531165
def test_telemetry_writer_multiple_sources_config(telemetry_writer, test_agent_session):

0 commit comments

Comments
 (0)