feat(logging): add component and logger fields to JSON logs for 3rd p… - #24447
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Greptile SummaryThis PR enhances
Confidence Score: 4/5
|
| Filename | Overview |
|---|---|
| litellm/_logging.py | Adds component (logger name) and logger (filename:lineno) fields to JsonFormatter output. The previous issue of user-supplied extra values being silently overwritten has been correctly addressed by placing the new assignments after the extra-attributes loop with if … not in json_record guards. Minor edge case: when pathname="" (used in json_excepthook) the logger field becomes ":0". |
| tests/test_litellm/test_logging.py | Three new unit tests cover the happy-path for both new fields and the not-overwritten guarantee for component. Tests are self-contained and mock-only (no network calls). Asymmetry: no corresponding not-overwritten test for the logger field, leaving a gap that a future regression could slip through undetected. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A["logger.info(msg, extra={...})"] --> B["JsonFormatter.format(record)"]
B --> C["Build base json_record\n{message, level, timestamp}"]
C --> D["Parse embedded JSON/dict in message\n→ merge into json_record if key absent"]
D --> E["Iterate record.__dict__\nfor non-standard attrs\n→ add to json_record if key absent"]
E --> F{"'component' in json_record?"}
F -- No --> G["json_record['component'] = record.name\ne.g. 'LiteLLM Proxy'"]
F -- Yes --> H["Keep user-supplied value"]
G --> I{"'logger' in json_record?"}
H --> I
I -- No --> J["json_record['logger'] = filename:lineno\ne.g. 'proxy_server.py:412'"]
I -- Yes --> K["Keep user-supplied value"]
J --> L["Append stacktrace if exc_info present"]
K --> L
L --> M["safe_dumps(json_record) → JSON string"]
Reviews (2): Last reviewed commit: "Let user-supplied extra fields win over ..." | Re-trigger Greptile
| assert obj["logger"].endswith(":123"), ( | ||
| f"Expected logger field to end with ':123', got {obj['logger']!r}" | ||
| ) |
There was a problem hiding this comment.
Weak assertion on
logger field value
The test only verifies that the logger field ends with :123, but does not validate the filename portion. Since record.filename is derived from pathname as os.path.basename(pathname), the full expected value is predictable (proxy_server.py:123). Using an exact equality check would catch regressions where the filename is missing or the format changes.
| assert obj["logger"].endswith(":123"), ( | |
| f"Expected logger field to end with ':123', got {obj['logger']!r}" | |
| ) | |
| assert obj["logger"] == "proxy_server.py:123", ( | |
| f"Expected logger='proxy_server.py:123', got {obj['logger']!r}" | |
| ) |
| "component": record.name, | ||
| "logger": f"{record.filename}:{record.lineno}", |
There was a problem hiding this comment.
component and logger silently shadow user-provided extra fields
The new keys component and logger are now written into json_record before the extra attributes loop runs. Because the loop skips keys already present (if key not in json_record), any caller that passes extra={"component": "my-service"} or extra={"logger": "custom"} will have their value silently dropped in favour of the auto-generated ones.
This is a subtle, hard-to-debug data-loss issue for users who are already using these field names for their own routing metadata:
verbose_proxy_logger.info("event", extra={"component": "auth-service"})
# JSON output will show component="LiteLLM Proxy" — the user's value is lostConsider one of the following mitigations:
- Allow
extrafields to win (move the new fields after theextraloop, and only set them if they are not already present). - Or document these as reserved keys and raise a warning when they are overridden.
The simplest safe fix is to apply the same guard already used for embedded-JSON keys:
# After the extra-attributes loop, set component/logger only if not already set by extra
if "component" not in json_record:
json_record["component"] = record.name
if "logger" not in json_record:
json_record["logger"] = f"{record.filename}:{record.lineno}"…er, tighten test assertions
| def test_json_formatter_extra_component_not_overwritten(): | ||
| """ | ||
| User-supplied extra={"component": "..."} must not be silently dropped. | ||
| """ | ||
| formatter = JsonFormatter() | ||
| record = logging.LogRecord( | ||
| name="LiteLLM Proxy", | ||
| level=logging.INFO, | ||
| pathname="proxy_server.py", | ||
| lineno=1, | ||
| msg="event", | ||
| args=(), | ||
| exc_info=None, | ||
| ) | ||
| record.component = "auth-service" | ||
| obj = json.loads(formatter.format(record)) | ||
| assert obj["component"] == "auth-service", ( | ||
| f"User-supplied component was overwritten, got {obj['component']!r}" | ||
| ) |
There was a problem hiding this comment.
Missing symmetric test for
logger not being overwritten
There is a test verifying that a user-supplied component in extra is not silently dropped (test_json_formatter_extra_component_not_overwritten), but no equivalent test for the logger field. Both fields are guarded by the same if … not in json_record check, so the gap in test coverage means a future refactor that changes the ordering of one guard but not the other would go undetected.
Add a counterpart test to close this gap:
def test_json_formatter_extra_logger_not_overwritten():
"""User-supplied extra={"logger": "..."} must not be silently dropped."""
formatter = JsonFormatter()
record = logging.LogRecord(
name="LiteLLM Proxy",
level=logging.INFO,
pathname="proxy_server.py",
lineno=1,
msg="event",
args=(),
exc_info=None,
)
record.logger = "custom-logger-value"
obj = json.loads(formatter.format(record))
assert obj["logger"] == "custom-logger-value", (
f"User-supplied logger was overwritten, got {obj['logger']!r}"
)| if "logger" not in json_record: | ||
| json_record["logger"] = f"{record.filename}:{record.lineno}" |
There was a problem hiding this comment.
Empty
pathname yields uninformative ":0" for the logger field
The json_excepthook created in _setup_json_exception_handlers constructs a LogRecord with pathname="" and lineno=0:
record = logging.LogRecord(
name="LiteLLM",
level=logging.ERROR,
pathname="", # <-- empty
lineno=0, # <-- zero
...
)record.filename is os.path.basename("") which evaluates to "", so the resulting logger field is ":0" — not particularly useful for debugging.
Consider guarding against this edge case:
| if "logger" not in json_record: | |
| json_record["logger"] = f"{record.filename}:{record.lineno}" | |
| if "logger" not in json_record: | |
| if record.filename and record.lineno: | |
| json_record["logger"] = f"{record.filename}:{record.lineno}" |
183a578
into
BerriAI:litellm_oss_staging_04_02_2026_p1
#24447) * feat(logging): add component and logger fields to JSON logs for 3rd party filtering * Let user-supplied extra fields win over auto-generated component/logger, tighten test assertions
* fix(vertex_ai): support pluggable (executable) credential_source for WIF auth (#24700) The WIF credential dispatch in load_auth() only handled identity_pool and aws credential types. When credential_source.executable was present (used for Azure Managed Identity via Workload Identity Federation), it fell through to identity_pool.Credentials which rejected it with MalformedError. Add dispatch to google.auth.pluggable.Credentials for executable-type credential sources, following the same pattern as the existing identity_pool and aws helpers. Fixes authentication for Azure Container Apps → GCP Vertex AI via WIF with executable credential sources. * feat(logging): add component and logger fields to JSON logs for 3rd p… (#24447) * feat(logging): add component and logger fields to JSON logs for 3rd party filtering * Let user-supplied extra fields win over auto-generated component/logger, tighten test assertions * Feat - Add organization into the metrics metadata for org_id & org_alias (#24440) * Add org_id and org_alias label names to Prometheus metric definitions * Add user_api_key_org_alias to StandardLoggingUserAPIKeyMetadata * Populate user_api_key_org_alias in pre-call metadata * Pass org_id and org_alias into per-request Prometheus metric labels * Add test for org labels on per-request Prometheus metrics * chore: resolve test mockdata * Address review: populate org_alias from DB view, add feature flag, use .get() for org metadata * Add org labels to failure path and verify flag behavior in test * Fix test: build flag-off enum_values without org fields * Gate org labels behind feature flag in get_labels() instead of static metric lists * Scope org label injection to metrics that carry team context, remove orphaned budget label defs, add test teardown * Use explicit metric allowlist for org label injection instead of team heuristic * Fix duplicate org label guard, move _org_label_metrics to class constant * Reset custom_prometheus_metadata_labels after duplicate label assertion * fix: emit org labels by default, remove flag, fix missing org_alias in all metadata paths * fix: emit org labels by default, no opt-in flag required * fix: write org_alias to metadata unconditionally in proxy_server.py * fix: 429s from batch creation being converted to 500 (#24703) * add us gov models (#24660) * add us gov models * added max tokens * Litellm dev 04 02 2026 p1 (#25052) * fix: replace hardcoded url * fix: Anthropic web search cost not tracked for Chat Completions The ModelResponse branch in response_object_includes_web_search_call() only checked url_citation annotations and prompt_tokens_details, missing Anthropic's server_tool_use.web_search_requests field. This caused _handle_web_search_cost() to never fire for Anthropic Claude models. Also routes vertex_ai/claude-* models to the Anthropic cost calculator instead of the Gemini one, since Claude on Vertex uses the same server_tool_use billing structure as the direct Anthropic API. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * fix(anthropic): pass logging_obj to client.post for litellm_overhead_time_ms (#24071) When LITELLM_DETAILED_TIMING=true, litellm_overhead_time_ms was null for Anthropic because the handler did not pass logging_obj to client.post(), so track_llm_api_timing could not set llm_api_duration_ms. Pass logging_obj=logging_obj at all four post() call sites (make_call, make_sync_call, acompletion, completion). Add test to ensure make_call passes logging_obj to client.post. Made-with: Cursor * sap - add additional parameters for grounding - additional parameter for grounding added for the sap provider * sap - fix models * (sap) add filtering, masking, translation SAP GEN AI Hub modules * (sap) add tests and docs for new SAP modules * (sap) add support of multiple modules config * (sap) code refactoring * (sap) rename file * test(): add safeguard tests * (sap) update tests * (sap) update docs, solve merge conflict in transformation.py * (sap) linter fix * (sap) Align embedding request transformation with current API * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) mock commit * (sap) run black formater * (sap) add literals to models, add negative tests, fix test for tool transformation * (sap) fix formating * (sap) fix models * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) commit for rerun bot review * (sap) minor improve * (sap) fix after bot review * (sap) lint fix * docs(sap): update documentation * fix(sap): change creds priority * fix(sap): change creds priority * fix(sap): fix sap creds unit test * fix(sap): linter fix * fix(sap): linter fix * linter fix * (sap) update logic of fetching creds, add additional tests * (sap) clean up code * (sap) fix after review * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) add a possibility to put the service key by both variants * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) update test * (sap) update service key resolve function * (sap) run black formater * (sap) fix validate credentials, add negative tests for credential fetching * (sap) fix validate credentials, add negative tests for credential fetching * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) lint fix * (sap) lint fix * feat: support service_tier in gemini * chore: add a service_tier field mapping from openai to gemini * fix: use x-gemini-service-tier header in response * docs: add service_tier to gemini docs * chore: add defaut/standard mapping, and some tests * chore: tidying up some case insensitivity * chore: remove unnecessary guard * fix: remove redundant test file * fix: handle 'auto' case-insensitively * fix: return service_tier on final steamed chunk * chore: black * feat: enable supports_service_tier to gemini models * Fix get_standard_logging_metadata tests * Fix test_get_model_info_bedrock_models * Fix test_get_model_info_bedrock_models * Fix remaining tests * Fix mypy issues * Fix tests * Fix merge conflicts * Fix code qa * Fix code qa * Fix code qa * Fix greptile review --------- Co-authored-by: michelligabriele <gabriele.michelli@icloud.com> Co-authored-by: Josh <36064836+J-Byron@users.noreply.github.com> Co-authored-by: mubashir1osmani <mubashir.osmani777@gmail.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: milan-berri <milan@berri.ai> Co-authored-by: Alperen Kömürcü <alperen.koemuercue@sap.com> Co-authored-by: Vasilisa Parshikova <vasilisa.parshikova@sap.com> Co-authored-by: Lin Xu <lin.xu03@sap.com> Co-authored-by: Mark McDonald <macd@google.com> Co-authored-by: Sameer Kankute <sameer@berri.ai>
* fix(vertex_ai): support pluggable (executable) credential_source for WIF auth (BerriAI#24700) The WIF credential dispatch in load_auth() only handled identity_pool and aws credential types. When credential_source.executable was present (used for Azure Managed Identity via Workload Identity Federation), it fell through to identity_pool.Credentials which rejected it with MalformedError. Add dispatch to google.auth.pluggable.Credentials for executable-type credential sources, following the same pattern as the existing identity_pool and aws helpers. Fixes authentication for Azure Container Apps → GCP Vertex AI via WIF with executable credential sources. * feat(logging): add component and logger fields to JSON logs for 3rd p… (BerriAI#24447) * feat(logging): add component and logger fields to JSON logs for 3rd party filtering * Let user-supplied extra fields win over auto-generated component/logger, tighten test assertions * Feat - Add organization into the metrics metadata for org_id & org_alias (BerriAI#24440) * Add org_id and org_alias label names to Prometheus metric definitions * Add user_api_key_org_alias to StandardLoggingUserAPIKeyMetadata * Populate user_api_key_org_alias in pre-call metadata * Pass org_id and org_alias into per-request Prometheus metric labels * Add test for org labels on per-request Prometheus metrics * chore: resolve test mockdata * Address review: populate org_alias from DB view, add feature flag, use .get() for org metadata * Add org labels to failure path and verify flag behavior in test * Fix test: build flag-off enum_values without org fields * Gate org labels behind feature flag in get_labels() instead of static metric lists * Scope org label injection to metrics that carry team context, remove orphaned budget label defs, add test teardown * Use explicit metric allowlist for org label injection instead of team heuristic * Fix duplicate org label guard, move _org_label_metrics to class constant * Reset custom_prometheus_metadata_labels after duplicate label assertion * fix: emit org labels by default, remove flag, fix missing org_alias in all metadata paths * fix: emit org labels by default, no opt-in flag required * fix: write org_alias to metadata unconditionally in proxy_server.py * fix: 429s from batch creation being converted to 500 (BerriAI#24703) * add us gov models (BerriAI#24660) * add us gov models * added max tokens * Litellm dev 04 02 2026 p1 (BerriAI#25052) * fix: replace hardcoded url * fix: Anthropic web search cost not tracked for Chat Completions The ModelResponse branch in response_object_includes_web_search_call() only checked url_citation annotations and prompt_tokens_details, missing Anthropic's server_tool_use.web_search_requests field. This caused _handle_web_search_cost() to never fire for Anthropic Claude models. Also routes vertex_ai/claude-* models to the Anthropic cost calculator instead of the Gemini one, since Claude on Vertex uses the same server_tool_use billing structure as the direct Anthropic API. --------- * fix(anthropic): pass logging_obj to client.post for litellm_overhead_time_ms (BerriAI#24071) When LITELLM_DETAILED_TIMING=true, litellm_overhead_time_ms was null for Anthropic because the handler did not pass logging_obj to client.post(), so track_llm_api_timing could not set llm_api_duration_ms. Pass logging_obj=logging_obj at all four post() call sites (make_call, make_sync_call, acompletion, completion). Add test to ensure make_call passes logging_obj to client.post. Made-with: Cursor * sap - add additional parameters for grounding - additional parameter for grounding added for the sap provider * sap - fix models * (sap) add filtering, masking, translation SAP GEN AI Hub modules * (sap) add tests and docs for new SAP modules * (sap) add support of multiple modules config * (sap) code refactoring * (sap) rename file * test(): add safeguard tests * (sap) update tests * (sap) update docs, solve merge conflict in transformation.py * (sap) linter fix * (sap) Align embedding request transformation with current API * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) mock commit * (sap) run black formater * (sap) add literals to models, add negative tests, fix test for tool transformation * (sap) fix formating * (sap) fix models * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) commit for rerun bot review * (sap) minor improve * (sap) fix after bot review * (sap) lint fix * docs(sap): update documentation * fix(sap): change creds priority * fix(sap): change creds priority * fix(sap): fix sap creds unit test * fix(sap): linter fix * fix(sap): linter fix * linter fix * (sap) update logic of fetching creds, add additional tests * (sap) clean up code * (sap) fix after review * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) add a possibility to put the service key by both variants * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) update test * (sap) update service key resolve function * (sap) run black formater * (sap) fix validate credentials, add negative tests for credential fetching * (sap) fix validate credentials, add negative tests for credential fetching * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) fix after bot review * (sap) lint fix * (sap) lint fix * feat: support service_tier in gemini * chore: add a service_tier field mapping from openai to gemini * fix: use x-gemini-service-tier header in response * docs: add service_tier to gemini docs * chore: add defaut/standard mapping, and some tests * chore: tidying up some case insensitivity * chore: remove unnecessary guard * fix: remove redundant test file * fix: handle 'auto' case-insensitively * fix: return service_tier on final steamed chunk * chore: black * feat: enable supports_service_tier to gemini models * Fix get_standard_logging_metadata tests * Fix test_get_model_info_bedrock_models * Fix test_get_model_info_bedrock_models * Fix remaining tests * Fix mypy issues * Fix tests * Fix merge conflicts * Fix code qa * Fix code qa * Fix code qa * Fix greptile review --------- Co-authored-by: michelligabriele <gabriele.michelli@icloud.com> Co-authored-by: Josh <36064836+J-Byron@users.noreply.github.com> Co-authored-by: mubashir1osmani <mubashir.osmani777@gmail.com> Co-authored-by: milan-berri <milan@berri.ai> Co-authored-by: Alperen Kömürcü <alperen.koemuercue@sap.com> Co-authored-by: Vasilisa Parshikova <vasilisa.parshikova@sap.com> Co-authored-by: Lin Xu <lin.xu03@sap.com> Co-authored-by: Mark McDonald <macd@google.com> Co-authored-by: Sameer Kankute <sameer@berri.ai>

Relevant issues
Resolves #23884
Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
tests/test_litellm/directory, Adding at least 1 test is a hard requirement - see detailsmake test-unit@greptileaiand received a Confidence Score of at least 4/5 before requesting a maintainer reviewDelays in PR merge?
If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).
CI (LiteLLM team)
Branch creation CI run
Link:
CI run for the last commit
Link:
Merge / cherry-pick CI run
Links:
Type
🆕 New Feature
Changes
Add
componentandloggerfields to structured JSON logsAdds two fields to the
JsonFormatteroutput:component: the logger name (e.g.LiteLLM Proxy,LiteLLM Router)logger: source file and line number (e.g.proxy_server.py:412)Makes it easier to filter and route logs by component in third-party
observability tools (Datadog, Grafana, CloudWatch) without parsing the
message string.