Skip to content

Commit 86d0196

Browse files
committed
Add shutdown signal and full example using axum
1 parent b056809 commit 86d0196

6 files changed

Lines changed: 201 additions & 36 deletions

File tree

.gitignore

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
/target
2-
/Cargo.lock
1+
target/
2+
Cargo.lock
33
.idea/

Cargo.toml

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
[package]
22
name = "ddtrace"
3-
version = "0.0.2"
3+
version = "0.0.3"
44
authors = ["David Steiner <david_j_steiner@yahoo.co.nz", "Fergus Strangways-Dixon <fergusdixon101@gmail.com>"]
55
edition = "2021"
66
license = "MIT"
77
description = "utilities for integrating Datadog with tracing"
88
readme = "README.md"
99

1010
[features]
11-
axum = ["dep:axum", "dep:axum-tracing-opentelemetry"]
12-
reqwest = ["dep:reqwest"]
11+
axum = ["dep:axum", "dep:tokio", "dep:axum-tracing-opentelemetry"]
1312

1413
[dependencies]
1514
axum = { version = "^0.6.10", optional = true }
@@ -18,9 +17,9 @@ chrono = "^0.4.24"
1817
opentelemetry = { version = "^0.18.0", features = ["rt-tokio"] }
1918
opentelemetry-datadog = "^0.6.0"
2019
opentelemetry-otlp = { version = "^0.11.0" }
21-
reqwest = { version = "^0.11.16", optional = true }
2220
serde = { version = "^1.0.156", features = ["derive"] }
2321
serde_json = "^1.0.95"
22+
tokio = { version = "^1.26.0", features = ["signal"], optional = true }
2423
tracing = "^0.1.37"
2524
tracing-opentelemetry = "^0.18.0"
2625
tracing-serde = "^0.1.3"

README.md

Lines changed: 109 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1-
This repository is home to a Rust crate `ddtrace` with various Datadog
2-
utilities for tracing and logging in Rust.
3-
4-
# Background
1+
Datadog tracing and log correlation for Rust services.
52

63
Datadog has official support for Python, which includes various SDKs and
74
other utilities (such as the Python `ddtrace` library)
@@ -12,32 +9,126 @@ They don't have similar support for Rust. However, they do support the
129
This crate contains the necessary glue to bridge the gap between OpenTelemetry
1310
and Datadog.
1411

