Skip to content

Commit 4a321b0

Browse files
fix: Use guard pattern to allow consumers to ensure final trace is sent (#185)
* Use guard pattern to allow consumers to ensure final trace is sent * Add must_use to struct * Switch examples to use guard pattern In discussion with the Rust OpenTelemetry crate maintainers, the `shutdown_tracer_provider` function intentionally no longer ensures that the final traces are sent to the OpenTelemetry server before the application exits. To perform this functionality, we now need to call `force_flush` on the TracerProvider instance. Since the public API functions for the TracerProvider only return a trait object which does not have this function exposed, we need to keep an instance of the actual TracerProvider around. In order to make this simple for consumers of this crate, I have used the guard pattern here so that they do not need to worry about those details or pull in the crates that provide those types. An instance of the TracerProvider is kept in a private field of the TracingGuard struct, which is returned by the init functions. That struct impls `Drop` to call `force_flush` when the struct is dropped. As such, crate consumers only need to use the `let _guard =` syntax to ensure the guard is only dropped when the function actually exits. This also allows us to, in the future, include other provider flushes in the same pattern, such as for OpenTelemetry logs and metrics. In addition, as the `Drop` impl only performs a `force_flush`, if any consumers of this crate update to a version with this and do not make any code changes, their code will continue to behave exactly the same - the guard struct will be dropped immediately, triggering the `force_flush`, but the provider will continue to operate. Their application will behave exactly as it did before with regard to sending traces. It only loses the behavior of ensuring the final traces are sent before application exit, but that wasn't working before this anyways. FIX #184
1 parent 9433355 commit 4a321b0

File tree

7 files changed

+31
-93
lines changed

7 files changed

+31
-93
lines changed

axum-tracing-opentelemetry/README.md

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,15 @@ use axum_tracing_opentelemetry::opentelemetry_tracing_layer;
2020
2121
#[tokio::main]
2222
async fn main() -> Result<(), axum::BoxError> {
23-
// very opinionated init of tracing, look as is source to make your own
24-
init_tracing_opentelemetry::tracing_subscriber_ext::init_subscribers()?;
23+
// very opinionated init of tracing, look at the source to make your own
24+
let _guard = init_tracing_opentelemetry::tracing_subscriber_ext::init_subscribers()?;
2525
2626
let app = app();
2727
// run it
2828
let addr = &"0.0.0.0:3000".parse::<SocketAddr>()?;
2929
tracing::warn!("listening on {}", addr);
3030
let listener = tokio::net::TcpListener::bind(addr).await?;
31-
axum::serve(listener, app.into_make_service())
32-
//FIXME .with_graceful_shutdown(shutdown_signal())
33-
.await?;
31+
axum::serve(listener, app.into_make_service()).await?;
3432
Ok(())
3533
}
3634
@@ -43,11 +41,6 @@ fn app() -> Router {
4341
.layer(OtelAxumLayer::default())
4442
.route("/health", get(health)) // request processed without span / trace
4543
}
46-
47-
async fn shutdown_signal() {
48-
//...
49-
opentelemetry::global::shutdown_tracer_provider();
50-
}
5144
```
5245

5346
For more info about how to initialize, you can look at crate [`init-tracing-opentelemetry`] or [`tracing-opentelemetry`].

examples/axum-otlp/src/main.rs

Lines changed: 2 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use tracing_opentelemetry_instrumentation_sdk::find_current_trace_id;
1111
#[tokio::main]
1212
async fn main() -> Result<(), BoxError> {
1313
// very opinionated init of tracing, look as is source to make your own
14-
init_tracing_opentelemetry::tracing_subscriber_ext::init_subscribers()?;
14+
let _guard = init_tracing_opentelemetry::tracing_subscriber_ext::init_subscribers()?;
1515

1616
let app = app();
1717
// run it
@@ -20,9 +20,7 @@ async fn main() -> Result<(), BoxError> {
2020
tracing::info!("try to call `curl -i http://127.0.0.1:3003/` (with trace)"); //Devskim: ignore DS137138
2121
tracing::info!("try to call `curl -i http://127.0.0.1:3003/health` (with NO trace)"); //Devskim: ignore DS137138
2222
let listener = tokio::net::TcpListener::bind(addr).await?;
23-
axum::serve(listener, app.into_make_service())
24-
.with_graceful_shutdown(shutdown_signal())
25-
.await?;
23+
axum::serve(listener, app.into_make_service()).await?;
2624
Ok(())
2725
}
2826

