diff --git a/python/sglang/multimodal_gen/runtime/server_args/server_args.py b/python/sglang/multimodal_gen/runtime/server_args/server_args.py index e1cb364a7..f54f96a77 100644 --- a/python/sglang/multimodal_gen/runtime/server_args/server_args.py +++ b/python/sglang/multimodal_gen/runtime/server_args/server_args.py @@ -589,6 +589,7 @@ class ServerArgs(DisaggServerArgsMixin): # Tracing enable_trace: bool = False otlp_traces_endpoint: str = "localhost:4317" + otlp_service_name: str | None = None # SGLang backend for encoder stage srt_encoder_url: str | None = None @@ -2893,6 +2894,13 @@ class ServerArgs(DisaggServerArgsMixin): default=ServerArgs.otlp_traces_endpoint, help="OTLP collector endpoint when --enable-trace is set. Format: :", ) + parser.add_argument( + "--otlp-service-name", + type=str, + default=ServerArgs.otlp_service_name, + help="Service name for OTLP traces (displayed as 'service.name' in trace backends). " + "If unset, falls back to the OTEL_SERVICE_NAME env var, then to 'sglang-diffusion'.", + ) parser.add_argument( "--log-requests", action="store_true", diff --git a/python/sglang/multimodal_gen/runtime/utils/trace_wrapper.py b/python/sglang/multimodal_gen/runtime/utils/trace_wrapper.py index 6dc5c987a..9f8f6e4c7 100644 --- a/python/sglang/multimodal_gen/runtime/utils/trace_wrapper.py +++ b/python/sglang/multimodal_gen/runtime/utils/trace_wrapper.py @@ -7,6 +7,7 @@ start/end bookkeeping. from __future__ import annotations +import os from contextlib import contextmanager from dataclasses import dataclass @@ -37,10 +38,17 @@ def init_diffusion_tracing(server_args, thread_label: str): trace_set_thread_info, ) + # Priority: --otlp-service-name > OTEL_SERVICE_NAME > "sglang-diffusion" + service_name = ( + server_args.otlp_service_name + or os.getenv("OTEL_SERVICE_NAME") + or "sglang-diffusion" + ) + # srt owns TraceReqContext and filters spans through its trace_modules list process_tracing_init( server_args.otlp_traces_endpoint, - "sglang-diffusion", + service_name, trace_modules=DIFFUSION_TRACE_MODULE, ) trace_set_thread_info(thread_label) diff --git a/python/sglang/srt/arg_groups/fields/observability.py b/python/sglang/srt/arg_groups/fields/observability.py index 1e7289f1c..902bcc85a 100644 --- a/python/sglang/srt/arg_groups/fields/observability.py +++ b/python/sglang/srt/arg_groups/fields/observability.py @@ -177,6 +177,11 @@ class Observability(msgspec.Struct): str, "Config opentelemetry collector endpoint if --enable-trace is set. format: :", ] = "localhost:4317" + otlp_service_name: A[ + Optional[str], + "Service name for OTLP traces (displayed as 'service.name' in trace backends). " + "If unset, falls back to the OTEL_SERVICE_NAME env var, then to 'sglang'.", + ] = None # RequestMetricsExporter configuration export_metrics_to_file: A[ bool, diff --git a/python/sglang/srt/disaggregation/encoder/runtime.py b/python/sglang/srt/disaggregation/encoder/runtime.py index 572c5d67a..3fab75f43 100644 --- a/python/sglang/srt/disaggregation/encoder/runtime.py +++ b/python/sglang/srt/disaggregation/encoder/runtime.py @@ -1775,7 +1775,7 @@ def launch_local_runtime(server_args: ServerArgs) -> EncoderRuntime: if get_observability().enable_trace: process_tracing_init( get_observability().otlp_traces_endpoint, - "sglang", + get_observability().otlp_service_name, trace_modules=get_observability().trace_modules, ) trace_set_thread_info("Encoder") diff --git a/python/sglang/srt/entrypoints/engine.py b/python/sglang/srt/entrypoints/engine.py index 9afd8c480..361e240e6 100644 --- a/python/sglang/srt/entrypoints/engine.py +++ b/python/sglang/srt/entrypoints/engine.py @@ -329,7 +329,7 @@ class Engine(EngineScoreMixin, EngineBase): if get_observability().enable_trace: process_tracing_init( get_observability().otlp_traces_endpoint, - "sglang", + get_observability().otlp_service_name, trace_modules=get_observability().trace_modules, ) thread_label = "Tokenizer" diff --git a/python/sglang/srt/entrypoints/http_server.py b/python/sglang/srt/entrypoints/http_server.py index dd93d6545..a20ed6914 100644 --- a/python/sglang/srt/entrypoints/http_server.py +++ b/python/sglang/srt/entrypoints/http_server.py @@ -293,7 +293,7 @@ async def lifespan(fast_api_app: FastAPI): if get_observability().enable_trace: process_tracing_init( get_observability().otlp_traces_endpoint, - "sglang", + get_observability().otlp_service_name, trace_modules=get_observability().trace_modules, ) if get_disagg().disaggregation_mode == "prefill": diff --git a/python/sglang/srt/managers/data_parallel_controller.py b/python/sglang/srt/managers/data_parallel_controller.py index c4b078b90..d1f0fc55e 100644 --- a/python/sglang/srt/managers/data_parallel_controller.py +++ b/python/sglang/srt/managers/data_parallel_controller.py @@ -839,7 +839,7 @@ def run_data_parallel_controller_process( if get_observability().enable_trace: process_tracing_init( get_observability().otlp_traces_endpoint, - "sglang", + get_observability().otlp_service_name, trace_modules=get_observability().trace_modules, ) thread_label = "DP Controller" diff --git a/python/sglang/srt/managers/scheduler.py b/python/sglang/srt/managers/scheduler.py index b06d1829f..442eba64d 100644 --- a/python/sglang/srt/managers/scheduler.py +++ b/python/sglang/srt/managers/scheduler.py @@ -5852,7 +5852,7 @@ def run_scheduler_process( if get_observability().enable_trace: process_tracing_init( get_observability().otlp_traces_endpoint, - "sglang", + get_observability().otlp_service_name, trace_modules=get_observability().trace_modules, ) thread_label = "Scheduler" diff --git a/python/sglang/srt/observability/trace.py b/python/sglang/srt/observability/trace.py index 9fad00eaf..127be7f2b 100644 --- a/python/sglang/srt/observability/trace.py +++ b/python/sglang/srt/observability/trace.py @@ -227,9 +227,12 @@ def process_tracing_init( ) try: + # Priority: explicit server_name > OTEL_SERVICE_NAME > "sglang" + service_name = server_name or os.getenv("OTEL_SERVICE_NAME", "sglang") + resource = Resource.create( attributes={ - SERVICE_NAME: server_name, + SERVICE_NAME: service_name, } ) tracer_provider = TracerProvider( @@ -259,7 +262,7 @@ def process_tracing_init( if envs.SGLANG_TRACE_ASYNC.get(): from sglang.srt.observability.trace_async import start_trace_exporter - start_trace_exporter(otlp_endpoint, server_name, trace_modules=trace_modules) + start_trace_exporter(otlp_endpoint, service_name, trace_modules=trace_modules) def get_global_tracing_enabled(): diff --git a/test/registered/unit/observability/test_trace.py b/test/registered/unit/observability/test_trace.py index f1107919b..3c4cfffa1 100644 --- a/test/registered/unit/observability/test_trace.py +++ b/test/registered/unit/observability/test_trace.py @@ -175,6 +175,48 @@ class TestProcessTracingInit(unittest.TestCase): finally: mod.opentelemetry_imported = orig + @unittest.skipIf(not _has_otel, "OpenTelemetry not installed") + def test_service_name_priority_explicit(self): + """Service name priority: explicit parameter > env var > default.""" + with patch.dict(os.environ, {"OTEL_SERVICE_NAME": "from-env"}): + with patch.object(mod, "TracerProvider"): + with patch.object(mod, "Resource") as mock_resource: + process_tracing_init("localhost:4317", "explicit-name") + # Verify Resource.create was called with the explicit name + mock_resource.create.assert_called_once() + call_kwargs = mock_resource.create.call_args[1] + self.assertEqual( + call_kwargs["attributes"][mod.SERVICE_NAME], "explicit-name" + ) + + @unittest.skipIf(not _has_otel, "OpenTelemetry not installed") + def test_service_name_priority_env_var(self): + """Service name falls back to OTEL_SERVICE_NAME when explicit is None.""" + with patch.dict(os.environ, {"OTEL_SERVICE_NAME": "from-env"}): + with patch.object(mod, "TracerProvider"): + with patch.object(mod, "Resource") as mock_resource: + process_tracing_init("localhost:4317", None) + # Verify Resource.create was called with env var value + mock_resource.create.assert_called_once() + call_kwargs = mock_resource.create.call_args[1] + self.assertEqual( + call_kwargs["attributes"][mod.SERVICE_NAME], "from-env" + ) + + @unittest.skipIf(not _has_otel, "OpenTelemetry not installed") + def test_service_name_priority_default(self): + """Service name falls back to 'sglang' when both explicit and env are unset.""" + with patch.dict(os.environ, {}, clear=True): + with patch.object(mod, "TracerProvider"): + with patch.object(mod, "Resource") as mock_resource: + process_tracing_init("localhost:4317", None) + # Verify Resource.create was called with default value + mock_resource.create.assert_called_once() + call_kwargs = mock_resource.create.call_args[1] + self.assertEqual( + call_kwargs["attributes"][mod.SERVICE_NAME], "sglang" + ) + class TestTraceReqContextDisabled(unittest.TestCase): def setUp(self):