Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,34 @@ datadog_opentelemetry::tracing()
.init();
```

### Tracer diagnostics

The tracer reports on itself - configuration problems, transport failures, dropped spans. Where
those diagnostics go depends on the application: if it has installed a `tracing` subscriber, they
are emitted to it under the target `datadog_opentelemetry`; if it has not, they are printed to
stdout and stderr, so a bare binary still sees them.

With a subscriber installed, two independent settings apply, and a diagnostic has to pass **both**:

| Setting | Decides | Default |
| --- | --- | --- |
| `DD_LOG_LEVEL`, or `ConfigBuilder::set_log_level_filter` | how verbose the tracer is: which diagnostics it produces at all | `ERROR` |
| the subscriber's own filter, typically `RUST_LOG` | which of those it keeps, and where they go | `ERROR`, for `EnvFilter` with `RUST_LOG` unset |

Neither overrides the other: the more restrictive of the two wins, so raising one alone leaves the
other in force. Both have to be raised to see anything below `ERROR`.

```bash
# Errors only: the tracer produces nothing below ERROR for the filter to admit.
RUST_LOG=datadog_opentelemetry=debug cargo run

# Errors only: the tracer produces DEBUG diagnostics, and the subscriber discards them.
DD_LOG_LEVEL=debug RUST_LOG=error cargo run

# DEBUG diagnostics reach the subscriber, which decides where they end up.
DD_LOG_LEVEL=debug RUST_LOG=datadog_opentelemetry=debug cargo run
```

## Support

* MSRV: 1.87
Expand All @@ -246,3 +274,5 @@ datadog_opentelemetry::tracing()
* `logs` enabled the log provider
* `logs-grpc` enabled the log provider, with GRPC OTLP export
* `logs-http` enabled the log provider, with HTTP OTLP export
* `log-compat` routes the tracer's internal diagnostics through the `log` facade when no `tracing`
subscriber is available
3 changes: 3 additions & 0 deletions datadog-opentelemetry/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ uuid = { version = "1.11.0", features = ["v4"] }
arc-swap = "1.7.1"
ctor = "0.2"
libc = "0.2"
tracing = { version = "0.1", default-features = false, features = ["std"] }
Comment thread
iunanua marked this conversation as resolved.
log = { version = "0.4.21", optional = true }

# core Test utils
criterion = { version = "0.5.1", optional = true }
Expand Down Expand Up @@ -108,6 +110,7 @@ logs-http = [
"opentelemetry-otlp/http-proto",
"opentelemetry-otlp/reqwest-blocking-client",
]
log-compat = ["dep:log"]
_unstable_propagation = []
# Emits sampled allocation USDT probes for out-of-process profilers to pick up.
#
Expand Down
7 changes: 7 additions & 0 deletions datadog-opentelemetry/examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,10 @@ The server runs on `http://localhost:3000` with endpoints:
- `/health` - Health check endpoint
- `/echo` - Echo request body
- `/jump` - Makes outbound request to port 3001

It also shows where the tracer's own diagnostics go. They are emitted through `tracing` under the
`datadog_opentelemetry` target, so the subscriber built in `init_logs` decides where they go: the
console through `fmt`, while the OpenTelemetry bridge excludes the trace transport's own crates so a
failed export cannot produce further records to export. How verbose they are is a separate setting,
controlled by `DD_LOG_LEVEL` — this example sets `Debug` in code. The subscriber is installed
before the tracer, because diagnostics emitted before one exists are printed rather than routed.
39 changes: 35 additions & 4 deletions datadog-opentelemetry/examples/propagator/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ use opentelemetry_stdout::{LogExporter, SpanExporter};
use std::{convert::Infallible, net::SocketAddr, sync::OnceLock};
use tokio::net::TcpListener;
use tracing::info;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
use tracing_subscriber::{
layer::SubscriberExt, util::SubscriberInitExt, EnvFilter, Layer as TracingLayer,
};

fn get_tracer() -> &'static BoxedTracer {
static TRACER: OnceLock<BoxedTracer> = OnceLock::new();
Expand Down Expand Up @@ -242,8 +244,34 @@ fn init_logs() -> SdkLoggerProvider {
.with_log_processor(EnrichWithBaggageLogProcessor)
.with_simple_exporter(LogExporter::default())
.build();
let otel_layer = OpenTelemetryTracingBridge::new(&logger_provider);
tracing_subscriber::registry().with(otel_layer).init();

// The tracer emits its own diagnostics through `tracing`, under the `datadog_opentelemetry`
// target, so this subscriber decides where they end up. How verbose they are is a separate
// question, answered by `DD_LOG_LEVEL` / `set_log_level_filter` — see `init_tracer`.
let otel_filter = EnvFilter::new("info")
.add_directive(
"datadog_opentelemetry=debug"
.parse()
.expect("valid filter directive"),
)
.add_directive("libdd=off".parse().expect("valid filter directive"))
.add_directive("hyper=off".parse().expect("valid filter directive"))
.add_directive("tonic=off".parse().expect("valid filter directive"))
.add_directive("h2=off".parse().expect("valid filter directive"));
let otel_layer = OpenTelemetryTracingBridge::new(&logger_provider).with_filter(otel_filter);

// Also print to the console, so the tracer's own debug diagnostics stay readable locally.
let fmt_filter = EnvFilter::new("info").add_directive(
"datadog_opentelemetry=debug"
.parse()
.expect("valid filter directive"),
);
let fmt_layer = tracing_subscriber::fmt::layer().with_filter(fmt_filter);

tracing_subscriber::registry()
.with(otel_layer)
.with(fmt_layer)
.init();

logger_provider
}
Expand All @@ -252,8 +280,11 @@ fn init_logs() -> SdkLoggerProvider {
async fn main() {
use hyper_util::server::conn::auto::Builder;

let provider = init_tracer();
// Install the subscriber before initialising the tracer. Diagnostics emitted before a
// subscriber exists are printed to stdout/stderr instead of being routed, so doing this first
// is what puts the tracer's start-up messages through the layers configured above.
let logger_provider = init_logs();
let provider = init_tracer();
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
let listener = TcpListener::bind(addr).await.unwrap();

Expand Down
Loading
Loading