@@ -61,41 +59,3 @@ async fn proxy_handler(Path((service, path)): Path<(String, String)>) -> impl In
6159
json!({ "my_trace_id": trace_id, "fake_proxy": { "service": service, "path": path } }),
6260
)
6361
}
64-
65-
async fn shutdown_signal() {
66-
use std::sync::mpsc;
67-
use std::{thread, time::Duration};
68-
69-
let ctrl_c = async {
70-
tokio::signal::ctrl_c()
71-
.await
72-
.expect("failed to install Ctrl+C handler");
73-
};
74-
75-
#[cfg(unix)]
76-
let terminate = async {
77-
tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
78-
.expect("failed to install signal handler")
79-
.recv()
80-
.await;
81-
};
82-
83-
#[cfg(not(unix))]
84-
let terminate = std::future::pending::<()>();
85-
86-
tokio::select! {
87-
_ = ctrl_c => {},
88-
_ = terminate => {},
89-
}
90-
91-
tracing::warn!("signal received, starting graceful shutdown");
92-
let (sender, receiver) = mpsc::channel();
93-
let _ = thread::spawn(move || {
94-
opentelemetry::global::shutdown_tracer_provider();
95-
sender.send(()).ok()
96-
});
97-
let shutdown_res = receiver.recv_timeout(Duration::from_millis(2_000));
98-
if shutdown_res.is_err() {
99-
tracing::error!("failed to shutdown OpenTelemetry");
100-
}
101-
}

examples/grpc/src/client.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ pub mod generated {
1313
#[tokio::main]
1414
async fn main() -> Result<(), Box<dyn std::error::Error>> {
1515
// very opinionated init of tracing, look as is source to make your own
16-
init_tracing_opentelemetry::tracing_subscriber_ext::init_subscribers()
16+
let _guard = init_tracing_opentelemetry::tracing_subscriber_ext::init_subscribers()
1717
.expect("init subscribers");
1818

1919
// let channel = Channel::from_static("http://[::1]:50051").connect().await?;
@@ -53,6 +53,5 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
5353
println!("RESPONSE={:?}", response);
5454
}
5555

56-
opentelemetry::global::shutdown_tracer_provider();
5756
Ok(())
5857
}

examples/grpc/src/server.rs

