Line data Source code
1 : //! Helper functions to set up OpenTelemetry tracing.
2 : //!
3 : //! This comes in two variants, depending on whether you have a Tokio runtime available.
4 : //! If you do, call `init_tracing()`. It sets up the trace processor and exporter to use
5 : //! the current tokio runtime. If you don't have a runtime available, or you don't want
6 : //! to share the runtime with the tracing tasks, call `init_tracing_without_runtime()`
7 : //! instead. It sets up a dedicated single-threaded Tokio runtime for the tracing tasks.
8 : //!
9 : //! Example:
10 : //!
11 : //! ```rust,no_run
12 : //! use tracing_subscriber::prelude::*;
13 : //!
14 : //! #[tokio::main]
15 : //! async fn main() {
16 : //! // Set up logging to stderr
17 : //! let env_filter = tracing_subscriber::EnvFilter::try_from_default_env()
18 : //! .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info"));
19 : //! let fmt_layer = tracing_subscriber::fmt::layer()
20 : //! .with_target(false)
21 : //! .with_writer(std::io::stderr);
22 : //!
23 : //! // Initialize OpenTelemetry. Exports tracing spans as OpenTelemetry traces
24 : //! let otlp_layer = tracing_utils::init_tracing("my_application", tracing_utils::ExportConfig::default()).await;
25 : //!
26 : //! // Put it all together
27 : //! tracing_subscriber::registry()
28 : //! .with(env_filter)
29 : //! .with(otlp_layer)
30 : //! .with(fmt_layer)
31 : //! .init();
32 : //! }
33 : //! ```
34 : #![deny(unsafe_code)]
35 : #![deny(clippy::undocumented_unsafe_blocks)]
36 :
37 : pub mod http;
38 :
39 : use opentelemetry::KeyValue;
40 : use opentelemetry::trace::TracerProvider;
41 : use opentelemetry_otlp::WithExportConfig;
42 : pub use opentelemetry_otlp::{ExportConfig, Protocol};
43 : use tracing::level_filters::LevelFilter;
44 : use tracing::{Dispatch, Subscriber};
45 : use tracing_subscriber::Layer;
46 : use tracing_subscriber::layer::SubscriberExt;
47 : use tracing_subscriber::registry::LookupSpan;
48 :
49 : /// Set up OpenTelemetry exporter, using configuration from environment variables.
50 : ///
51 : /// `service_name` is set as the OpenTelemetry 'service.name' resource (see
52 : /// <https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/resource/semantic_conventions/README.md#service>)
53 : ///
54 : /// We try to follow the conventions for the environment variables specified in
55 : /// <https://opentelemetry.io/docs/reference/specification/sdk-environment-variables/>
56 : ///
57 : /// However, we only support a subset of those options:
58 : ///
59 : /// - OTEL_SDK_DISABLED is supported. The default is "false", meaning tracing
60 : /// is enabled by default. Set it to "true" to disable.
61 : ///
62 : /// - We use the OTLP exporter, with HTTP protocol. Most of the OTEL_EXPORTER_OTLP_*
63 : /// settings specified in
64 : /// <https://opentelemetry.io/docs/reference/specification/protocol/exporter/>
65 : /// are supported, as they are handled by the `opentelemetry-otlp` crate.
66 : /// Settings related to other exporters have no effect.
67 : ///
68 : /// - Some other settings are supported by the `opentelemetry` crate.
69 : ///
70 : /// If you need some other setting, please test if it works first. And perhaps
71 : /// add a comment in the list above to save the effort of testing for the next
72 : /// person.
73 : ///
74 : /// This doesn't block, but is marked as 'async' to hint that this must be called in
75 : /// asynchronous execution context.
76 0 : pub async fn init_tracing<S>(
77 0 : service_name: &str,
78 0 : export_config: ExportConfig,
79 0 : ) -> Option<impl Layer<S>>
80 0 : where
81 0 : S: Subscriber + for<'span> LookupSpan<'span>,
82 0 : {
83 0 : if std::env::var("OTEL_SDK_DISABLED") == Ok("true".to_string()) {
84 0 : return None;
85 0 : };
86 0 : Some(init_tracing_internal(
87 0 : service_name.to_string(),
88 0 : export_config,
89 0 : ))
90 0 : }
91 :
92 : /// Like `init_tracing`, but creates a separate tokio Runtime for the tracing
93 : /// tasks.
94 0 : pub fn init_tracing_without_runtime<S>(
95 0 : service_name: &str,
96 0 : export_config: ExportConfig,
97 0 : ) -> Option<impl Layer<S>>
98 0 : where
99 0 : S: Subscriber + for<'span> LookupSpan<'span>,
100 0 : {
101 0 : if std::env::var("OTEL_SDK_DISABLED") == Ok("true".to_string()) {
102 0 : return None;
103 0 : };
104 0 :
105 0 : // The opentelemetry batch processor and the OTLP exporter needs a Tokio
106 0 : // runtime. Create a dedicated runtime for them. One thread should be
107 0 : // enough.
108 0 : //
109 0 : // (Alternatively, instead of batching, we could use the "simple
110 0 : // processor", which doesn't need Tokio, and use "reqwest-blocking"
111 0 : // feature for the OTLP exporter, which also doesn't need Tokio. However,
112 0 : // batching is considered best practice, and also I have the feeling that
113 0 : // the non-Tokio codepaths in the opentelemetry crate are less used and
114 0 : // might be more buggy, so better to stay on the well-beaten path.)
115 0 : //
116 0 : // We leak the runtime so that it keeps running after we exit the
117 0 : // function.
118 0 : let runtime = Box::leak(Box::new(
119 0 : tokio::runtime::Builder::new_multi_thread()
120 0 : .enable_all()
121 0 : .thread_name("otlp runtime thread")
122 0 : .worker_threads(1)
123 0 : .build()
124 0 : .unwrap(),
125 0 : ));
126 0 : let _guard = runtime.enter();
127 0 :
128 0 : Some(init_tracing_internal(
129 0 : service_name.to_string(),
130 0 : export_config,
131 0 : ))
132 0 : }
133 :
134 0 : fn init_tracing_internal<S>(service_name: String, export_config: ExportConfig) -> impl Layer<S>
135 0 : where
136 0 : S: Subscriber + for<'span> LookupSpan<'span>,
137 0 : {
138 0 : // Sets up exporter from the provided [`ExportConfig`] parameter.
139 0 : // If the endpoint is not specified, it is loaded from the
140 0 : // OTEL_EXPORTER_OTLP_ENDPOINT environment variable.
141 0 : let exporter = opentelemetry_otlp::SpanExporter::builder()
142 0 : .with_http()
143 0 : .with_export_config(export_config)
144 0 : .build()
145 0 : .expect("could not initialize opentelemetry exporter");
146 0 :
147 0 : // TODO: opentelemetry::global::set_error_handler() with custom handler that
148 0 : // bypasses default tracing layers, but logs regular looking log
149 0 : // messages.
150 0 :
151 0 : // Propagate trace information in the standard W3C TraceContext format.
152 0 : opentelemetry::global::set_text_map_propagator(
153 0 : opentelemetry_sdk::propagation::TraceContextPropagator::new(),
154 0 : );
155 0 :
156 0 : let tracer = opentelemetry_sdk::trace::TracerProvider::builder()
157 0 : .with_batch_exporter(exporter, opentelemetry_sdk::runtime::Tokio)
158 0 : .with_resource(opentelemetry_sdk::Resource::new(vec![KeyValue::new(
159 0 : opentelemetry_semantic_conventions::resource::SERVICE_NAME,
160 0 : service_name,
161 0 : )]))
162 0 : .build()
163 0 : .tracer("global");
164 0 :
165 0 : tracing_opentelemetry::layer().with_tracer(tracer)
166 0 : }
167 :
168 : // Shutdown trace pipeline gracefully, so that it has a chance to send any
169 : // pending traces before we exit.
170 0 : pub fn shutdown_tracing() {
171 0 : opentelemetry::global::shutdown_tracer_provider();
172 0 : }
173 :
174 : pub enum OtelEnablement {
175 : Disabled,
176 : Enabled {
177 : service_name: String,
178 : export_config: ExportConfig,
179 : runtime: &'static tokio::runtime::Runtime,
180 : },
181 : }
182 :
183 : pub struct OtelGuard {
184 : pub dispatch: Dispatch,
185 : }
186 :
187 : impl Drop for OtelGuard {
188 0 : fn drop(&mut self) {
189 0 : shutdown_tracing();
190 0 : }
191 : }
192 :
193 : /// Initializes OTEL infrastructure for performance tracing according to the provided configuration
194 : ///
195 : /// Performance tracing is handled by a different [`tracing::Subscriber`]. This functions returns
196 : /// an [`OtelGuard`] containing a [`tracing::Dispatch`] associated with a newly created subscriber.
197 : /// Applications should use this dispatch for their performance traces.
198 : ///
199 : /// The lifetime of the guard should match taht of the application. On drop, it tears down the
200 : /// OTEL infra.
201 0 : pub fn init_performance_tracing(otel_enablement: OtelEnablement) -> Option<OtelGuard> {
202 0 : let otel_subscriber = match otel_enablement {
203 0 : OtelEnablement::Disabled => None,
204 : OtelEnablement::Enabled {
205 0 : service_name,
206 0 : export_config,
207 0 : runtime,
208 0 : } => {
209 0 : let otel_layer = runtime
210 0 : .block_on(init_tracing(&service_name, export_config))
211 0 : .with_filter(LevelFilter::INFO);
212 0 : let otel_subscriber = tracing_subscriber::registry().with(otel_layer);
213 0 : let otel_dispatch = Dispatch::new(otel_subscriber);
214 0 :
215 0 : Some(otel_dispatch)
216 : }
217 : };
218 :
219 0 : otel_subscriber.map(|dispatch| OtelGuard { dispatch })
220 0 : }
|