├── .gitignore ├── Cargo.toml ├── LICENSE.txt ├── README.md ├── examples ├── report.rs ├── span_extension.rs └── tonic │ ├── .gitignore │ ├── Cargo.toml │ ├── README.md │ ├── build.rs │ ├── proto │ └── helloworld.proto │ ├── src │ ├── client.rs │ └── server.rs │ └── trace.png ├── src ├── layer.rs ├── lib.rs └── span_ext.rs └── trace.png /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tracing-opentelemetry" 3 | version = "0.3.1" 4 | authors = ["Julian Tescher "] 5 | description = "OpenTelemetry integration for tracing" 6 | homepage = "https://github.com/jtescher/tracing-opentelemetry" 7 | repository = "https://github.com/jtescher/tracing-opentelemetry" 8 | readme = "README.md" 9 | categories = [ 10 | "development-tools::debugging", 11 | "development-tools::profiling", 12 | "asynchronous", 13 | ] 14 | keywords = ["tracing", "opentelemetry", "jaeger", "zipkin", "async"] 15 | license = "MIT" 16 | edition = "2018" 17 | 18 | [dependencies] 19 | opentelemetry = { version = "0.4.0", default-features = false, features = ["trace"] } 20 | rand = "0.7.3" 21 | tracing = "0.1.13" 22 | tracing-core = "0.1.10" 23 | tracing-subscriber = "0.2.3" 24 | 25 | [dev-dependencies] 26 | opentelemetry-jaeger = "0.3.0" 27 | thrift = "0.13.0" 28 | tracing-attributes = "0.1.7" 29 | 30 | [workspace] 31 | members = ["examples/tonic"] 32 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 Julian Tescher 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # This crate has been moved! 2 | 3 | You can now find it in the [tokio-rs/tracing](https://github.com/tokio-rs/tracing/tree/master/tracing-opentelemetry) 4 | repo. 5 | -------------------------------------------------------------------------------- /examples/report.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate tracing; 3 | 4 | use opentelemetry::{api::Provider, global, sdk}; 5 | use std::{thread, time::Duration}; 6 | use tracing_attributes::instrument; 7 | use tracing_opentelemetry::OpenTelemetryLayer; 8 | use tracing_subscriber::layer::SubscriberExt; 9 | use tracing_subscriber::Registry; 10 | 11 | #[instrument] 12 | #[inline] 13 | fn expensive_work() -> String { 14 | span!(tracing::Level::INFO, "expensive_step_1") 15 | .in_scope(|| thread::sleep(Duration::from_millis(25))); 16 | span!(tracing::Level::INFO, "expensive_step_2") 17 | .in_scope(|| thread::sleep(Duration::from_millis(25))); 18 | 19 | format!("success") 20 | } 21 | 22 | fn init_tracer() -> thrift::Result<()> { 23 | let exporter = opentelemetry_jaeger::Exporter::builder() 24 | .with_agent_endpoint("127.0.0.1:6831".parse().unwrap()) 25 | .with_process(opentelemetry_jaeger::Process { 26 | service_name: "report_example".to_string(), 27 | tags: Vec::new(), 28 | }) 29 | .init()?; 30 | let provider = sdk::Provider::builder() 31 | .with_simple_exporter(exporter) 32 | .with_config(sdk::Config { 33 | default_sampler: Box::new(sdk::Sampler::Always), 34 | ..Default::default() 35 | }) 36 | .build(); 37 | global::set_provider(provider); 38 | 39 | Ok(()) 40 | } 41 | 42 | fn main() -> thrift::Result<()> { 43 | init_tracer()?; 44 | let tracer = global::trace_provider().get_tracer("tracing"); 45 | let opentelemetry = OpenTelemetryLayer::with_tracer(tracer); 46 | let subscriber = Registry::default().with(opentelemetry); 47 | 48 | tracing::subscriber::with_default(subscriber, || { 49 | let root = span!(tracing::Level::INFO, "app_start", work_units = 2); 50 | let _enter = root.enter(); 51 | 52 | let work_result = expensive_work(); 53 | 54 | span!(tracing::Level::INFO, "faster_work") 55 | .in_scope(|| thread::sleep(Duration::from_millis(10))); 56 | 57 | warn!("About to exit!"); 58 | trace!("status: {}", work_result); 59 | }); 60 | 61 | // Allow flush 62 | thread::sleep(Duration::from_millis(250)); 63 | 64 | Ok(()) 65 | } 66 | -------------------------------------------------------------------------------- /examples/span_extension.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate tracing; 3 | 4 | use opentelemetry::api::{HttpTextFormat, Provider}; 5 | use opentelemetry::{api, global}; 6 | use std::collections::HashMap; 7 | use tracing_opentelemetry::{OpenTelemetryLayer, OpenTelemetrySpanExt}; 8 | use tracing_subscriber::layer::SubscriberExt; 9 | use tracing_subscriber::Registry; 10 | 11 | fn make_request(_span_context: api::SpanContext) { 12 | // perform external request after injecting context 13 | // e.g. if there are request headers that impl `opentelemetry::api::Carrier` 14 | // then `propagator.inject(span_context, request.headers_mut())` 15 | } 16 | 17 | fn build_example_carrier() -> HashMap<&'static str, String> { 18 | let mut carrier = HashMap::new(); 19 | carrier.insert( 20 | "X-B3", 21 | "4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-1".to_string(), 22 | ); 23 | 24 | carrier 25 | } 26 | 27 | fn main() { 28 | let tracer = global::trace_provider().get_tracer("example"); 29 | let opentelemetry = OpenTelemetryLayer::with_tracer(tracer); 30 | let subscriber = Registry::default().with(opentelemetry); 31 | 32 | // Propagator can be swapped with trace context propagator binary propagator, etc. 33 | let propagator = api::B3Propagator::new(true); 34 | 35 | tracing::subscriber::with_default(subscriber, || { 36 | // Extract from request headers, or any type that impls `opentelemetry::api::Carrier` 37 | let parent_context = propagator.extract(&build_example_carrier()); 38 | 39 | // Generate tracing span as usual 40 | let app_root = span!(tracing::Level::INFO, "app_start"); 41 | 42 | // Assign parent trace from external context 43 | app_root.set_parent(parent_context); 44 | 45 | // To include tracing span context in client requests from _this_ app, 46 | // use `context` to extract the current OpenTelemetry span context. 47 | make_request(app_root.context()); 48 | }); 49 | } 50 | -------------------------------------------------------------------------------- /examples/tonic/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /examples/tonic/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "helloworld-tonic" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | [[bin]] # Bin to run the HelloWorld gRPC server 7 | name = "helloworld-server" 8 | path = "src/server.rs" 9 | 10 | [[bin]] # Bin to run the HelloWorld gRPC client 11 | name = "helloworld-client" 12 | path = "src/client.rs" 13 | 14 | [dependencies] 15 | http = "0.2" 16 | tonic = "0.1" 17 | prost = "0.6" 18 | tokio = { version = "0.2", features = ["full"] } 19 | tracing = "0.1" 20 | tracing-subscriber = "0.2" 21 | tracing-opentelemetry = { version = "0.3", path = "../.." } 22 | opentelemetry = { version = "0.4", default-features = false, features = ["trace"] } 23 | opentelemetry-jaeger = "0.3" 24 | 25 | [build-dependencies] 26 | tonic-build = "0.1" 27 | -------------------------------------------------------------------------------- /examples/tonic/README.md: -------------------------------------------------------------------------------- 1 | # Tonic example 2 | 3 | Example showing [Tonic] client and server interaction with [tracing] 4 | instrumentation and [OpenTelemetry] context propagation. 5 | 6 | [Tonic]: https://github.com/hyperium/tonic 7 | [tracing]: https://tracing.rs/ 8 | [OpenTelemetry]: https://github.com/open-telemetry/opentelemetry-rust 9 | 10 | Examples 11 | -------- 12 | 13 | ```shell 14 | # Run jaeger in background 15 | $ docker run -d -p6831:6831/udp -p6832:6832/udp -p16686:16686 jaegertracing/all-in-one:latest 16 | 17 | # Run the server 18 | $ RUST_LOG=trace cargo run --bin helloworld-server 19 | 20 | # Now run the client to make a request to the server 21 | $ RUST_LOG=trace cargo run --bin helloworld-client 22 | 23 | # View spans (see the image below) 24 | $ firefox http://localhost:16686/ 25 | ``` 26 | 27 | ![Jaeger UI](trace.png) -------------------------------------------------------------------------------- /examples/tonic/build.rs: -------------------------------------------------------------------------------- 1 | fn main() -> Result<(), Box> { 2 | tonic_build::compile_protos("proto/helloworld.proto")?; 3 | Ok(()) 4 | } 5 | -------------------------------------------------------------------------------- /examples/tonic/proto/helloworld.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package helloworld; 3 | 4 | service Greeter { 5 | // Our SayHello rpc accepts HelloRequests and returns HelloReplies 6 | rpc SayHello (HelloRequest) returns (HelloReply); 7 | } 8 | 9 | message HelloRequest { 10 | // Request message contains the name to be greeted 11 | string name = 1; 12 | } 13 | 14 | message HelloReply { 15 | // Reply contains the greeting message 16 | string message = 1; 17 | } 18 | -------------------------------------------------------------------------------- /examples/tonic/src/client.rs: -------------------------------------------------------------------------------- 1 | use hello_world::greeter_client::GreeterClient; 2 | use hello_world::HelloRequest; 3 | use opentelemetry::api::{B3Propagator, HttpTextFormat, KeyValue, Provider}; 4 | use opentelemetry::sdk::Sampler; 5 | use opentelemetry::{api, sdk}; 6 | use tracing_opentelemetry::{OpenTelemetryLayer, OpenTelemetrySpanExt}; 7 | use tracing_subscriber::{layer::SubscriberExt, Registry}; 8 | 9 | pub mod hello_world { 10 | tonic::include_proto!("helloworld"); 11 | } 12 | 13 | fn tracing_init() -> Result<(), Box> { 14 | let builder = opentelemetry_jaeger::Exporter::builder() 15 | .with_agent_endpoint("127.0.0.1:6831".parse().unwrap()); 16 | 17 | let exporter = builder 18 | .with_process(opentelemetry_jaeger::Process { 19 | service_name: "client".to_string(), 20 | tags: vec![KeyValue::new("version", "0.1.0")], 21 | }) 22 | .init()?; 23 | 24 | let provider = sdk::Provider::builder() 25 | .with_simple_exporter(exporter) 26 | .with_config(sdk::Config { 27 | default_sampler: Box::new(Sampler::Always), 28 | ..Default::default() 29 | }) 30 | .build(); 31 | 32 | let tracer = provider.get_tracer("my-tracer"); 33 | let telemetry = OpenTelemetryLayer::with_tracer(tracer); 34 | 35 | let subscriber = Registry::default() 36 | // add the OpenTelemetry subscriber layer 37 | .with(telemetry) 38 | // add a logging layer 39 | .with(tracing_subscriber::fmt::Layer::default()) 40 | // add RUST_LOG-based filtering 41 | .with(tracing_subscriber::EnvFilter::from_default_env()); 42 | tracing::subscriber::set_global_default(subscriber)?; 43 | 44 | Ok(()) 45 | } 46 | 47 | struct TonicMetadataMapCarrier<'a>(&'a mut tonic::metadata::MetadataMap); 48 | impl<'a> api::Carrier for TonicMetadataMapCarrier<'a> { 49 | fn get(&self, key: &'static str) -> Option<&str> { 50 | self.0.get(key).and_then(|metadata| metadata.to_str().ok()) 51 | } 52 | 53 | fn set(&mut self, key: &'static str, value: String) { 54 | if let Ok(key) = tonic::metadata::MetadataKey::from_bytes(key.to_lowercase().as_bytes()) { 55 | self.0.insert( 56 | key, 57 | tonic::metadata::MetadataValue::from_str(&value).unwrap(), 58 | ); 59 | } 60 | } 61 | } 62 | 63 | #[tokio::main] 64 | async fn main() -> Result<(), Box> { 65 | tracing_init()?; 66 | let mut client = GreeterClient::connect("http://[::1]:50051").await?; 67 | let propagator = B3Propagator::new(true); 68 | let request_span = tracing::info_span!("client-request"); 69 | let _guard = request_span.enter(); 70 | 71 | let mut request = tonic::Request::new(HelloRequest { 72 | name: "Tonic".into(), 73 | }); 74 | propagator.inject( 75 | request_span.context(), 76 | &mut TonicMetadataMapCarrier(request.metadata_mut()), 77 | ); 78 | 79 | let response = client.say_hello(request).await?; 80 | 81 | tracing::debug!(response = ?response, "response-received"); 82 | Ok(()) 83 | } 84 | -------------------------------------------------------------------------------- /examples/tonic/src/server.rs: -------------------------------------------------------------------------------- 1 | use tonic::{transport::Server, Request, Response, Status}; 2 | 3 | use hello_world::greeter_server::{Greeter, GreeterServer}; 4 | use hello_world::{HelloReply, HelloRequest}; 5 | use opentelemetry::api::{self, HttpTextFormat, KeyValue, Provider}; 6 | use opentelemetry::sdk::{self, Sampler}; 7 | use tracing_opentelemetry::{OpenTelemetryLayer, OpenTelemetrySpanExt}; 8 | use tracing_subscriber::{layer::SubscriberExt, Registry}; 9 | 10 | pub mod hello_world { 11 | tonic::include_proto!("helloworld"); // The string specified here must match the proto package name 12 | } 13 | 14 | #[derive(Debug, Default)] 15 | pub struct MyGreeter {} 16 | 17 | #[tonic::async_trait] 18 | impl Greeter for MyGreeter { 19 | async fn say_hello( 20 | &self, 21 | request: Request, // Accept request of type HelloRequest 22 | ) -> Result, Status> { 23 | // Return an instance of type HelloReply 24 | tracing::debug!(request = ?request, "Processing reply"); 25 | 26 | let reply = hello_world::HelloReply { 27 | message: format!("Hello {}!", request.into_inner().name), // We must use .into_inner() as the fields of gRPC requests and responses are private 28 | }; 29 | 30 | Ok(Response::new(reply)) // Send back our formatted greeting 31 | } 32 | } 33 | 34 | fn tracing_init() -> Result<(), Box> { 35 | let builder = opentelemetry_jaeger::Exporter::builder() 36 | .with_agent_endpoint("127.0.0.1:6831".parse().unwrap()); 37 | 38 | let exporter = builder 39 | .with_process(opentelemetry_jaeger::Process { 40 | service_name: "server".to_string(), 41 | tags: vec![KeyValue::new("version", "0.1.0")], 42 | }) 43 | .init()?; 44 | let batch = 45 | sdk::BatchSpanProcessor::builder(exporter, tokio::spawn, tokio::time::interval).build(); 46 | 47 | let provider = sdk::Provider::builder() 48 | .with_batch_exporter(batch) 49 | .with_config(sdk::Config { 50 | default_sampler: Box::new(Sampler::Always), 51 | ..Default::default() 52 | }) 53 | .build(); 54 | 55 | let tracer = provider.get_tracer("my-tracer"); 56 | let telemetry = OpenTelemetryLayer::with_tracer(tracer); 57 | 58 | let subscriber = Registry::default() 59 | // add the OpenTelemetry subscriber layer 60 | .with(telemetry) 61 | // add a logging layer 62 | .with(tracing_subscriber::fmt::Layer::default()) 63 | // add RUST_LOG-based filtering 64 | .with(tracing_subscriber::EnvFilter::from_default_env()); 65 | tracing::subscriber::set_global_default(subscriber)?; 66 | 67 | Ok(()) 68 | } 69 | 70 | struct HttpHeaderMapCarrier<'a>(&'a http::HeaderMap); 71 | impl<'a> api::Carrier for HttpHeaderMapCarrier<'a> { 72 | fn get(&self, key: &'static str) -> Option<&str> { 73 | self.0 74 | .get(key.to_lowercase().as_str()) 75 | .and_then(|value| value.to_str().ok()) 76 | } 77 | 78 | fn set(&mut self, _key: &'static str, _value: String) { 79 | unimplemented!() 80 | } 81 | } 82 | 83 | #[tokio::main] 84 | async fn main() -> Result<(), Box> { 85 | tracing_init()?; 86 | let addr = "[::1]:50051".parse()?; 87 | let greeter = MyGreeter::default(); 88 | let propagator = api::B3Propagator::new(true); 89 | 90 | Server::builder() 91 | .trace_fn(move |header| { 92 | let parent = propagator.extract(&HttpHeaderMapCarrier(header)); 93 | let tracing_span = tracing::info_span!("Received request"); 94 | tracing_span.set_parent(parent); 95 | tracing_span 96 | }) 97 | .add_service(GreeterServer::new(greeter)) 98 | .serve(addr) 99 | .await?; 100 | 101 | Ok(()) 102 | } 103 | -------------------------------------------------------------------------------- /examples/tonic/trace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jtescher/tracing-opentelemetry/25bc1178ea1c2a65a7db34fe84b2bc6d21b5ab09/examples/tonic/trace.png -------------------------------------------------------------------------------- /src/layer.rs: -------------------------------------------------------------------------------- 1 | use opentelemetry::api; 2 | use std::any::TypeId; 3 | use std::fmt; 4 | use std::marker; 5 | use std::time::SystemTime; 6 | use tracing_core::span::{self, Attributes, Id, Record}; 7 | use tracing_core::{field, Event, Subscriber}; 8 | use tracing_subscriber::layer::Context; 9 | use tracing_subscriber::registry::LookupSpan; 10 | use tracing_subscriber::Layer; 11 | 12 | /// OpenTelemetry layer for use in a project that uses [tracing]. 13 | /// 14 | /// [tracing]: https://github.com/tokio-rs/tracing 15 | pub struct OpenTelemetryLayer { 16 | tracer: T, 17 | 18 | get_context: WithContext, 19 | _registry: marker::PhantomData, 20 | } 21 | 22 | // this function "remembers" the types of the subscriber so that we 23 | // can downcast to something aware of them without knowing those 24 | // types at the callsite. 25 | // 26 | // See https://github.com/tokio-rs/tracing/blob/4dad420ee1d4607bad79270c1520673fa6266a3d/tracing-error/src/layer.rs 27 | pub(crate) struct WithContext( 28 | fn(&tracing::Dispatch, &span::Id, f: &mut dyn FnMut(&mut api::SpanBuilder)), 29 | ); 30 | 31 | impl WithContext { 32 | // This function allows a function to be called in the context of the 33 | // "remembered" subscriber. 34 | pub(crate) fn with_context<'a>( 35 | &self, 36 | dispatch: &'a tracing::Dispatch, 37 | id: &span::Id, 38 | mut f: impl FnMut(&mut api::SpanBuilder), 39 | ) { 40 | (self.0)(dispatch, id, &mut f) 41 | } 42 | } 43 | 44 | pub(crate) fn build_context(builder: &mut api::SpanBuilder) -> api::SpanContext { 45 | let span_id = builder.span_id.expect("Builders must have id"); 46 | let (trace_id, trace_flags) = builder 47 | .parent_context 48 | .as_ref() 49 | .map(|parent_context| (parent_context.trace_id(), parent_context.trace_flags())) 50 | .unwrap_or_else(|| { 51 | ( 52 | builder.trace_id.expect("trace_id should exist"), 53 | api::TRACE_FLAG_SAMPLED, 54 | ) 55 | }); 56 | 57 | api::SpanContext::new(trace_id, span_id, trace_flags, false) 58 | } 59 | 60 | struct SpanEventVisitor<'a>(&'a mut api::Event); 61 | 62 | impl<'a> field::Visit for SpanEventVisitor<'a> { 63 | /// Record events on the underlying OpenTelemetry `Span`. 64 | fn record_debug(&mut self, field: &field::Field, value: &dyn fmt::Debug) { 65 | if field.name() == "message" { 66 | self.0.name = format!("{:?}", value); 67 | } else { 68 | self.0 69 | .attributes 70 | .push(api::Key::new(field.name()).string(format!("{:?}", value))); 71 | } 72 | } 73 | } 74 | 75 | struct SpanAttributeVisitor<'a>(&'a mut api::SpanBuilder); 76 | 77 | impl<'a> field::Visit for SpanAttributeVisitor<'a> { 78 | /// Set attributes on the underlying OpenTelemetry `Span`. 79 | fn record_debug(&mut self, field: &field::Field, value: &dyn fmt::Debug) { 80 | let attribute = api::Key::new(field.name()).string(format!("{:?}", value)); 81 | if let Some(attributes) = &mut self.0.attributes { 82 | attributes.push(attribute); 83 | } else { 84 | self.0.attributes = Some(vec![attribute]); 85 | } 86 | } 87 | } 88 | 89 | impl OpenTelemetryLayer 90 | where 91 | S: Subscriber + for<'span> LookupSpan<'span>, 92 | T: api::Tracer + 'static, 93 | { 94 | /// Retrieve the parent OpenTelemetry [`SpanContext`] from the current 95 | /// tracing [`span`] through the [`Registry`]. This [`SpanContext`] 96 | /// links spans to their parent for proper hierarchical visualization. 97 | fn parent_context( 98 | &self, 99 | attrs: &Attributes<'_>, 100 | ctx: &Context<'_, S>, 101 | ) -> Option { 102 | // If a span is specified, it _should_ exist in the underlying `Registry`. 103 | if let Some(parent) = attrs.parent() { 104 | let span = ctx.span(parent).expect("Span not found, this is a bug"); 105 | let mut extensions = span.extensions_mut(); 106 | extensions.get_mut::().map(build_context) 107 | // Else if the span is inferred from context, look up any available current span. 108 | } else if attrs.is_contextual() { 109 | ctx.current_span().id().and_then(|span_id| { 110 | let span = ctx.span(span_id).expect("Span not found, this is a bug"); 111 | let mut extensions = span.extensions_mut(); 112 | extensions.get_mut::().map(build_context) 113 | }) 114 | // Explicit root spans should have no parent context. 115 | } else { 116 | None 117 | } 118 | } 119 | 120 | /// Set the `OpenTelemetry` `Tracer` that this layer will use to produce 121 | /// and track `Span`s. 122 | /// 123 | /// ```rust,no_run 124 | /// use opentelemetry::{api::Provider, global, sdk}; 125 | /// use tracing_opentelemetry::OpenTelemetryLayer; 126 | /// use tracing_subscriber::layer::SubscriberExt; 127 | /// use tracing_subscriber::Registry; 128 | /// 129 | /// // Create a jaeger exporter for a `trace-demo` service. 130 | /// let exporter = opentelemetry_jaeger::Exporter::builder() 131 | /// .with_agent_endpoint("127.0.0.1:6831".parse().unwrap()) 132 | /// .with_process(opentelemetry_jaeger::Process { 133 | /// service_name: "trace_demo".to_string(), 134 | /// tags: Vec::new(), 135 | /// }) 136 | /// .init().expect("Error initializing Jaeger exporter"); 137 | /// 138 | /// // Build a provider from the jaeger exporter that always samples. 139 | /// let provider = sdk::Provider::builder() 140 | /// .with_simple_exporter(exporter) 141 | /// .with_config(sdk::Config { 142 | /// default_sampler: Box::new(sdk::Sampler::Always), 143 | /// ..Default::default() 144 | /// }) 145 | /// .build(); 146 | /// 147 | /// // Get a tracer from the provider for a component 148 | /// let tracer = provider.get_tracer("component-name"); 149 | /// 150 | /// // Create a layer with the configured tracer 151 | /// let otel_layer = OpenTelemetryLayer::with_tracer(tracer); 152 | /// 153 | /// // Use the tracing subscriber `Registry`, or any other subscriber 154 | /// // that impls `LookupSpan` 155 | /// let _subscriber = Registry::default() 156 | /// .with(otel_layer); 157 | /// ``` 158 | pub fn with_tracer(tracer: T) -> Self { 159 | OpenTelemetryLayer { 160 | tracer, 161 | get_context: WithContext(Self::get_context), 162 | _registry: marker::PhantomData, 163 | } 164 | } 165 | 166 | fn get_context( 167 | dispatch: &tracing::Dispatch, 168 | id: &span::Id, 169 | f: &mut dyn FnMut(&mut api::SpanBuilder), 170 | ) { 171 | let subscriber = dispatch 172 | .downcast_ref::() 173 | .expect("subscriber should downcast to expected type; this is a bug!"); 174 | let span = subscriber 175 | .span(id) 176 | .expect("registry should have a span for the current ID"); 177 | 178 | let mut extensions = span.extensions_mut(); 179 | if let Some(builder) = extensions.get_mut::() { 180 | f(builder); 181 | } 182 | } 183 | } 184 | 185 | impl Layer for OpenTelemetryLayer 186 | where 187 | S: Subscriber + for<'span> LookupSpan<'span>, 188 | T: api::Tracer + 'static, 189 | { 190 | /// Creates an `OpenTelemetry` `Span` for the corresponding `tracing` `Span`. 191 | /// This will attempt to parse the parent context if possible from the given attributes. 192 | fn new_span(&self, attrs: &Attributes<'_>, id: &span::Id, ctx: Context<'_, S>) { 193 | let span = ctx.span(id).expect("Span not found, this is a bug"); 194 | let mut extensions = span.extensions_mut(); 195 | 196 | let mut builder = self 197 | .tracer 198 | .span_builder(attrs.metadata().name()) 199 | .with_start_time(SystemTime::now()) 200 | // Eagerly assign span id so children have stable parent id 201 | .with_span_id(api::SpanId::from_u64(rand::random())); 202 | builder.parent_context = self.parent_context(attrs, &ctx); 203 | 204 | // Ensure trace id exists so children are matched properly. 205 | if builder.parent_context.is_none() { 206 | builder.trace_id = Some(api::TraceId::from_u128(rand::random())); 207 | } 208 | 209 | attrs.record(&mut SpanAttributeVisitor(&mut builder)); 210 | extensions.insert(builder); 211 | } 212 | 213 | /// Record values for the given span. 214 | fn on_record(&self, id: &Id, values: &Record<'_>, ctx: Context<'_, S>) { 215 | let span = ctx.span(id).expect("Span not found, this is a bug"); 216 | let mut extensions = span.extensions_mut(); 217 | if let Some(builder) = extensions.get_mut::() { 218 | values.record(&mut SpanAttributeVisitor(builder)); 219 | } 220 | } 221 | 222 | /// Record logs for the given event. 223 | fn on_event(&self, event: &Event<'_>, ctx: Context<'_, S>) { 224 | // Ignore events that are not in the context of a span 225 | if let Some(span_id) = ctx.current_span().id() { 226 | let span = ctx.span(span_id).expect("Span not found, this is a bug"); 227 | let mut extensions = span.extensions_mut(); 228 | if let Some(builder) = extensions.get_mut::() { 229 | let mut otel_event = api::Event::new( 230 | String::new(), 231 | SystemTime::now(), 232 | vec![ 233 | api::Key::new("level").string(event.metadata().level().to_string()), 234 | api::Key::new("target").string(event.metadata().target()), 235 | ], 236 | ); 237 | 238 | if builder.status_code.is_none() && *event.metadata().level() == tracing_core::Level::ERROR { 239 | builder.status_code = Some(api::StatusCode::Unknown); 240 | } 241 | 242 | event.record(&mut SpanEventVisitor(&mut otel_event)); 243 | 244 | if let Some(ref mut events) = builder.message_events { 245 | events.push(otel_event); 246 | } else { 247 | builder.message_events = Some(vec![otel_event]); 248 | } 249 | } 250 | }; 251 | } 252 | 253 | /// Mark the `Span` as ended when it is closed. 254 | fn on_close(&self, id: span::Id, ctx: Context<'_, S>) { 255 | let span = ctx.span(&id).expect("Span not found, this is a bug"); 256 | let mut extensions = span.extensions_mut(); 257 | if let Some(builder) = extensions.remove::() { 258 | // Assign end time, build and start span, drop span to export 259 | builder.with_end_time(SystemTime::now()).start(&self.tracer); 260 | } 261 | } 262 | 263 | // SAFETY: this is safe because the `WithContext` function pointer is valid 264 | // for the lifetime of `&self`. 265 | unsafe fn downcast_raw(&self, id: TypeId) -> Option<*const ()> { 266 | match id { 267 | id if id == TypeId::of::() => Some(self as *const _ as *const ()), 268 | id if id == TypeId::of::() => { 269 | Some(&self.get_context as *const _ as *const ()) 270 | } 271 | _ => None, 272 | } 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # Tracing OpenTelemetry 2 | //! 3 | //! An OpenTelemetry layer for the [tracing] library. 4 | //! 5 | //! [tracing]: https://github.com/tokio-rs/tracing 6 | //! 7 | //! ```rust,no_run 8 | //! #[macro_use] 9 | //! extern crate tracing; 10 | //! 11 | //! use opentelemetry::{api::Provider, sdk}; 12 | //! use tracing_opentelemetry::OpenTelemetryLayer; 13 | //! use tracing_subscriber::layer::SubscriberExt; 14 | //! use tracing_subscriber::Registry; 15 | //! 16 | //! fn main() { 17 | //! // Create a new tracer 18 | //! let tracer = sdk::Provider::default().get_tracer("service_name"); 19 | //! 20 | //! // Create a new OpenTelemetry tracing layer 21 | //! let telemetry = OpenTelemetryLayer::with_tracer(tracer); 22 | //! 23 | //! let subscriber = Registry::default().with(telemetry); 24 | //! 25 | //! // Trace executed code 26 | //! tracing::subscriber::with_default(subscriber, || { 27 | //! let root = span!(tracing::Level::TRACE, "app_start", work_units = 2); 28 | //! let _enter = root.enter(); 29 | //! 30 | //! error!("This event will be logged in the root span."); 31 | //! }); 32 | //! } 33 | //! ``` 34 | #![deny(unreachable_pub)] 35 | #![cfg_attr(test, deny(warnings))] 36 | 37 | /// Implementation of the trace::Layer as a source of OpenTelemetry data. 38 | mod layer; 39 | /// Span extension which enables OpenTelemetry span context management. 40 | mod span_ext; 41 | 42 | pub use layer::OpenTelemetryLayer; 43 | pub use span_ext::OpenTelemetrySpanExt; 44 | -------------------------------------------------------------------------------- /src/span_ext.rs: -------------------------------------------------------------------------------- 1 | use crate::layer::{build_context, WithContext}; 2 | use opentelemetry::api; 3 | 4 | /// `OpenTelemetrySpanExt` allows tracing spans to accept and return 5 | /// OpenTelemetry `SpanContext`s. 6 | pub trait OpenTelemetrySpanExt { 7 | /// Associates `self` with a given `OpenTelemetry` trace, using 8 | /// the provided parent context. 9 | /// 10 | /// ```rust 11 | /// use opentelemetry::api::{self, HttpTextFormat}; 12 | /// use tracing_opentelemetry::OpenTelemetrySpanExt; 13 | /// use std::collections::HashMap; 14 | /// use tracing::Span; 15 | /// 16 | /// // Example carrier, could be a framework header map that impls `api::Carrier`. 17 | /// let mut carrier = HashMap::new(); 18 | /// 19 | /// // Propagator can be swapped with trace context propagator binary propagator, etc. 20 | /// let propagator = api::B3Propagator::new(true); 21 | /// 22 | /// // Extract otel parent span context via the chosen propagator 23 | /// let parent_context = propagator.extract(&carrier); 24 | /// 25 | /// // Generate a tracing span as usual 26 | /// let app_root = tracing::span!(tracing::Level::INFO, "app_start"); 27 | /// 28 | /// // Assign parent trace from external context 29 | /// app_root.set_parent(parent_context); 30 | /// 31 | /// // Or if the current span has been created elsewhere: 32 | /// Span::current().set_parent(propagator.extract(&carrier)); 33 | /// ``` 34 | fn set_parent(&self, span_context: api::SpanContext); 35 | 36 | /// Extracts an `OpenTelemetry` context from `self`. 37 | /// 38 | /// ```rust 39 | /// use opentelemetry::api; 40 | /// use tracing_opentelemetry::OpenTelemetrySpanExt; 41 | /// use tracing::Span; 42 | /// 43 | /// fn make_request(span_context: api::SpanContext) { 44 | /// // perform external request after injecting context 45 | /// // e.g. if there are request headers that impl `opentelemetry::api::Carrier` 46 | /// // then `propagator.inject(span_context, request.headers_mut())` 47 | /// } 48 | /// 49 | /// // Generate a tracing span as usual 50 | /// let app_root = tracing::span!(tracing::Level::INFO, "app_start"); 51 | /// 52 | /// // To include tracing span context in client requests from _this_ app, 53 | /// // extract the current OpenTelemetry span context. 54 | /// make_request(app_root.context()); 55 | /// 56 | /// // Or if the current span has been created elsewhere: 57 | /// make_request(Span::current().context()) 58 | /// ``` 59 | fn context(&self) -> api::SpanContext; 60 | } 61 | 62 | impl OpenTelemetrySpanExt for tracing::Span { 63 | fn set_parent(&self, parent_context: api::SpanContext) { 64 | self.with_subscriber(move |(id, subscriber)| { 65 | let mut parent_context = Some(parent_context); 66 | if let Some(get_context) = subscriber.downcast_ref::() { 67 | get_context.with_context(subscriber, id, move |builder| { 68 | builder.parent_context = parent_context.take() 69 | }); 70 | } 71 | }); 72 | } 73 | 74 | fn context(&self) -> api::SpanContext { 75 | let mut span_context = None; 76 | self.with_subscriber(|(id, subscriber)| { 77 | if let Some(get_context) = subscriber.downcast_ref::() { 78 | get_context.with_context(subscriber, id, |builder| { 79 | span_context = Some(build_context(builder)); 80 | }) 81 | } 82 | }); 83 | 84 | span_context.unwrap_or_else(api::SpanContext::empty_context) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /trace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jtescher/tracing-opentelemetry/25bc1178ea1c2a65a7db34fe84b2bc6d21b5ab09/trace.png --------------------------------------------------------------------------------