Otel: unify tracing subscriber across dev+release; env-gated OTLP export (#138)
Install ONE `tracing` subscriber in both build profiles via `otel::init_tracing()` (previously release-only, so spans were a no-op in dev). Behaviour is now environment-driven, not build-profile-driven: - OTLP span export happens only when `OTLP_OTLS_ENDPOINT` is set (release / staging). Unset -> spans are still created with valid contexts (so outgoing `traceparent` injection keeps working) but export nowhere; the console `fmt` layer is what shows them. - `tracing_subscriber::registry()` + `EnvFilter` (RUST_LOG) + `OpenTelemetryLayer` + console `fmt` layer, installed in both builds. - Set the global W3C `TraceContextPropagator`: required by the `tracing-actix-web` request middleware (#139) and by the outgoing traceparent helpers, both of which read the *global* propagator. - `tracing-log` bridge stays OFF (`default-features = false`) so the console fmt layer prints tracing spans only; `log` records keep their existing path (env_logger in debug, OTel logs in release) without double-printing. Deps: + tracing, tracing-subscriber (env-filter/fmt/ansi), tracing-opentelemetry 0.32 (pins opentelemetry 0.31 + otel_sdk 0.31, no version bump); opentelemetry_sdk gains the `testing` feature. Add `tests/otel_propagation.rs`: the regression matrix from spike #133, pinning the invariant the whole migration rests on -- an OTel context does NOT cross a bare `tokio::spawn` on a multi-thread runtime (S2/S4 orphan onto a fresh trace) but DOES with an explicit parent carry across the spawn (S6 `.instrument()`, S7 `span.enter()`). This is why every spawn in the codebase must carry the parent. clippy --all-targets clean; 690 lib + 775 integration + regression + 4 doc-tests pass.
This commit is contained in:
1 parent
8284509c90
commit
e93f6b6d69
5 files changed
+447
-14
No files matched your search
+4
-2
@@ -111,7 +111,10 @@ fn main() -> std::io::Result<()> {
|
||||
|
||||
let system = actix::System::new();
|
||||
system.block_on(async {
|
||||
// Just use basic logger when running a non-release build
|
||||
// One `tracing` subscriber in every build: env-driven, so dev prints
|
||||
// spans to the console and release exports them over OTLP. The `log`
|
||||
// pipeline stays separate — env_logger in debug, OTel logs in release.
|
||||
otel::init_tracing();
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
env_logger::init();
|
||||
@@ -119,7 +122,6 @@ fn main() -> std::io::Result<()> {
|
||||
#[cfg(not(debug_assertions))]
|
||||
{
|
||||
otel::init_logs();
|
||||
otel::init_tracing();
|
||||
}
|
||||
|
||||
// AppState construction loads (and seeds if needed) the libraries
|
||||
|
||||
+47
-11
@@ -3,9 +3,11 @@ use actix_web::http::header::HeaderMap;
|
||||
use opentelemetry::global::{BoxedSpan, BoxedTracer};
|
||||
use opentelemetry::propagation::TextMapPropagator;
|
||||
use opentelemetry::trace::{
|
||||
Span, SpanBuilder, SpanContext, SpanKind, Status, TraceContextExt, Tracer,
|
||||
Span, SpanBuilder, SpanContext, SpanKind, Status, TraceContextExt, Tracer, TracerProvider,
|
||||
};
|
||||
use opentelemetry::{Context, KeyValue, global};
|
||||
use tracing_subscriber::layer::SubscriberExt;
|
||||
use tracing_subscriber::util::SubscriberInitExt;
|
||||
use opentelemetry_appender_log::OpenTelemetryLogBridge;
|
||||
use opentelemetry_otlp::WithExportConfig;
|
||||
use opentelemetry_sdk::Resource;
|
||||
@@ -16,8 +18,26 @@ pub fn global_tracer() -> BoxedTracer {
|
||||
global::tracer("image-server")
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
/// Install the process-wide `tracing` subscriber plus the OTel span pipeline.
|
||||
///
|
||||
/// Called in BOTH debug and release builds (the old split made spans a no-op
|
||||
/// in dev). Behaviour is driven by the environment, not the build profile:
|
||||
/// - `OTLP_OTLS_ENDPOINT` set -> spans are batch-exported over OTLP (the
|
||||
/// release / staging path).
|
||||
/// - unset -> spans are created with valid contexts (so
|
||||
/// outgoing `traceparent` injection still works) but exported nowhere; the
|
||||
/// console `fmt` layer is what shows them.
|
||||
///
|
||||
/// The console `fmt` layer is always present, so a release build run locally
|
||||
/// also prints spans to stdout. `log` records are NOT routed through this
|
||||
/// subscriber (the `tracing-log` bridge is off) — they keep their existing
|
||||
/// path: `env_logger` in debug, OTel logs in release.
|
||||
pub fn init_tracing() {
|
||||
// W3C propagator for the whole process: the request middleware
|
||||
// (tracing-actix-web) and the outgoing `traceparent` helpers both read
|
||||
// the *global* propagator, so it must be set before any request lands.
|
||||
global::set_text_map_propagator(TraceContextPropagator::new());
|
||||
|
||||
let resources = Resource::builder()
|
||||
.with_attributes([
|
||||
KeyValue::new("service.name", "image-server"),
|
||||
@@ -25,18 +45,34 @@ pub fn init_tracing() {
|
||||
])
|
||||
.build();
|
||||
|
||||
let span_exporter = opentelemetry_otlp::SpanExporter::builder()
|
||||
.with_tonic()
|
||||
.with_endpoint(std::env::var("OTLP_OTLS_ENDPOINT").unwrap())
|
||||
.build()
|
||||
.unwrap();
|
||||
let mut provider_builder =
|
||||
opentelemetry_sdk::trace::SdkTracerProvider::builder().with_resource(resources);
|
||||
|
||||
let tracer_provider = opentelemetry_sdk::trace::SdkTracerProvider::builder()
|
||||
.with_batch_exporter(span_exporter)
|
||||
.with_resource(resources)
|
||||
.build();
|
||||
if let Ok(endpoint) = std::env::var("OTLP_OTLS_ENDPOINT") {
|
||||
let span_exporter = opentelemetry_otlp::SpanExporter::builder()
|
||||
.with_tonic()
|
||||
.with_endpoint(endpoint)
|
||||
.build()
|
||||
.expect("OTLP span exporter");
|
||||
provider_builder = provider_builder.with_batch_exporter(span_exporter);
|
||||
}
|
||||
let tracer_provider = provider_builder.build();
|
||||
|
||||
// The rest of the codebase resolves spans through the global provider
|
||||
// (`global_tracer()`, `global::tracer("db")`), so install it there; the
|
||||
// subscriber's OTel layer uses the same provider's tracer.
|
||||
let tracer = tracer_provider.tracer("image-server");
|
||||
global::set_tracer_provider(tracer_provider);
|
||||
|
||||
tracing_subscriber::registry()
|
||||
.with(
|
||||
tracing_subscriber::EnvFilter::try_from_default_env()
|
||||
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")),
|
||||
)
|
||||
.with(tracing_opentelemetry::OpenTelemetryLayer::new(tracer))
|
||||
.with(tracing_subscriber::fmt::layer().with_target(false))
|
||||
.try_init()
|
||||
.expect("failed to install tracing subscriber");
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
|
||||
Reference in new issue
Block a user