15-
# Further Information and Rationale
16-
## Tracing
12+
# Features
13+
14+
`ddtrace` has the following features:
15+
1. tracing: utilities for building an OpenTelemetry tracer/layer that sends traces to the Datadog agent
16+
2. log correlation: a log formatter that converts the trace ID and span ID to the Datadog native format and injects them into the `dd.trace_id` and `dd.span_id` fields
17+
(more information: https://docs.datadoghq.com/tracing/other_telemetry/connect_logs_and_traces/opentelemetry/)
18+
3. propagation: a utility function to set the Datadog propagator as the global propagator
19+
4. axum (enabled via the `axum` feature): re-exposing the functionality of [axum-tracing-opentelemetry](https://github.com/davidB/axum-tracing-opentelemetry)
20+
21+
# A Complete Example
22+
23+
The following is an example for using `ddtrace` with the `axum` feature enabled
24+
to set up an `axum` service with traces and logs sent to Datadog.
25+
26+
```rust
27+
use std::net::SocketAddr;
28+
use std::time::Duration;
29+
30+
use axum::{routing::get, Router};
31+
use ddtrace::axum::opentelemetry_tracing_layer;
32+
use ddtrace::formatter::DatadogFormatter;
33+
use ddtrace::set_global_propagator;
34+
use ddtrace::tracer::{build_layer, TraceResult};
35+
use tracing_subscriber::layer::SubscriberExt;
36+
use tracing_subscriber::util::SubscriberInitExt;
37+
38+
#[tokio::main]
39+
async fn main() -> TraceResult<()> {
40+
let service_name = std::env::var("DD_SERVICE").unwrap_or("my-service".to_string());
41+
let tracing_layer = build_layer(&service_name)?;
42+
tracing_subscriber::registry()
43+
.with(tracing_subscriber::EnvFilter::new(
44+
std::env::var("RUST_LOG").unwrap_or_else(|_| "info".into()),
45+
))
46+
.with(
47+
tracing_subscriber::fmt::layer()
48+
.json()
49+
.event_format(DatadogFormatter),
50+
)
51+
.with(tracing_layer)
52+
.init();
53+
set_global_propagator();
54+
55+
let app = Router::new()
56+
.route("/", get(root))
57+
.layer(opentelemetry_tracing_layer())
58+
.route("/health", get(health));
59+
60+
let addr = SocketAddr::from(([0, 0, 0, 0], 3025));
61+
tracing::info!("listening on {}", addr);
62+
axum::Server::bind(&addr)
63+
.serve(app.into_make_service())
64+
.with_graceful_shutdown(ddtrace::axum::shutdown_signal())
65+
.await
66+
.unwrap();
67+
68+
Ok(())
69+
}
70+
71+
async fn root() -> &'static str {
72+
do_something().await;
73+
"Hello, World!"
74+
}
75+
76+
#[tracing::instrument]
77+
async fn do_something() {
78+
tokio::time::sleep(Duration::from_millis(120)).await;
79+
do_something_else().await;
80+
tracing::info!("in the middle of doing something");
81+
tokio::time::sleep(Duration::from_millis(10)).await;
82+
do_something_else().await;
83+
tokio::time::sleep(Duration::from_millis(20)).await;
84+
}
85+
86+
#[tracing::instrument]
87+
async fn do_something_else() {
88+
tokio::time::sleep(Duration::from_millis(40)).await;
89+
}
90+
91+
async fn health() -> &'static str {
92+
"healthy"
93+
}
94+
```
95+
96+
# Datadog Agent Setup
97+
98+
The Datadog agent needs to be configured to receive OTel traces over gRPC.
99+
Please [refer to the Datadog documentation](https://docs.datadoghq.com/opentelemetry/otlp_ingest_in_the_agent/?tab=docker)
100+
to set up the agent.
101+
102+
# Further Context and Rationale
103+
104+
## Exporting Traces
17105
For traces, the official Datadog agent
18106
[can ingest OTel trace data](https://docs.datadoghq.com/opentelemetry/)
19107
with the correct environment variable settings. The traces can be sent
20-
via either HTTP or gRPC. More information on this can be found here:
21-
https://docs.datadoghq.com/opentelemetry/otlp_ingest_in_the_agent/?tab=docker
108+
via either HTTP or gRPC. More information on this can be found
109+
[here](https://docs.datadoghq.com/opentelemetry/otlp_ingest_in_the_agent/?tab=docker).
22110

23111
OpenTelemetry has an official Rust crate with extensions for major
24112
formats/providers. This includes a Datadog exporter. We have found
25113
this exporter to be less reliable than the standard OTel exporter
26114
sending data to the OTel endpoint of the Datadog agent, though.
115+
This crate builds on the OTel exporter.
27116

28-
This library provides utilities to set up the Rust `tracing` crate
29-
for sending data to the agent in the correct way.
117+
## Propagation
30118

31-
## Logging
32-
Datadog can ingest OpenTelemetry logs with two caveats -
33-
it expects the `dd.trace_id` and `dd.span_id` attributes
34-
to be set, and it expects a slightly different format for
35-
trace ID.
119+
Two commonly used propagation standards are `B3` (OpenZipkin's propagation style)
120+
and Jaeger. OpenTelemetry [supports both](https://opentelemetry.io/docs/reference/specification/context/api-propagators/#propagators-distribution).
36121

37-
This crate contains a JSON formatter layer that also correctly
38-
transform the trace ID to the Datadog native format.
122+
Most Datadog SDK's support both `B3` and the Datadog native propagation style.
123+
For example, the Python `ddtrace` library supports `B3` but it
124+
[needs to be explicitly enabled](https://ddtrace.readthedocs.io/en/stable/configuration.html#DD_TRACE_PROPAGATION_STYLE).
39125

40-
## Propagation
126+
For ease of integration with services written in other languages that use the official Datadog SDK,
127+
we opted for sticking with Datadog-style propagation over `B3`. This is set via the
128+
`set_global_propagator` function.
129+
130+
131+
# Reqwest Propagation
41132
The Python library takes care of propagation of the trace context automatically.
42133
Unfortunately, we need to do this manually in Rust.
43134

@@ -70,15 +161,3 @@ fn get_http_client() -> ClientWithMiddleware {
70161
.build()
71162
}
72163
```
73-
74-
75-
## Axum Support
76-
The trace context propagated from other services needs to be extracted and injected
77-
into the propagator. For `axum`, our choice of HTTP API framework, a third-party crate
78-
exists that supports this: https://github.com/davidB/axum-tracing-opentelemetry.
79-
80-
We re-expose this library for convenience.
81-
82-
# Full Example
83-
84-
TODO

examples/axum/Cargo.toml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[package]
2+
name = "axum-full-example"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[dependencies]
7+
axum = "^0.6.12"
8+
ddtrace = { path = "../..", features = ["axum"] }
9+
tokio = { version = "^1.26.0", features = ["macros", "rt-multi-thread"] }
10+
tracing = "^0.1.37"
11+
tracing-subscriber = { version = "^0.3.16", features = ["env-filter", "json"] }

examples/axum/src/main.rs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
use std::net::SocketAddr;
2+
use std::time::Duration;
3+
4+
use axum::{routing::get, Router};
5+
use ddtrace::axum::opentelemetry_tracing_layer;
6+
use ddtrace::formatter::DatadogFormatter;
7+
use ddtrace::set_global_propagator;
8+
use ddtrace::tracer::{build_layer, TraceResult};
9+
use tracing_subscriber::layer::SubscriberExt;
10+
use tracing_subscriber::util::SubscriberInitExt;
11+
12+
#[tokio::main]
13+
async fn main() -> TraceResult<()> {
14+
let service_name = std::env::var("DD_SERVICE").unwrap_or("my-service".to_string());
15+
let tracing_layer = build_layer(&service_name)?;
16+
tracing_subscriber::registry()
17+
.with(tracing_subscriber::EnvFilter::new(
18+
std::env::var("RUST_LOG").unwrap_or_else(|_| "info".into()),
19+
))
20+
.with(
21+
tracing_subscriber::fmt::layer()
22+
.json()
23+
.event_format(DatadogFormatter),
24+
)
25+
.with(tracing_layer)
26+
.init();
27+
set_global_propagator();
28+
29+
let app = Router::new()
30+
.route("/", get(root))
31+
.layer(opentelemetry_tracing_layer())
32+
.route("/health", get(health));
33+
34+
let addr = SocketAddr::from(([0, 0, 0, 0], 3025));
35+
tracing::info!("listening on {}", addr);
36+
axum::Server::bind(&addr)
37+
.serve(app.into_make_service())
38+
.with_graceful_shutdown(ddtrace::axum::shutdown_signal())
39+
.await
40+
.unwrap();
41+
42+
Ok(())
43+
}
44+
45+
async fn root() -> &'static str {
46+
do_something().await;
47+
"Hello, World!"
48+
}
49+
50+
#[tracing::instrument]
51+
async fn do_something() {
52+
tokio::time::sleep(Duration::from_millis(120)).await;
53+
do_something_else().await;
54+
tracing::info!("in the middle of doing something");
55+
tokio::time::sleep(Duration::from_millis(10)).await;
56+
do_something_else().await;
57+
tokio::time::sleep(Duration::from_millis(20)).await;
58+
}
59+
60+
#[tracing::instrument]
61+
async fn do_something_else() {
62+
tokio::time::sleep(Duration::from_millis(40)).await;
63+
}
64+
65+
async fn health() -> &'static str {
66+
"healthy"
67+
}

src/axum.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,12 @@
77
88
pub use axum_tracing_opentelemetry::opentelemetry_tracing_layer;
99
pub use axum_tracing_opentelemetry::opentelemetry_tracing_layer_grpc;
10+
11+
pub async fn shutdown_signal() {
12+
tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
13+
.expect("failed to install signal handler")
14+
.recv()
15+
.await;
16+
17+
opentelemetry::global::shutdown_tracer_provider();
18+
}

0 commit comments

Comments
 (0)