{"record":{"id":"c4b039b37149e566","repo":"BerriAI/litellm","slug":"otel-attributes-include-list-and-exclude-list-are","errorCode":null,"errorMessage":"otel.attributes: include_list and exclude_list are mutually exclusive","messagePattern":"otel\\.attributes: include_list and exclude_list are mutually exclusive","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"litellm/integrations/opentelemetry.py","lineNumber":179,"sourceCode":"        raise ValueError(\n            \"otel.attributes must be a mapping with optional 'include_list' / \"\n            f\"'exclude_list', got {type(value).__name__}\"\n        )\n    return OTELMetricAttributeFilter(\n        include_list=value.get(\"include_list\"),\n        exclude_list=value.get(\"exclude_list\"),\n    )\n\n\ndef _resolve_metric_attribute_filter(\n    attributes: OTELMetricAttributeFilter | None,\n) -> tuple[frozenset[str] | None, frozenset[str] | None]:\n    if attributes is None:\n        return None, None\n    include: Final = attributes.include_list or None\n    exclude: Final = attributes.exclude_list or None\n    if include and exclude:\n        raise ValueError(\"otel.attributes: include_list and exclude_list are mutually exclusive\")\n    requested: Final = include or exclude or []\n    if TOKEN_TYPE_ATTRIBUTE in requested:\n        raise ValueError(\n            f\"otel.attributes: {TOKEN_TYPE_ATTRIBUTE} is a structural token-usage discriminator and cannot be filtered\"\n        )\n    unknown: Final = sorted(name for name in requested if name not in VALID_METRIC_ATTRIBUTE_NAMES)\n    if unknown:\n        raise ValueError(\n            f\"otel.attributes: unknown attribute name(s) {unknown}. Valid names: {sorted(VALID_METRIC_ATTRIBUTE_NAMES)}\"\n        )\n    return (\n        frozenset(include) if include else None,\n        frozenset(exclude) if exclude else None,\n    )\n\n\ndef _normalize_team_metadata_keys(value: Any) -> list[str]:\n    \"\"\"Coerce a team-metadata allowlist from a list or comma-separated string.","sourceCodeStart":161,"sourceCodeEnd":197,"githubUrl":"https://github.com/BerriAI/litellm/blob/77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8/litellm/integrations/opentelemetry.py#L161-L197","documentation":"The otel.attributes metric filter supports either an include_list (only these attributes are kept on metrics) or an exclude_list (drop these), never both. _resolve_metric_attribute_filter raises ValueError when both resolve to non-empty lists, because applying two opposite cardinality policies to the same metric set is ambiguous. Empty/falsy lists are treated as absent, so one side must actually be omitted or empty.","triggerScenarios":"Configuring otel.attributes (or attributes= on OpenTelemetry(...)) with both keys populated, e.g. {include_list: [gen_ai.system], exclude_list: [metadata.user_api_key_team_id]}. Validation runs eagerly when an explicit config is passed at construction, or lazily on the first metric record when the proxy populates callback_settings.otel.attributes after the logger is built.","commonSituations":"Merging two example configs (one using include, one exclude) during a merge/refactor; incrementally tightening cardinality by adding exclude_list on top of an existing include_list instead of editing it; misunderstanding the filter as ordered rules (include-then-exclude pipeline) rather than mutually exclusive modes.","solutions":["Pick one mode: keep include_list and delete exclude_list, or vice versa","For 'allow most but drop a few', use exclude_list alone with the attributes you want removed","For 'keep only a few', use include_list alone","Re-apply config and restart; if the error appears on first metric rather than startup, remember lazy resolution means the bad config was in the proxy's callback_settings, not just the constructor args"],"exampleFix":"# before (both -> ValueError)\nattributes:\n  include_list: [gen_ai.system]\n  exclude_list: [metadata.user_api_key_team_id]\n# after (single mode)\nattributes:\n  include_list: [gen_ai.system, gen_ai.request.model]","handlingStrategy":"validation","validationCode":"attrs = config['otel'].get('attributes') or {}\ninclude, exclude = attrs.get('include_list'), attrs.get('exclude_list')\nif include and exclude:\n    raise SystemExit('otel.attributes: set only include_list OR exclude_list, not both')","typeGuard":"def has_single_filter_mode(attrs: dict) -> bool:\n    \"\"\"True when at most one of include_list/exclude_list is non-empty.\"\"\"\n    return bool(attrs.get('include_list')) != bool(attrs.get('exclude_list')) or not (\n        attrs.get('include_list') or attrs.get('exclude_list')\n    )","tryCatchPattern":"try:\n    otel_cb = OpenTelemetry(attributes=attrs)\nexcept ValueError as e:\n    if 'mutually exclusive' in str(e):\n        # drop the weaker policy and keep the stricter include_list\n        attrs.pop('exclude_list', None)\n        otel_cb = OpenTelemetry(attributes=attrs)\n    else:\n        raise","preventionTips":["Document the either/or policy next to the otel.attributes block in your config repo","Lint config merges (Helm overlay + base) for both keys appearing together","Decide cardinality strategy once (allowlist vs denylist) and standardize across environments","Remember lazy validation: proxy-populated callback_settings may only fail on the first metric, so smoke-test a metric emission after config changes"],"tags":["opentelemetry","otel","configuration","validation","metrics"],"backgroundTag":"mutually-exclusive-config-options","analyzedSha":"77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8","analyzedAt":"2026-08-18T11:44:31.656Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}