@@ -33,7 +33,8 @@ const OTEL_EXPORTER_OTLP_PROTOCOL: &str = "OTEL_EXPORTER_OTLP_PROTOCOL";
3333/// Initialise an OTLP tracer provider.
3434///
3535/// **Required env var:**
36- /// - \`OTEL_EXPORTER_OTLP_ENDPOINT\` — collector address.
36+ /// - \`OTEL_EXPORTER_OTLP_ENDPOINT\` — collector address, or the literal
37+ /// value \`stdout\` to write spans as JSON to process stdout (no collector).
3738/// For HTTP exporters the SDK appends the signal path automatically:
3839/// e.g. \`http://localhost:4318\` → \`http://localhost:4318/v1/traces\`.
3940/// Set a signal-specific var \`OTEL_EXPORTER_OTLP_TRACES_ENDPOINT\` to
@@ -44,59 +45,74 @@ const OTEL_EXPORTER_OTLP_PROTOCOL: &str = "OTEL_EXPORTER_OTLP_PROTOCOL";
4445/// - \`grpc\` → gRPC / tonic (Jaeger, Tempo, …)
4546/// - \`http/json\` → HTTP + JSON (Parseable OSS ingest at \`/v1/traces\`)
4647/// - \`http/protobuf\` → HTTP + protobuf
48+ /// Ignored when endpoint is \`stdout\`.
4749/// - \`OTEL_EXPORTER_OTLP_HEADERS\` — comma-separated \`key=value\` pairs forwarded
4850/// as gRPC metadata or HTTP headers, e.g.
4951/// \`authorization=Basic <token>,x-p-stream=my-stream,x-p-log-source=otel-traces\`
5052///
5153/// Returns \`None\` when \`OTEL_EXPORTER_OTLP_ENDPOINT\` or `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` is not set (OTEL disabled).
5254/// The caller must call \`provider.shutdown()\` before process exit.
5355pub fn init_tracing ( service : & ' static str ) -> Option < SdkTracerProvider > {
54- // Only used to decide whether OTEL is enabled; the SDK reads it again
55- // from env to build the exporter (which also appends /v1/traces for HTTP).
56- if std:: env:: var ( OTEL_EXPORTER_OTLP_ENDPOINT ) . is_err ( )
57- && std:: env:: var ( OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ) . is_err ( )
58- {
59- return None ;
60- }
56+ let endpoint = std:: env:: var ( OTEL_EXPORTER_OTLP_ENDPOINT )
57+ . or_else ( |_| std:: env:: var ( OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ) )
58+ . ok ( ) ?;
59+
60+ // Endpoint sentinel: route spans to stdout, skip OTLP exporter entirely.
61+ let use_stdout = endpoint. eq_ignore_ascii_case ( "stdout" ) ;
6162
6263 let protocol =
6364 std:: env:: var ( OTEL_EXPORTER_OTLP_PROTOCOL ) . unwrap_or_else ( |_| "http/json" . to_string ( ) ) ;
6465
65- // Build the exporter using the SDK's env-var-aware builders.
66+ // Build the exporter and wrap it in a BatchSpanProcessor. The stdout
67+ // exporter has a different concrete type, so we build the processor
68+ // inside each match arm to keep the types monomorphic.
69+ //
6670 // We intentionally do NOT call .with_endpoint() / .with_headers() /
67- // .with_metadata() here — the SDK reads OTEL_EXPORTER_OTLP_ENDPOINT and
68- // OTEL_EXPORTER_OTLP_HEADERS from the environment automatically, which
71+ // .with_metadata() on OTLP builders — the SDK reads OTEL_EXPORTER_OTLP_ENDPOINT
72+ // and OTEL_EXPORTER_OTLP_HEADERS from the environment automatically, which
6973 // preserves correct path-appending behaviour for HTTP exporters.
70- let exporter = match protocol. as_str ( ) {
71- // ── gRPC ─────────────────────────────────────────────────────────────
72- "grpc" => opentelemetry_otlp:: SpanExporter :: builder ( )
73- . with_tonic ( )
74- . build ( ) ,
75- // ── HTTP/Protobuf ────────────────────────────────────────────────────
76- "http/protobuf" => SpanExporter :: builder ( )
77- . with_http ( )
78- . with_protocol ( Protocol :: HttpBinary )
79- . build ( ) ,
80- // ── HTTP/JSON ─────────────────────────────────────────────────────────
81- "http/json" => SpanExporter :: builder ( )
82- . with_http ( )
83- . with_protocol ( Protocol :: HttpJson )
84- . build ( ) ,
85- // return none if an invalid value is set
86- other => {
87- tracing:: warn!(
88- "Unknown OTEL_EXPORTER_OTLP_PROTOCOL value '{}'; disabling OTEL tracing. \
89- Supported values: grpc, http/protobuf, http/json",
90- other
91- ) ;
92- return None ;
74+ let processor = if use_stdout {
75+ let exporter = opentelemetry_stdout:: SpanExporter :: default ( ) ;
76+ BatchSpanProcessor :: builder ( exporter) . build ( )
77+ } else {
78+ match protocol. as_str ( ) {
79+ "grpc" => {
80+ let exporter = opentelemetry_otlp:: SpanExporter :: builder ( )
81+ . with_tonic ( )
82+ . build ( )
83+ . map_err ( |e| tracing:: warn!( "Failed to build OTEL span exporter: {}" , e) )
84+ . ok ( ) ?;
85+ BatchSpanProcessor :: builder ( exporter) . build ( )
86+ }
87+ "http/protobuf" => {
88+ let exporter = SpanExporter :: builder ( )
89+ . with_http ( )
90+ . with_protocol ( Protocol :: HttpBinary )
91+ . build ( )
92+ . map_err ( |e| tracing:: warn!( "Failed to build OTEL span exporter: {}" , e) )
93+ . ok ( ) ?;
94+ BatchSpanProcessor :: builder ( exporter) . build ( )
95+ }
96+ "http/json" => {
97+ let exporter = SpanExporter :: builder ( )
98+ . with_http ( )
99+ . with_protocol ( Protocol :: HttpJson )
100+ . build ( )
101+ . map_err ( |e| tracing:: warn!( "Failed to build OTEL span exporter: {}" , e) )
102+ . ok ( ) ?;
103+ BatchSpanProcessor :: builder ( exporter) . build ( )
104+ }
105+ other => {
106+ tracing:: warn!(
107+ "Unknown OTEL_EXPORTER_OTLP_PROTOCOL value '{}'; disabling OTEL tracing. \
108+ Supported values: grpc, http/protobuf, http/json",
109+ other
110+ ) ;
111+ return None ;
112+ }
93113 }
94114 } ;
95115
96- let exporter = exporter
97- . map_err ( |e| tracing:: warn!( "Failed to build OTEL span exporter: {}" , e) )
98- . ok ( ) ?;
99-
100116 // Declare conformance to OTel Semantic Conventions v1.56.0 via schema_url.
101117 // Downstream collectors (e.g., the Schema Translate Processor) can apply
102118 // migration tables to rewrite attribute names across semconv versions —
@@ -109,8 +125,6 @@ pub fn init_tracing(service: &'static str) -> Option<SdkTracerProvider> {
109125 )
110126 . build ( ) ;
111127
112- let processor = BatchSpanProcessor :: builder ( exporter) . build ( ) ;
113-
114128 let provider = SdkTracerProvider :: builder ( )
115129 . with_span_processor ( processor)
116130 . with_resource ( resource)
0 commit comments