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 : //! use tracing_opentelemetry::OpenTelemetryLayer;
14 : //!
15 : //! #[tokio::main]
16 : //! async fn main() {
17 : //! // Set up logging to stderr
18 : //! let env_filter = tracing_subscriber::EnvFilter::try_from_default_env()
19 : //! .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info"));
20 : //! let fmt_layer = tracing_subscriber::fmt::layer()
21 : //! .with_target(false)
22 : //! .with_writer(std::io::stderr);
23 : //!
24 : //! // Initialize OpenTelemetry. Exports tracing spans as OpenTelemetry traces
25 : //! let otlp_layer = tracing_utils::init_tracing("my_application").await.map(OpenTelemetryLayer::new);
26 : //!
27 : //! // Put it all together
28 : //! tracing_subscriber::registry()
29 : //! .with(env_filter)
30 : //! .with(otlp_layer)
31 : //! .with(fmt_layer)
32 : //! .init();
33 : //! }
34 : //! ```
35 : #![deny(unsafe_code)]
36 : #![deny(clippy::undocumented_unsafe_blocks)]
37 :
38 : use opentelemetry::sdk::Resource;
39 : use opentelemetry::KeyValue;
40 : use opentelemetry_otlp::WithExportConfig;
41 : use opentelemetry_otlp::{OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_TRACES_ENDPOINT};
42 :
43 : pub use tracing_opentelemetry::OpenTelemetryLayer;
44 :
45 : pub mod http;
46 :
47 : /// Set up OpenTelemetry exporter, using configuration from environment variables.
48 : ///
49 : /// `service_name` is set as the OpenTelemetry 'service.name' resource (see
50 : /// <https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/resource/semantic_conventions/README.md#service>)
51 : ///
52 : /// We try to follow the conventions for the environment variables specified in
53 : /// <https://opentelemetry.io/docs/reference/specification/sdk-environment-variables/>
54 : ///
55 : /// However, we only support a subset of those options:
56 : ///
57 : /// - OTEL_SDK_DISABLED is supported. The default is "false", meaning tracing
58 : /// is enabled by default. Set it to "true" to disable.
59 : ///
60 : /// - We use the OTLP exporter, with HTTP protocol. Most of the OTEL_EXPORTER_OTLP_*
61 : /// settings specified in
62 : /// <https://opentelemetry.io/docs/reference/specification/protocol/exporter/>
63 : /// are supported, as they are handled by the `opentelemetry-otlp` crate.
64 : /// Settings related to other exporters have no effect.
65 : ///
66 : /// - Some other settings are supported by the `opentelemetry` crate.
67 : ///
68 : /// If you need some other setting, please test if it works first. And perhaps
69 : /// add a comment in the list above to save the effort of testing for the next
70 : /// person.
71 : ///
72 : /// This doesn't block, but is marked as 'async' to hint that this must be called in
73 : /// asynchronous execution context.
74 26 : pub async fn init_tracing(service_name: &str) -> Option<opentelemetry::sdk::trace::Tracer> {
75 26 : if std::env::var("OTEL_SDK_DISABLED") == Ok("true".to_string()) {
76 0 : return None;
77 26 : };
78 26 : Some(init_tracing_internal(service_name.to_string()))
79 26 : }
80 :
81 : /// Like `init_tracing`, but creates a separate tokio Runtime for the tracing
82 : /// tasks.
83 572 : pub fn init_tracing_without_runtime(
84 572 : service_name: &str,
85 572 : ) -> Option<opentelemetry::sdk::trace::Tracer> {
86 572 : if std::env::var("OTEL_SDK_DISABLED") == Ok("true".to_string()) {
87 0 : return None;
88 572 : };
89 572 :
90 572 : // The opentelemetry batch processor and the OTLP exporter needs a Tokio
91 572 : // runtime. Create a dedicated runtime for them. One thread should be
92 572 : // enough.
93 572 : //
94 572 : // (Alternatively, instead of batching, we could use the "simple
95 572 : // processor", which doesn't need Tokio, and use "reqwest-blocking"
96 572 : // feature for the OTLP exporter, which also doesn't need Tokio. However,
97 572 : // batching is considered best practice, and also I have the feeling that
98 572 : // the non-Tokio codepaths in the opentelemetry crate are less used and
99 572 : // might be more buggy, so better to stay on the well-beaten path.)
100 572 : //
101 572 : // We leak the runtime so that it keeps running after we exit the
102 572 : // function.
103 572 : let runtime = Box::leak(Box::new(
104 572 : tokio::runtime::Builder::new_multi_thread()
105 572 : .enable_all()
106 572 : .thread_name("otlp runtime thread")
107 572 : .worker_threads(1)
108 572 : .build()
109 572 : .unwrap(),
110 572 : ));
111 572 : let _guard = runtime.enter();
112 572 :
113 572 : Some(init_tracing_internal(service_name.to_string()))
114 572 : }
115 :
116 598 : fn init_tracing_internal(service_name: String) -> opentelemetry::sdk::trace::Tracer {
117 598 : // Set up exporter from the OTEL_EXPORTER_* environment variables
118 598 : let mut exporter = opentelemetry_otlp::new_exporter().http().with_env();
119 598 :
120 598 : // XXX opentelemetry-otlp v0.18.0 has a bug in how it uses the
121 598 : // OTEL_EXPORTER_OTLP_ENDPOINT env variable. According to the
122 598 : // OpenTelemetry spec at
123 598 : // <https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/exporter.md#endpoint-urls-for-otlphttp>,
124 598 : // the full exporter URL is formed by appending "/v1/traces" to the value
125 598 : // of OTEL_EXPORTER_OTLP_ENDPOINT. However, opentelemetry-otlp only does
126 598 : // that with the grpc-tonic exporter. Other exporters, like the HTTP
127 598 : // exporter, use the URL from OTEL_EXPORTER_OTLP_ENDPOINT as is, without
128 598 : // appending "/v1/traces".
129 598 : //
130 598 : // See https://github.com/open-telemetry/opentelemetry-rust/pull/950
131 598 : //
132 598 : // Work around that by checking OTEL_EXPORTER_OTLP_ENDPOINT, and setting
133 598 : // the endpoint url with the "/v1/traces" path ourselves. If the bug is
134 598 : // fixed in a later version, we can remove this code. But if we don't
135 598 : // remember to remove this, it won't do any harm either, as the crate will
136 598 : // just ignore the OTEL_EXPORTER_OTLP_ENDPOINT setting when the endpoint
137 598 : // is set directly with `with_endpoint`.
138 598 : if std::env::var(OTEL_EXPORTER_OTLP_TRACES_ENDPOINT).is_err() {
139 598 : if let Ok(mut endpoint) = std::env::var(OTEL_EXPORTER_OTLP_ENDPOINT) {
140 0 : if !endpoint.ends_with('/') {
141 0 : endpoint.push('/');
142 0 : }
143 0 : endpoint.push_str("v1/traces");
144 0 : exporter = exporter.with_endpoint(endpoint);
145 598 : }
146 0 : }
147 :
148 : // Propagate trace information in the standard W3C TraceContext format.
149 598 : opentelemetry::global::set_text_map_propagator(
150 598 : opentelemetry::sdk::propagation::TraceContextPropagator::new(),
151 598 : );
152 598 :
153 598 : opentelemetry_otlp::new_pipeline()
154 598 : .tracing()
155 598 : .with_exporter(exporter)
156 598 : .with_trace_config(
157 598 : opentelemetry::sdk::trace::config().with_resource(Resource::new(vec![KeyValue::new(
158 598 : opentelemetry_semantic_conventions::resource::SERVICE_NAME,
159 598 : service_name,
160 598 : )])),
161 598 : )
162 598 : .install_batch(opentelemetry::runtime::Tokio)
163 598 : .expect("could not initialize opentelemetry exporter")
164 598 : }
165 :
166 : // Shutdown trace pipeline gracefully, so that it has a chance to send any
167 : // pending traces before we exit.
168 597 : pub fn shutdown_tracing() {
169 597 : opentelemetry::global::shutdown_tracer_provider();
170 597 : }
|