Lines changed: 2 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ impl Greeter for MyGreeter {
4848
#[tokio::main]
4949
async fn main() -> Result<(), Box<dyn std::error::Error>> {
5050
// very opinionated init of tracing, look as is source to make your own
51-
init_tracing_opentelemetry::tracing_subscriber_ext::init_subscribers()
51+
let _guard = init_tracing_opentelemetry::tracing_subscriber_ext::init_subscribers()
5252
.expect("init subscribers");
5353

5454
let addr = "0.0.0.0:50051".parse().unwrap();
@@ -68,35 +68,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
6868
.add_service(reflection_service)
6969
//.add_service(GreeterServer::new(greeter))
7070
.add_service(GreeterServer::new(greeter))
71-
.serve_with_shutdown(addr, shutdown_signal())
71+
.serve(addr)
7272
.await?;
7373

7474
Ok(())
7575
}
76-
77-
async fn shutdown_signal() {
78-
let ctrl_c = async {
79-
tokio::signal::ctrl_c()
80-
.await
81-
.expect("failed to install Ctrl+C handler");
82-
};
83-
84-
#[cfg(unix)]
85-
let terminate = async {
86-
tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
87-
.expect("failed to install signal handler")
88-
.recv()
89-
.await;
90-
};
91-
92-
#[cfg(not(unix))]
93-
let terminate = std::future::pending::<()>();
94-
95-
tokio::select! {
96-
_ = ctrl_c => {},
97-
_ = terminate => {},
98-
}
99-
100-
//tracing::warn!("signal received, starting graceful shutdown");
101-
opentelemetry::global::shutdown_tracer_provider();
102-
}

examples/load/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use tracing_opentelemetry_instrumentation_sdk::otel_trace_span;
77
#[tokio::main]
88
async fn main() -> Result<(), Box<dyn std::error::Error>> {
99
// very opinionated init of tracing, look as is source to make your own
10-
init_tracing_opentelemetry::tracing_subscriber_ext::init_subscribers()?;
10+
let _guard = init_tracing_opentelemetry::tracing_subscriber_ext::init_subscribers()?;
1111
let mut stats = memory_stats();
1212
if stats.is_none() {
1313
eprintln!("Couldn't get the current memory usage :(");

init-tracing-opentelemetry/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,15 @@ use axum_tracing_opentelemetry::opentelemetry_tracing_layer;
1212
#[tokio::main]
1313
async fn main() -> Result<(), axum::BoxError> {
1414
// very opinionated init of tracing, look as is source to compose your own
15-
init_tracing_opentelemetry::tracing_subscriber_ext::init_subscribers()?;
15+
let _guard = init_tracing_opentelemetry::tracing_subscriber_ext::init_subscribers()?;
1616
1717
...;
1818
1919
Ok(())
2020
}
2121
```
2222

23-
AND Call `opentelemetry::global::shutdown_tracer_provider();` on shutdown of the app to be sure to send the pending trace,...
23+
The `init_subscribers` function returns a `TracingGuard` instance. Following the guard pattern, this struct provides no functions but, when dropped, ensures that any pending traces are sent before it exits. The syntax `let _guard` is suggested to ensure that Rust does not drop the struct until the application exits.
2424

2525
To configure opentelemetry tracer & tracing, you can use the functions from `init_tracing_opentelemetry::tracing_subscriber_ext`, but they are very opinionated (and WIP to make them more customizable and friendly), so we recommend making your composition, but look at the code (to avoid some issue) and share your feedback.
2626

init-tracing-opentelemetry/src/tracing_subscriber_ext.rs

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use opentelemetry::trace::{TraceError, TracerProvider};
2-
use opentelemetry_sdk::trace::Tracer;
2+
use opentelemetry_sdk::trace::{self, Tracer};
33
use tracing::{info, Subscriber};
44
use tracing_opentelemetry::OpenTelemetryLayer;
55
use tracing_subscriber::{filter::EnvFilter, layer::SubscriberExt, registry::LookupSpan, Layer};
@@ -60,7 +60,7 @@ pub fn build_loglevel_filter_layer() -> tracing_subscriber::filter::EnvFilter {
6060
EnvFilter::from_default_env()
6161
}
6262

63-
pub fn build_otel_layer<S>() -> Result<OpenTelemetryLayer<S, Tracer>, TraceError>
63+
pub fn build_otel_layer<S>() -> Result<(OpenTelemetryLayer<S, Tracer>, TracingGuard), TraceError>
6464
where
6565
S: Subscriber + for<'a> LookupSpan<'a>,
6666
{
@@ -88,22 +88,35 @@ where
8888
let layer = tracing_opentelemetry::layer()
8989
.with_error_records_to_exceptions(true)
9090
.with_tracer(tracerprovider.tracer(""));
91-
global::set_tracer_provider(tracerprovider);
92-
Ok(layer)
91+
global::set_tracer_provider(tracerprovider.clone());
92+
Ok((layer, TracingGuard { tracerprovider }))
9393
}
9494

95-
pub fn init_subscribers() -> Result<(), Error> {
95+
#[must_use = "Recommend holding with 'let _guard = ' pattern to ensure final traces are sent to the server"]
96+
pub struct TracingGuard {
97+
tracerprovider: trace::TracerProvider,
98+
}
99+
100+
impl Drop for TracingGuard {
101+
fn drop(&mut self) {
102+
self.tracerprovider.force_flush();
103+
}
104+
}
105+
106+
pub fn init_subscribers() -> Result<TracingGuard, Error> {
96107
//setup a temporary subscriber to log output during setup
97108
let subscriber = tracing_subscriber::registry()
98109
.with(build_loglevel_filter_layer())
99110
.with(build_logger_text());
100111
let _guard = tracing::subscriber::set_default(subscriber);
101112
info!("init logging & tracing");
102113

114+
let (layer, guard) = build_otel_layer()?;
115+
103116
let subscriber = tracing_subscriber::registry()
104-
.with(build_otel_layer()?)
117+
.with(layer)
105118
.with(build_loglevel_filter_layer())
106119
.with(build_logger_text());
107120
tracing::subscriber::set_global_default(subscriber)?;
108-
Ok(())
121+
Ok(guard)
109122
}

0 commit comments

Comments
 (0)