mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-06 02:48:13 +00:00
feat(otel): register SigNoz across the remaining callback surfaces
Walking every file that mentions an existing preset turned up registration points the first pass missed, each failing somewhere different: the callback was absent from the logger registry, from the `AllCallbacks` list behind `/get/config/callbacks`, and from the `/health/services` service list, so the Admin UI's Test button returned a 400. The proxy tile also offered only an ingestion key, leaving no way to say where traces should go; the endpoint is now a team/key param alongside it, as every other logging tile has one. Left blank it keeps the proxy's configured endpoint. `signoz_*` callback vars are scoped to the `signoz` callback, matching the existing per-vendor guard: those vars reach the tracer through the trusted overlay with no callback-name check, so a team that saved them under a different callback never asked for SigNoz and must not export to it. Adds the callback to the dashboard's own callback list too, which is what the per-team logging settings read.
This commit is contained in:
parent
f5e2d68e45
commit
ee6a6d8f68
13 changed files with 97 additions and 10 deletions
|
|
@ -410,14 +410,20 @@
|
|||
"logo": "signoz.svg",
|
||||
"supports_key_team_logging": true,
|
||||
"dynamic_params": {
|
||||
"signoz_ingestion_endpoint": {
|
||||
"type": "text",
|
||||
"ui_name": "SigNoz Ingestion Endpoint",
|
||||
"description": "Ingestion endpoint for this team, e.g. https://ingest.us.signoz.cloud:443 for SigNoz Cloud or your own collector. Leave blank to use the proxy's configured endpoint. Regions: https://signoz.io/docs/ingestion/signoz-cloud/overview/",
|
||||
"required": false
|
||||
},
|
||||
"signoz_ingestion_key": {
|
||||
"type": "password",
|
||||
"ui_name": "SigNoz Ingestion Key",
|
||||
"description": "Per-team ingestion key. Team traces export to this key's SigNoz account over OTLP.",
|
||||
"ui_name": "SigNoz Ingestion Key (optional)",
|
||||
"description": "Ingestion key for this team, so its traces land in its own SigNoz account. Not needed for self-hosted SigNoz. Keys: https://signoz.io/docs/ingestion/signoz-cloud/keys/",
|
||||
"required": false
|
||||
}
|
||||
},
|
||||
"description": "SigNoz OpenTelemetry Observability Integration"
|
||||
"description": "SigNoz Logging Integration. Setup: https://signoz.io/docs/litellm-observability/"
|
||||
},
|
||||
{
|
||||
"id": "sqs",
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ from litellm.integrations.otel.presets.phoenix import (
|
|||
phoenix_project_headers,
|
||||
)
|
||||
from litellm.integrations.otel.presets.signoz import (
|
||||
signoz_dynamic_endpoint,
|
||||
signoz_dynamic_headers,
|
||||
signoz_preset,
|
||||
)
|
||||
|
|
@ -77,6 +78,7 @@ DYNAMIC_ENDPOINT_BY_CALLBACK: Final[Mapping[str, Callable[[StandardCallbackDynam
|
|||
MappingProxyType(
|
||||
{
|
||||
"newrelic": newrelic_dynamic_endpoint,
|
||||
"signoz": signoz_dynamic_endpoint,
|
||||
}
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -51,6 +51,15 @@ def signoz_preset(
|
|||
)
|
||||
|
||||
|
||||
def signoz_dynamic_endpoint(params: StandardCallbackDynamicParams) -> str | None:
|
||||
"""Per-request SigNoz endpoint from team/key dynamic params.
|
||||
|
||||
``None`` keeps the operator's endpoint, so a team that saved only a key
|
||||
still exports to the configured destination.
|
||||
"""
|
||||
return params.get("signoz_ingestion_endpoint")
|
||||
|
||||
|
||||
def signoz_dynamic_headers(params: StandardCallbackDynamicParams) -> dict[str, str]: # mutable-ok: registry type
|
||||
"""Per-request SigNoz OTLP headers from team/key dynamic params."""
|
||||
key: Final = params.get("signoz_ingestion_key")
|
||||
|
|
|
|||
|
|
@ -87,6 +87,7 @@ class CustomLoggerRegistry:
|
|||
"langtrace": OpenTelemetry,
|
||||
"weave_otel": OpenTelemetry,
|
||||
"levo": OpenTelemetry,
|
||||
"signoz": OpenTelemetry,
|
||||
"mlflow": MlflowLogger,
|
||||
"langfuse": LangfusePromptManagement,
|
||||
"otel": OpenTelemetry,
|
||||
|
|
|
|||
|
|
@ -74,6 +74,7 @@ _supported_callback_params: Final[tuple[str, ...]] = (
|
|||
"dd_agent_port",
|
||||
"newrelic_api_key",
|
||||
"newrelic_region",
|
||||
"signoz_ingestion_endpoint",
|
||||
"signoz_ingestion_key",
|
||||
"turn_off_message_logging",
|
||||
)
|
||||
|
|
@ -88,6 +89,7 @@ _request_blocked_callback_params: Final = frozenset(
|
|||
"dd_agent_port",
|
||||
"newrelic_api_key",
|
||||
"newrelic_region",
|
||||
"signoz_ingestion_endpoint",
|
||||
"signoz_ingestion_key",
|
||||
}
|
||||
)
|
||||
|
|
@ -101,6 +103,7 @@ _trusted_overlay_callback_params: Final = frozenset(
|
|||
{
|
||||
"newrelic_api_key",
|
||||
"newrelic_region",
|
||||
"signoz_ingestion_endpoint",
|
||||
"signoz_ingestion_key",
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -3500,6 +3500,15 @@ class AllCallbacks(LiteLLMPydanticObjectBase):
|
|||
],
|
||||
)
|
||||
|
||||
signoz: CallbackOnUI = CallbackOnUI(
|
||||
litellm_callback_name="signoz",
|
||||
ui_callback_name="SigNoz",
|
||||
litellm_callback_params=[
|
||||
"SIGNOZ_INGESTION_ENDPOINT",
|
||||
"SIGNOZ_INGESTION_KEY",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
class SpendLogsMetadata(TypedDict):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -174,6 +174,7 @@ services = (
|
|||
"arize",
|
||||
"galileo",
|
||||
"newrelic",
|
||||
"signoz",
|
||||
"sqs",
|
||||
]
|
||||
| str
|
||||
|
|
@ -251,6 +252,7 @@ async def health_services_endpoint(
|
|||
"arize",
|
||||
"galileo",
|
||||
"newrelic",
|
||||
"signoz",
|
||||
"sqs",
|
||||
]:
|
||||
raise HTTPException(
|
||||
|
|
|
|||
|
|
@ -760,6 +760,8 @@ def convert_key_logging_metadata_to_callback(
|
|||
# must not export to it.
|
||||
if var.startswith("newrelic_") and data.callback_name != "newrelic":
|
||||
continue
|
||||
if var.startswith("signoz_") and data.callback_name != "signoz":
|
||||
continue
|
||||
if team_callback_settings_obj.callback_vars is None:
|
||||
team_callback_settings_obj.callback_vars = {}
|
||||
team_callback_settings_obj.callback_vars[var] = str(value)
|
||||
|
|
|
|||
|
|
@ -3307,6 +3307,7 @@ class StandardCallbackDynamicParams(TypedDict, total=False):
|
|||
|
||||
# SigNoz dynamic params (proxy-stamped team/key callback vars only;
|
||||
# request-supplied values are blocked)
|
||||
signoz_ingestion_endpoint: str | None # writable-ok: initialize_standard_callback_dynamic_params assigns into the dict
|
||||
signoz_ingestion_key: str | None # writable-ok: initialize_standard_callback_dynamic_params assigns into the dict
|
||||
|
||||
# Logging settings
|
||||
|
|
|
|||
|
|
@ -470,12 +470,13 @@ def test_signoz_dynamic_headers_stamp_ingestion_key():
|
|||
assert dynamic_otlp_headers("signoz", {}) is None
|
||||
|
||||
|
||||
def test_signoz_registers_no_dynamic_endpoint_resolver():
|
||||
# A team supplies a key, never a destination, so the operator's endpoint wins.
|
||||
from litellm.integrations.otel.presets import (
|
||||
DYNAMIC_ENDPOINT_BY_CALLBACK,
|
||||
dynamic_otlp_endpoint,
|
||||
)
|
||||
def test_signoz_dynamic_endpoint_comes_from_team_config():
|
||||
from litellm.integrations.otel.presets import dynamic_otlp_endpoint
|
||||
|
||||
assert "signoz" not in DYNAMIC_ENDPOINT_BY_CALLBACK
|
||||
assert (
|
||||
dynamic_otlp_endpoint("signoz", {"signoz_ingestion_endpoint": "https://ingest.eu.signoz.cloud:443"})
|
||||
== "https://ingest.eu.signoz.cloud:443"
|
||||
)
|
||||
# A team that saved only a key keeps the operator's configured endpoint.
|
||||
assert dynamic_otlp_endpoint("signoz", {"signoz_ingestion_key": "k"}) is None
|
||||
assert dynamic_otlp_endpoint("signoz", {}) is None
|
||||
|
|
|
|||
|
|
@ -5343,6 +5343,14 @@ def test_signoz_dispatch_keeps_legacy_otel_when_flag_off(monkeypatch):
|
|||
assert legacy.config.endpoint == "http://signoz-collector.internal:4318/v1/traces"
|
||||
assert legacy.config.headers == "signoz-ingestion-key=legacy-key"
|
||||
assert "OTEL_EXPORTER_OTLP_TRACES_HEADERS" not in os.environ
|
||||
# Same name resolves to the same instance, not a second exporter.
|
||||
again = logging_module._init_custom_logger_compatible_class(
|
||||
logging_integration="signoz",
|
||||
internal_usage_cache=None,
|
||||
llm_router=None,
|
||||
custom_logger_init_args={},
|
||||
)
|
||||
assert again is legacy
|
||||
finally:
|
||||
logging_module._in_memory_loggers.clear()
|
||||
is_otel_v2_enabled.cache_clear()
|
||||
|
|
|
|||
|
|
@ -7418,3 +7418,34 @@ def test_newrelic_vars_scoped_to_newrelic_callback_entry():
|
|||
None,
|
||||
)
|
||||
assert legit.callback_vars == {"newrelic_api_key": "REAL", "newrelic_region": "us"}
|
||||
|
||||
|
||||
def test_signoz_callback_vars_are_scoped_to_the_signoz_callback():
|
||||
"""``signoz_*`` vars reach the tracer through the trusted overlay with no
|
||||
callback-name check, so a team that saved them under a different callback
|
||||
never asked for SigNoz and must not export to it."""
|
||||
from litellm.proxy._types import AddTeamCallback
|
||||
from litellm.proxy.litellm_pre_call_utils import convert_key_logging_metadata_to_callback
|
||||
|
||||
under_signoz = convert_key_logging_metadata_to_callback(
|
||||
data=AddTeamCallback(
|
||||
callback_name="signoz",
|
||||
callback_type="success",
|
||||
callback_vars={"signoz_ingestion_key": "team-key", "signoz_ingestion_endpoint": "https://ingest.eu.signoz.cloud:443"},
|
||||
),
|
||||
team_callback_settings_obj=None,
|
||||
)
|
||||
assert under_signoz.callback_vars == {
|
||||
"signoz_ingestion_key": "team-key",
|
||||
"signoz_ingestion_endpoint": "https://ingest.eu.signoz.cloud:443",
|
||||
}
|
||||
|
||||
under_other = convert_key_logging_metadata_to_callback(
|
||||
data=AddTeamCallback(
|
||||
callback_name="langfuse",
|
||||
callback_type="success",
|
||||
callback_vars={"signoz_ingestion_key": "team-key", "langfuse_host": "https://cloud.langfuse.com"},
|
||||
),
|
||||
team_callback_settings_obj=None,
|
||||
)
|
||||
assert under_other.callback_vars == {"langfuse_host": "https://cloud.langfuse.com"}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import langsmithLogo from "../../public/assets/logos/langsmith.png";
|
|||
import newrelicLogo from "../../public/assets/logos/newrelic.png";
|
||||
import openmeterLogo from "../../public/assets/logos/openmeter.png";
|
||||
import otelLogo from "../../public/assets/logos/otel.png";
|
||||
import signozLogo from "../../public/assets/logos/signoz.svg";
|
||||
|
||||
interface CallbackConfig {
|
||||
id: string;
|
||||
|
|
@ -172,6 +173,17 @@ export const CALLBACK_CONFIGS: CallbackConfig[] = [
|
|||
},
|
||||
description: "S3 Bucket (AWS) Logging Integration",
|
||||
},
|
||||
{
|
||||
id: "signoz",
|
||||
displayName: "SigNoz",
|
||||
logo: signozLogo.src,
|
||||
supports_key_team_logging: true,
|
||||
dynamic_params: {
|
||||
signoz_ingestion_endpoint: "text",
|
||||
signoz_ingestion_key: "password",
|
||||
},
|
||||
description: "SigNoz Logging Integration. Setup: https://signoz.io/docs/litellm-observability/",
|
||||
},
|
||||
{
|
||||
id: "SQS",
|
||||
displayName: "SQS",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue