Skip to content

Commit 1452b4a

Browse files
chore: stdout for span exporter (#1647)
if env OTEL_EXPORTER_OTLP_ENDPOINT or OTEL_EXPORTER_OTLP_TRACES_ENDPOINT set to `stdout` export the traces to stdout
1 parent 64928ab commit 1452b4a

3 files changed

Lines changed: 72 additions & 38 deletions

File tree

Cargo.lock

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ opentelemetry-otlp = { version = "0.31.1", features = [
127127
"http-proto",
128128
"http-json",
129129
] }
130+
opentelemetry-stdout = "0.31.0"
130131
tracing-actix-web = "0.7"
131132

132133
# Time and Date

src/telemetry.rs

Lines changed: 59 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -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,81 @@ 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.
5355
pub 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-
{
56+
let endpoint = std::env::var(OTEL_EXPORTER_OTLP_ENDPOINT).ok();
57+
let traces_endpoint = std::env::var(OTEL_EXPORTER_OTLP_TRACES_ENDPOINT).ok();
58+
if endpoint.is_none() && traces_endpoint.is_none() {
5959
return None;
6060
}
6161

62+
// Endpoint sentinel: route spans to stdout, skip OTLP exporter entirely.
63+
// Either env var set to "stdout" (case-insensitive) triggers stdout mode.
64+
let is_stdout_sentinel = |v: &Option<String>| {
65+
v.as_deref()
66+
.is_some_and(|s| s.eq_ignore_ascii_case("stdout"))
67+
};
68+
let use_stdout = is_stdout_sentinel(&endpoint) || is_stdout_sentinel(&traces_endpoint);
69+
6270
let protocol =
6371
std::env::var(OTEL_EXPORTER_OTLP_PROTOCOL).unwrap_or_else(|_| "http/json".to_string());
6472

65-
// Build the exporter using the SDK's env-var-aware builders.
73+
// Build the exporter and wrap it in a BatchSpanProcessor. The stdout
74+
// exporter has a different concrete type, so we build the processor
75+
// inside each match arm to keep the types monomorphic.
76+
//
6677
// 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
78+
// .with_metadata() on OTLP builders — the SDK reads OTEL_EXPORTER_OTLP_ENDPOINT
79+
// and OTEL_EXPORTER_OTLP_HEADERS from the environment automatically, which
6980
// 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;
81+
let processor = if use_stdout {
82+
let exporter = opentelemetry_stdout::SpanExporter::default();
83+
BatchSpanProcessor::builder(exporter).build()
84+
} else {
85+
match protocol.as_str() {
86+
"grpc" => {
87+
let exporter = opentelemetry_otlp::SpanExporter::builder()
88+
.with_tonic()
89+
.build()
90+
.map_err(|e| tracing::warn!("Failed to build OTEL span exporter: {}", e))
91+
.ok()?;
92+
BatchSpanProcessor::builder(exporter).build()
93+
}
94+
"http/protobuf" => {
95+
let exporter = SpanExporter::builder()
96+
.with_http()
97+
.with_protocol(Protocol::HttpBinary)
98+
.build()
99+
.map_err(|e| tracing::warn!("Failed to build OTEL span exporter: {}", e))
100+
.ok()?;
101+
BatchSpanProcessor::builder(exporter).build()
102+
}
103+
"http/json" => {
104+
let exporter = SpanExporter::builder()
105+
.with_http()
106+
.with_protocol(Protocol::HttpJson)
107+
.build()
108+
.map_err(|e| tracing::warn!("Failed to build OTEL span exporter: {}", e))
109+
.ok()?;
110+
BatchSpanProcessor::builder(exporter).build()
111+
}
112+
other => {
113+
tracing::warn!(
114+
"Unknown OTEL_EXPORTER_OTLP_PROTOCOL value '{}'; disabling OTEL tracing. \
115+
Supported values: grpc, http/protobuf, http/json",
116+
other
117+
);
118+
return None;
119+
}
93120
}
94121
};
95122

96-
let exporter = exporter
97-
.map_err(|e| tracing::warn!("Failed to build OTEL span exporter: {}", e))
98-
.ok()?;
99-
100123
// Declare conformance to OTel Semantic Conventions v1.56.0 via schema_url.
101124
// Downstream collectors (e.g., the Schema Translate Processor) can apply
102125
// migration tables to rewrite attribute names across semconv versions —
@@ -109,8 +132,6 @@ pub fn init_tracing(service: &'static str) -> Option<SdkTracerProvider> {
109132
)
110133
.build();
111134

112-
let processor = BatchSpanProcessor::builder(exporter).build();
113-
114135
let provider = SdkTracerProvider::builder()
115136
.with_span_processor(processor)
116137
.with_resource(resource)

0 commit comments

Comments
 (0)