Repository navigation
Proposal: TelemetrySanitizer interface, built-in StandardTelemetrySanitizer, and composable sanitizer chain on TelemetryConfig #7845
Description
** Please make sure you read the contribution guide and file the issues in the right place. **
Contribution guide.
🔴 Required Information
Is your feature request related to a specific problem?
Yes. Today, ADK's telemetry and logging layers face two related redaction/sanitization problems:
- Fragmented built-in credential/secret redaction across ADK modules:
src/google/adk/telemetry/tracing.pycalls_redact_credential_secrets(src/google/adk/auth/auth_credential.py) ontool_call_argsandtool_response, whilesafe_json_serialize(src/google/adk/telemetry/_serialization.py) does not (tracked in safe_json_serialize dumps AuthCredential secret fields into span attributes; MCP headers are redacted, these are not #7311 / fix: mask credential material in the telemetry serializer #7312).src/google/adk/plugins/auto_tracing_helpers.pymaintains its own_CREDENTIAL_TYPE_NAMESand_redacted_repr.src/google/adk/plugins/debug_logging_plugin.pymaintains_redact_private_keysand secret key-name redaction.src/google/adk/plugins/bigquery_agent_analytics_plugin.pymaintains_redact_sensitive_patterns(_BASIC_TOKEN_RE,_SENSITIVE_TEXT_KEY_RE, structural key redaction, and fail-closed redaction boundaries, see fix(plugins): redact credential patterns in free-text values, not just exact keys #6499).
- No extensible sanitizer interface between raw
ALL_CONTENTand blindNO_CONTENTonTelemetryConfig:- In
src/google/adk/telemetry/context.py,TelemetryConfigandContentCapturingModeonly offer a binary choice between capturing raw payloads (ALL_CONTENT/SPAN_ONLY/EVENT_ONLY) and dropping all payload content (NO_CONTENT, where legacy attributes record"{}"and OTel GenAI events omit message bodies; see docs(telemetry): disable all content paths in the admin lock recipe #7739). - Regulated enterprise deployments (healthcare, finance, customer support) need
gcp.vertex.agent.llm_request,llm_response,tool_call_args,tool_response, andgen_ai.*events to retain structural JSON (schema keys, tool names, non-sensitive fields, and redacted placeholders like[REDACTED_EMAIL]) while scrubbing domain-specific PII/secrets before attributes are serialized onto the span or log record. - Furthermore, even under
ContentCapturingMode.NO_CONTENT, unhandled tool or model exceptions flow throughsrc/google/adk/telemetry/_instrumentation.py(_finish_scope_with_error/ToolScope) intospan.record_exception(error)andStatus(StatusCode.ERROR, description=str(error)). Because PydanticValidationError, HTTP client errors, and database exceptions routinely embed raw input values instr(error), sensitive payloads leak into span status descriptions and exception events even when content capture is disabled.
- In
Describe the Solution You'd Like
Introduce a pluggable sanitizer architecture in google.adk.telemetry with three parts:
-
TelemetrySanitizerinterface (Protocol / ABC):
Defines a standard contract for pre-export telemetry sanitization covering both structured content payloads and exception descriptions:sanitize_content(self, value: Any) -> Any: Transforms/redacts LLM request/response payloads, tool args/responses, and GenAI event bodies beforesafe_json_serializewrites them to spans or OTel logs.sanitize_exception(self, error: BaseException) -> str: Scrubs exception message text before it is recorded onStatus(StatusCode.ERROR, description=...)andexception.messagespan events (active even underContentCapturingMode.NO_CONTENT).
-
Built-in
StandardTelemetrySanitizershipped by ADK:
Consolidates ADK's standard credential and secret hygiene (AuthCredential/AuthConfigsecret fields from safe_json_serialize dumps AuthCredential secret fields into span attributes; MCP headers are redacted, these are not #7311, Bearer/Basic auth tokens, PEM private keys, and sensitive header/dict keys) into a reusable, fail-closed built-in implementation ofTelemetrySanitizer. -
Composable configuration on
TelemetryConfig(extend or replace):
Allow teams to either append additional custom sanitizers on top ofStandardTelemetrySanitizeror replace the sanitizer pipeline entirely onTelemetryConfig:sanitizers: tuple[TelemetrySanitizer, ...] = (StandardTelemetrySanitizer(),)(orextra_sanitizersto append to the standard sanitizer vs.sanitizersto replace it).- Sanitizers execute in sequence (
s1 -> s2 -> ...), with fail-closed error handling (if a sanitizer raises on malformed input, return a safe"[REDACTION_ERROR]"marker rather than leaking raw content or crashing the agent invocation).
Impact on your work
In production ticket-routing and enterprise support agents, we currently maintain a custom ADK plugin + custom OTel SpanProcessor that reaches into private ReadableSpan._attributes and scrubs StatusCode.ERROR descriptions post-hoc to pass compliance checks. Having ADK supply the TelemetrySanitizer interface and StandardTelemetrySanitizer—while letting teams chain domain-specific PII sanitizers or swap in their own enterprise DLP sanitizer via TelemetryConfig—eliminates private OTel SDK state mutation and unifies redaction across ADK.
Willingness to contribute
Yes — happy to submit a focused PR with unit tests once we align on the interface and TelemetryConfig API.
🟡 Recommended Information
Describe Alternatives You've Considered
ContentCapturingMode.NO_CONTENT(docs(telemetry): disable all content paths in the admin lock recipe #7739): Drops all prompt, response, and tool payload structure ("{}"), making it impossible to inspect non-PII fields in Cloud Trace / Dev UI, and still leavesStatus(StatusCode.ERROR, description=str(error))unsanitized.- Custom OTel
SpanProcessor: Requires mutatingReadableSpan._attributes(a private attribute on an immutable-by-contract OTel object) and parsing already-serialized JSON strings (gcp.vertex.agent.llm_request) back into dicts on every span end. - Raw single-callable hook without a standard sanitizer: Forces every team to re-implement baseline credential/token masking from scratch instead of layering domain rules onto ADK's built-in secret hygiene.
Proposed API / Implementation
from typing import Any, Protocol, runtime_checkable
from google.adk.telemetry.context import ContentCapturingMode, TelemetryConfig
from google.adk.telemetry.sanitizers import (
StandardTelemetrySanitizer,
TelemetrySanitizer,
)
@runtime_checkable
class TelemetrySanitizer(Protocol):
"""Contract for pre-export telemetry content and exception sanitization."""
def sanitize_content(self, value: Any) -> Any:
"""Sanitizes structured/text payloads before JSON serialization onto spans/logs."""
...
def sanitize_exception(self, error: BaseException) -> str:
"""Sanitizes exception messages before recording StatusCode.ERROR / exception events."""
...
class CustomerPiiSanitizer:
"""Team-specific sanitizer layered on top of ADK's StandardTelemetrySanitizer."""
def sanitize_content(self, value: Any) -> Any:
# Redact SSNs, emails, payment cards in dicts/lists/strings
...
def sanitize_exception(self, error: BaseException) -> str:
# Scrub PII patterns from str(error)
...
# 1. Default: uses (StandardTelemetrySanitizer(),) out of the box
default_cfg = TelemetryConfig()
# 2. Extend: keep ADK's StandardTelemetrySanitizer and chain custom sanitizers after it
extended_cfg = TelemetryConfig(
capture_message_content=ContentCapturingMode.SPAN_ONLY,
sanitizers=(StandardTelemetrySanitizer(), CustomerPiiSanitizer()),
)
# 3. Replace: supply a completely custom enterprise sanitizer
replaced_cfg = TelemetryConfig(
capture_message_content=ContentCapturingMode.SPAN_ONLY,
sanitizers=(EnterpriseDlpSanitizer(),),
)Additional Context
- Related issues/PRs: safe_json_serialize dumps AuthCredential secret fields into span attributes; MCP headers are redacted, these are not #7311, fix: mask credential material in the telemetry serializer #7312 (
AuthCredentialmasking insafe_json_serialize), fix(plugins): redact credential patterns in free-text values, not just exact keys #6499 (credential pattern redaction in plugins), docs(telemetry): disable all content paths in the admin lock recipe #7739 (NO_CONTENTadmin lock recipe).
Activity
[-]Proposal: Optional pre-export content and exception sanitizer hooks on `TelemetryConfig`[/-][+]Proposal: `TelemetrySanitizer` interface, built-in `StandardTelemetrySanitizer`, and composable sanitizer chain on `TelemetryConfig`[/+]