Skip to content

Commit ce280fd

Browse files
chore: stdout for span exporter
if env OTEL_EXPORTER_OTLP_ENDPOINT or OTEL_EXPORTER_OTLP_TRACES_ENDPOINT set to `stdout` export the traces to stdout
1 parent 64928ab commit ce280fd

3 files changed

Lines changed: 67 additions & 40 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: 54 additions & 40 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,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.
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-
{
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

Comments
 (0)