├── .github ├── actions-rs │ └── grcov.yml └── workflows │ └── ci.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md └── src ├── carrier.rs ├── convert.rs ├── error.rs ├── lib.rs ├── log.rs ├── sampler.rs ├── span.rs ├── tag.rs └── tracer.rs /.github/actions-rs/grcov.yml: -------------------------------------------------------------------------------- 1 | ignore-not-existing: true 2 | llvm: true 3 | filter: covered 4 | ignore: 5 | - "/*" 6 | - "../*" 7 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # Based on https://github.com/actions-rs/meta/blob/master/recipes/quickstart.md 2 | 3 | name: CI 4 | 5 | on: [push, pull_request] 6 | 7 | jobs: 8 | check: 9 | name: Check 10 | runs-on: ubuntu-latest 11 | strategy: 12 | matrix: 13 | toolchain: [stable, beta, nightly] 14 | steps: 15 | - name: Checkout sources 16 | uses: actions/checkout@v2 17 | 18 | - name: Install ${{ matrix.toolchain }} toolchain 19 | uses: actions-rs/toolchain@v1 20 | with: 21 | profile: minimal 22 | toolchain: ${{ matrix.toolchain }} 23 | override: true 24 | 25 | - name: Run cargo check 26 | uses: actions-rs/cargo@v1 27 | with: 28 | command: check 29 | args: --all-features 30 | 31 | test: 32 | name: Test Suite 33 | runs-on: ubuntu-latest 34 | strategy: 35 | matrix: 36 | toolchain: [stable, beta, nightly] 37 | steps: 38 | - name: Checkout sources 39 | uses: actions/checkout@v2 40 | 41 | - name: Install ${{ matrix.toolchain }} toolchain 42 | uses: actions-rs/toolchain@v1 43 | with: 44 | profile: minimal 45 | toolchain: ${{ matrix.toolchain }} 46 | override: true 47 | 48 | - name: Run cargo test 49 | uses: actions-rs/cargo@v1 50 | with: 51 | command: test 52 | args: --all-features 53 | 54 | lints: 55 | name: Lints 56 | runs-on: ubuntu-latest 57 | strategy: 58 | matrix: 59 | toolchain: [stable, beta, nightly] 60 | steps: 61 | - name: Checkout sources 62 | uses: actions/checkout@v2 63 | 64 | - name: Install ${{ matrix.toolchain }} toolchain 65 | uses: actions-rs/toolchain@v1 66 | with: 67 | profile: minimal 68 | toolchain: ${{ matrix.toolchain }} 69 | override: true 70 | components: rustfmt, clippy 71 | 72 | - name: Run cargo fmt 73 | uses: actions-rs/cargo@v1 74 | with: 75 | command: fmt 76 | args: --all -- --check 77 | 78 | - name: Run cargo clippy 79 | uses: actions-rs/cargo@v1 80 | with: 81 | command: clippy 82 | args: --all-features -- -D warnings 83 | 84 | grcov: 85 | name: Coverage 86 | runs-on: ubuntu-latest 87 | steps: 88 | - uses: actions/checkout@v2 89 | 90 | - name: Install toolchain 91 | uses: actions-rs/toolchain@v1 92 | with: 93 | toolchain: nightly 94 | override: true 95 | 96 | - name: Execute tests 97 | uses: actions-rs/cargo@v1 98 | with: 99 | command: test 100 | args: --all --all-features 101 | env: 102 | CARGO_INCREMENTAL: 0 103 | RUSTFLAGS: "-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort" 104 | RUSTDOCFLAGS: "-Cpanic=abort" 105 | 106 | - name: Gather coverage data 107 | id: coverage 108 | uses: actions-rs/grcov@v0.1 109 | 110 | - name: Coveralls upload 111 | uses: coverallsapp/github-action@master 112 | with: 113 | github-token: ${{ secrets.GITHUB_TOKEN }} 114 | parallel: true 115 | path-to-lcov: ${{ steps.coverage.outputs.report }} 116 | 117 | grcov_finalize: 118 | runs-on: ubuntu-latest 119 | needs: grcov 120 | steps: 121 | - name: Coveralls finalization 122 | uses: coverallsapp/github-action@master 123 | with: 124 | github-token: ${{ secrets.GITHUB_TOKEN }} 125 | parallel-finished: true 126 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | **/*.rs.bk 3 | Cargo.lock 4 | .idea 5 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rustracing" 3 | version = "0.6.0" 4 | authors = ["Takeru Ohta "] 5 | description = "OpenTracing API for Rust" 6 | homepage = "https://github.com/sile/rustracing" 7 | repository = "https://github.com/sile/rustracing" 8 | readme = "README.md" 9 | keywords = ["opentracing", "distributed-tracing"] 10 | categories = ["web-programming"] 11 | license = "MIT" 12 | edition = "2021" 13 | 14 | [badges] 15 | coveralls = {repository = "sile/rustracing"} 16 | 17 | [features] 18 | stacktrace = [ "backtrace" ] 19 | default = [ "stacktrace" ] 20 | 21 | [dependencies] 22 | backtrace = { version = "0.3", optional = true } 23 | crossbeam-channel = "0.5" 24 | rand = "0.8.1" 25 | trackable = "1.2" 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2017 Takeru Ohta 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | rustracing 2 | ========== 3 | 4 | [![Crates.io: rustracing](https://img.shields.io/crates/v/rustracing.svg)](https://crates.io/crates/rustracing) 5 | [![Documentation](https://docs.rs/rustracing/badge.svg)](https://docs.rs/rustracing) 6 | [![Actions Status](https://github.com/sile/rustracing/workflows/CI/badge.svg)](https://github.com/sile/rustracing/actions) 7 | [![Coverage Status](https://coveralls.io/repos/github/sile/rustracing/badge.svg?branch=master)](https://coveralls.io/github/sile/rustracing?branch=master) 8 | [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) 9 | 10 | [OpenTracing] API for Rust. 11 | 12 | [Documentation](https://docs.rs/rustracing) 13 | 14 | Examples 15 | -------- 16 | 17 | ```rust 18 | use rustracing::sampler::AllSampler; 19 | use rustracing::tag::Tag; 20 | use rustracing::Tracer; 21 | use std::thread; 22 | use std::time::Duration; 23 | 24 | // Creates a tracer 25 | let (span_tx, span_rx) = crossbeam_channel::bounded(10); 26 | let tracer = Tracer::with_sender(AllSampler, span_tx); 27 | { 28 | // Starts "parent" span 29 | let parent_span = tracer.span("parent").start_with_state(()); 30 | thread::sleep(Duration::from_millis(10)); 31 | { 32 | // Starts "child" span 33 | let mut child_span = tracer 34 | .span("child_span") 35 | .child_of(&parent_span) 36 | .tag(Tag::new("key", "value")) 37 | .start_with_state(()); 38 | 39 | child_span.log(|log| { 40 | log.error().message("a log message"); 41 | }); 42 | } // The "child" span dropped and will be sent to `span_rx` 43 | } // The "parent" span dropped and will be sent to `span_rx` 44 | 45 | // Outputs finished spans to the standard output 46 | while let Ok(span) = span_rx.try_recv() { 47 | println!("# SPAN: {:?}", span); 48 | } 49 | ``` 50 | 51 | As an actual usage example of the crate and an implementation of the [OpenTracing] API, 52 | it may be helpful to looking at [rustracing_jaeger] crate. 53 | 54 | References 55 | ---------- 56 | 57 | - [The OpenTracing Semantic Specification (v1.1)][specification] 58 | 59 | [OpenTracing]: http://opentracing.io/ 60 | [specification]: https://github.com/opentracing/specification/blob/master/specification.md 61 | [rustracing_jaeger]: https://github.com/sile/rustracing_jaeger 62 | -------------------------------------------------------------------------------- /src/carrier.rs: -------------------------------------------------------------------------------- 1 | //! Traits for representing carriers that propagate span contexts across process boundaries. 2 | use crate::span::SpanContext; 3 | use crate::Result; 4 | use std::collections::{BTreeMap, HashMap}; 5 | use std::hash::{BuildHasher, Hash}; 6 | use std::io::{Read, Write}; 7 | 8 | /// This trait allows to inject `SpanContext` to `TextMap`. 9 | pub trait InjectToTextMap: Sized 10 | where 11 | T: TextMap, 12 | { 13 | /// Injects `context` to `carrier`. 14 | fn inject_to_text_map(context: &SpanContext, carrier: &mut T) -> Result<()>; 15 | } 16 | 17 | /// This trait allows to extract `SpanContext` from `TextMap`. 18 | pub trait ExtractFromTextMap: Sized 19 | where 20 | T: TextMap, 21 | { 22 | /// Extracts `SpanContext` from `carrier`. 23 | /// 24 | /// If `carrier` contains no span context, it will return `Ok(None)`. 25 | fn extract_from_text_map(carrier: &T) -> Result>>; 26 | } 27 | 28 | /// This trait represents carriers which support **Text Map** format. 29 | /// 30 | /// **Text Map** is an arbitrary string-to-string map with an unrestricted character set 31 | /// for both keys and values. 32 | pub trait TextMap { 33 | /// Sets the value of `key` in the map to `value`. 34 | fn set(&mut self, key: &str, value: &str); 35 | 36 | /// Gets the value of `key'. 37 | fn get(&self, key: &str) -> Option<&str>; 38 | } 39 | impl TextMap for HashMap { 40 | fn set(&mut self, key: &str, value: &str) { 41 | self.insert(key.to_owned(), value.to_owned()); 42 | } 43 | fn get(&self, key: &str) -> Option<&str> { 44 | HashMap::get(self, key).map(|v| v.as_ref()) 45 | } 46 | } 47 | impl TextMap for BTreeMap { 48 | fn set(&mut self, key: &str, value: &str) { 49 | self.insert(key.to_owned(), value.to_owned()); 50 | } 51 | fn get(&self, key: &str) -> Option<&str> { 52 | BTreeMap::get(self, key).map(|v| v.as_ref()) 53 | } 54 | } 55 | 56 | /// This trait allows to inject `SpanContext` to HTTP header. 57 | pub trait InjectToHttpHeader: Sized 58 | where 59 | T: SetHttpHeaderField, 60 | { 61 | /// Injects `context` to `carrier`. 62 | fn inject_to_http_header(context: &SpanContext, carrier: &mut T) -> Result<()>; 63 | } 64 | 65 | /// This trait allows to extract `SpanContext` from HTTP header. 66 | pub trait ExtractFromHttpHeader<'a, T>: Sized 67 | where 68 | T: IterHttpHeaderFields<'a>, 69 | { 70 | /// Extracts `SpanContext` from `carrier`. 71 | /// 72 | /// If `carrier` contains no span context, it will return `Ok(None)`. 73 | fn extract_from_http_header(carrier: &'a T) -> Result>>; 74 | } 75 | 76 | /// This trait allows to insert fields in a HTTP header. 77 | pub trait SetHttpHeaderField { 78 | /// Sets the value of the field named `name` in the HTTP header to `value`. 79 | fn set_http_header_field(&mut self, name: &str, value: &str) -> Result<()>; 80 | } 81 | impl SetHttpHeaderField for HashMap { 82 | fn set_http_header_field(&mut self, name: &str, value: &str) -> Result<()> { 83 | self.insert(name.to_owned(), value.to_owned()); 84 | Ok(()) 85 | } 86 | } 87 | 88 | /// This trait allows to iterate over the fields of a HTTP header. 89 | pub trait IterHttpHeaderFields<'a> { 90 | /// Iterator for traversing HTTP header fields. 91 | type Fields: Iterator; 92 | 93 | /// Returns an iterator for traversing the HTTP header fields. 94 | fn fields(&'a self) -> Self::Fields; 95 | } 96 | impl<'a, K, V, S> IterHttpHeaderFields<'a> for HashMap 97 | where 98 | K: AsRef + Eq + Hash, 99 | V: AsRef<[u8]>, 100 | S: BuildHasher, 101 | { 102 | type Fields = Box + 'a>; 103 | 104 | fn fields(&'a self) -> Self::Fields { 105 | Box::new(self.iter().map(|x| (x.0.as_ref(), x.1.as_ref()))) 106 | } 107 | } 108 | 109 | /// This trait allows to inject `SpanContext` to binary stream. 110 | pub trait InjectToBinary: Sized 111 | where 112 | T: Write, 113 | { 114 | /// Injects `context` to `carrier`. 115 | fn inject_to_binary(context: &SpanContext, carrier: &mut T) -> Result<()>; 116 | } 117 | 118 | /// This trait allows to extract `SpanContext` from binary stream. 119 | pub trait ExtractFromBinary: Sized 120 | where 121 | T: Read, 122 | { 123 | /// Extracts `SpanContext` from `carrier`. 124 | /// 125 | /// If `carrier` contains no span context, it will return `Ok(None)`. 126 | fn extract_from_binary(carrier: &mut T) -> Result>>; 127 | } 128 | -------------------------------------------------------------------------------- /src/convert.rs: -------------------------------------------------------------------------------- 1 | //! Traits for conversions between types. 2 | 3 | /// A cheap reference-to-reference conversion that has a possibility to fail. 4 | pub trait MaybeAsRef { 5 | /// Performs the conversion. 6 | fn maybe_as_ref(&self) -> Option<&T>; 7 | } 8 | impl MaybeAsRef for Option 9 | where 10 | U: MaybeAsRef, 11 | { 12 | fn maybe_as_ref(&self) -> Option<&T> { 13 | self.as_ref().and_then(|u| u.maybe_as_ref()) 14 | } 15 | } 16 | 17 | #[cfg(test)] 18 | mod tests { 19 | use super::*; 20 | 21 | #[test] 22 | fn it_works() { 23 | struct Foo; 24 | struct Bar(Foo); 25 | impl MaybeAsRef for Bar { 26 | fn maybe_as_ref(&self) -> Option<&Foo> { 27 | Some(&self.0) 28 | } 29 | } 30 | 31 | let bar = Bar(Foo); 32 | assert!(bar.maybe_as_ref().is_some()); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use trackable::error::ErrorKind as TrackableErrorKind; 2 | use trackable::error::TrackableError; 3 | 4 | /// This crate specific error type. 5 | #[derive(Debug, Clone, TrackableError)] 6 | pub struct Error(TrackableError); 7 | 8 | /// The list of the possible error kinds 9 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 10 | pub enum ErrorKind { 11 | /// Input data is invalid. 12 | InvalidInput, 13 | 14 | /// Other errors (e.g., I/O error). 15 | Other, 16 | } 17 | impl TrackableErrorKind for ErrorKind {} 18 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! [OpenTracing][opentracing] API for Rust 2 | //! 3 | //! # Examples 4 | //! 5 | //! ``` 6 | //! use rustracing::sampler::AllSampler; 7 | //! use rustracing::tag::Tag; 8 | //! use rustracing::Tracer; 9 | //! use std::thread; 10 | //! use std::time::Duration; 11 | //! 12 | //! // Creates a tracer 13 | //! let (span_tx, span_rx) = crossbeam_channel::bounded(10); 14 | //! let tracer = Tracer::with_sender(AllSampler, span_tx); 15 | //! { 16 | //! // Starts "parent" span 17 | //! let parent_span = tracer.span("parent").start_with_state(()); 18 | //! thread::sleep(Duration::from_millis(10)); 19 | //! { 20 | //! // Starts "child" span 21 | //! let mut child_span = tracer 22 | //! .span("child_span") 23 | //! .child_of(&parent_span) 24 | //! .tag(Tag::new("key", "value")) 25 | //! .start_with_state(()); 26 | //! 27 | //! child_span.log(|log| { 28 | //! log.error().message("a log message"); 29 | //! }); 30 | //! } // The "child" span dropped and will be sent to `span_rx` 31 | //! } // The "parent" span dropped and will be sent to `span_rx` 32 | //! 33 | //! // Outputs finished spans to the standard output 34 | //! while let Ok(span) = span_rx.try_recv() { 35 | //! println!("# SPAN: {:?}", span); 36 | //! } 37 | //! ``` 38 | //! 39 | //! As an actual usage example of the crate and an implementation of the [OpenTracing] API, 40 | //! it may be helpful to looking at [rustracing_jaeger] crate. 41 | //! 42 | //! # References 43 | //! 44 | //! - [The OpenTracing Semantic Specification (v1.1)][specification] 45 | //! 46 | //! [opentracing]: http://opentracing.io/ 47 | //! [specification]: https://github.com/opentracing/specification/blob/master/specification.md 48 | //! [rustracing_jaeger]: https://github.com/sile/rustracing_jaeger 49 | #![warn(missing_docs)] 50 | #![allow(clippy::new_ret_no_self)] 51 | #[macro_use] 52 | extern crate trackable; 53 | 54 | pub use crate::error::{Error, ErrorKind}; 55 | pub use crate::tracer::Tracer; 56 | 57 | pub mod carrier; 58 | pub mod convert; 59 | pub mod log; 60 | pub mod sampler; 61 | pub mod span; 62 | pub mod tag; 63 | 64 | mod error; 65 | mod tracer; 66 | 67 | /// This crate specific `Result` type. 68 | pub type Result = std::result::Result; 69 | 70 | #[cfg(test)] 71 | mod tests { 72 | use super::*; 73 | use crate::sampler::AllSampler; 74 | use crate::tag::{StdTag, Tag}; 75 | use std::thread; 76 | use std::time::Duration; 77 | 78 | #[test] 79 | fn it_works() { 80 | let (span_tx, span_rx) = crossbeam_channel::bounded(10); 81 | let tracer = Tracer::with_sender(AllSampler, span_tx); 82 | { 83 | let span = tracer.span("it_works").start_with_state(()); 84 | let mut child = span.child("child", |options| options.start_with_state(())); 85 | child.set_tags(|| StdTag::peer_addr("127.0.0.1:80".parse().unwrap())); 86 | } 87 | 88 | let span = span_rx.try_recv().unwrap(); 89 | assert_eq!(span.operation_name(), "child"); 90 | 91 | let span = span_rx.try_recv().unwrap(); 92 | assert_eq!(span.operation_name(), "it_works"); 93 | } 94 | 95 | #[test] 96 | fn example_code_works() { 97 | // Creates a tracer 98 | let (span_tx, span_rx) = crossbeam_channel::bounded(10); 99 | let tracer = Tracer::with_sender(AllSampler, span_tx); 100 | { 101 | // Starts "parent" span 102 | let parent_span = tracer.span("parent").start_with_state(()); 103 | thread::sleep(Duration::from_millis(10)); 104 | { 105 | // Starts "child" span 106 | let mut child_span = tracer 107 | .span("child_span") 108 | .child_of(&parent_span) 109 | .tag(Tag::new("key", "value")) 110 | .start_with_state(()); 111 | 112 | child_span.log(|log| { 113 | log.error().message("a log message"); 114 | }); 115 | } // The "child" span dropped and will be sent to `span_rx` 116 | } // The "parent" span dropped and will be sent to `span_rx` 117 | 118 | // Outputs finished spans to the standard output 119 | let mut count = 0; 120 | while let Ok(span) = span_rx.try_recv() { 121 | println!("# SPAN: {:?}", span); 122 | count += 1; 123 | } 124 | assert_eq!(count, 2); 125 | } 126 | 127 | #[test] 128 | fn nonblocking_on_full_queue() { 129 | let (span_tx, span_rx) = crossbeam_channel::bounded(2); 130 | let tracer = Tracer::with_sender(AllSampler, span_tx); 131 | { 132 | let span = tracer.span("first").start_with_state(()); 133 | let mut child = span.child("second", |options| options.start_with_state(())); 134 | child.set_tags(|| StdTag::peer_addr("127.0.0.1:80".parse().unwrap())); 135 | let _ = tracer.span("third").start_with_state(()); 136 | } // All spans dropped but only two ones will be sent to `span_rx` due to capacity limit, others are lost 137 | 138 | // If the code continues, there was no blocking operation while sending span to the channel 139 | assert!(span_rx.is_full()); 140 | assert_eq!(span_rx.len(), 2); 141 | 142 | let span = span_rx.try_recv().unwrap(); 143 | assert_eq!(span.operation_name(), "third"); 144 | 145 | let span = span_rx.try_recv().unwrap(); 146 | assert_eq!(span.operation_name(), "second"); 147 | 148 | assert!(span_rx.is_empty()); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/log.rs: -------------------------------------------------------------------------------- 1 | //! Span log. 2 | #[cfg(feature = "stacktrace")] 3 | use backtrace::Backtrace; 4 | use std::borrow::Cow; 5 | use std::time::SystemTime; 6 | 7 | /// Span log builder. 8 | #[derive(Debug)] 9 | pub struct LogBuilder { 10 | fields: Vec, 11 | time: Option, 12 | } 13 | impl LogBuilder { 14 | /// Adds the field. 15 | pub fn field>(&mut self, field: T) -> &mut Self { 16 | self.fields.push(field.into()); 17 | self 18 | } 19 | 20 | /// Sets the value of timestamp to `time`. 21 | pub fn time(&mut self, time: SystemTime) -> &mut Self { 22 | self.time = Some(time); 23 | self 24 | } 25 | 26 | /// Returns a specialized builder for the standard log fields. 27 | pub fn std(&mut self) -> StdLogFieldsBuilder { 28 | StdLogFieldsBuilder(self) 29 | } 30 | 31 | /// Returns a specialized builder for the standard error log fields. 32 | pub fn error(&mut self) -> StdErrorLogFieldsBuilder { 33 | self.field(LogField::new("event", "error")); 34 | StdErrorLogFieldsBuilder(self) 35 | } 36 | 37 | pub(crate) fn new() -> Self { 38 | LogBuilder { 39 | fields: Vec::new(), 40 | time: None, 41 | } 42 | } 43 | 44 | pub(crate) fn finish(mut self) -> Option { 45 | if self.fields.is_empty() { 46 | None 47 | } else { 48 | self.fields.reverse(); 49 | self.fields.sort_by(|a, b| a.name.cmp(&b.name)); 50 | self.fields.dedup_by(|a, b| a.name == b.name); 51 | Some(Log { 52 | fields: self.fields, 53 | time: self.time.unwrap_or_else(SystemTime::now), 54 | }) 55 | } 56 | } 57 | } 58 | 59 | /// Span log. 60 | #[derive(Debug, Clone)] 61 | pub struct Log { 62 | fields: Vec, 63 | time: SystemTime, 64 | } 65 | impl Log { 66 | /// Returns the fields of this log. 67 | pub fn fields(&self) -> &[LogField] { 68 | &self.fields 69 | } 70 | 71 | /// Returns the timestamp of this log. 72 | pub fn time(&self) -> SystemTime { 73 | self.time 74 | } 75 | } 76 | 77 | /// Span log field. 78 | #[derive(Debug, Clone)] 79 | pub struct LogField { 80 | name: Cow<'static, str>, 81 | value: Cow<'static, str>, 82 | } 83 | impl LogField { 84 | /// Makes a new `LogField` instance. 85 | pub fn new(name: N, value: V) -> Self 86 | where 87 | N: Into>, 88 | V: Into>, 89 | { 90 | LogField { 91 | name: name.into(), 92 | value: value.into(), 93 | } 94 | } 95 | 96 | /// Returns the name of this field. 97 | pub fn name(&self) -> &str { 98 | self.name.as_ref() 99 | } 100 | 101 | /// Returns the value of this field. 102 | pub fn value(&self) -> &str { 103 | self.value.as_ref() 104 | } 105 | } 106 | impl From<(N, V)> for LogField 107 | where 108 | N: Into>, 109 | V: Into>, 110 | { 111 | fn from((n, v): (N, V)) -> Self { 112 | LogField::new(n, v) 113 | } 114 | } 115 | 116 | /// A specialized span log builder for [the standard log fields]. 117 | /// 118 | /// [the standard log fields]: https://github.com/opentracing/specification/blob/master/semantic_conventions.md#log-fields-table 119 | #[derive(Debug)] 120 | pub struct StdLogFieldsBuilder<'a>(&'a mut LogBuilder); 121 | impl<'a> StdLogFieldsBuilder<'a> { 122 | /// Adds the field `LogField::new("event", event)`. 123 | /// 124 | /// `event` is a stable identifier for some notable moment in the lifetime of a Span. 125 | /// For instance, a mutex lock acquisition or release or the sorts of lifetime events 126 | /// in a browser page load described in the [Performance.timing] specification. 127 | /// 128 | /// E.g., from Zipkin, `"cs"`, `"sr"`, `"ss"`, or `"cr"`. 129 | /// Or, more generally, `"initialized"` or `"timed out"`. 130 | /// 131 | /// [Performance.timing]: https://developer.mozilla.org/en-US/docs/Web/API/PerformanceTiming 132 | pub fn event(&mut self, event: T) -> &mut Self 133 | where 134 | T: Into>, 135 | { 136 | self.0.field(LogField::new("event", event)); 137 | self 138 | } 139 | 140 | /// Adds the field `LogField::new("message", message)`. 141 | /// 142 | /// `message` is a concise, human-readable, one-line message explaining the event. 143 | /// 144 | /// E.g., `"Could not connect to backend"`, `"Cache invalidation succeeded"` 145 | pub fn message(&mut self, message: T) -> &mut Self 146 | where 147 | T: Into>, 148 | { 149 | self.0.field(LogField::new("message", message)); 150 | self 151 | } 152 | 153 | #[cfg(feature = "stacktrace")] 154 | /// Adds the field `LogField::new("stack", {stack trace})`. 155 | pub fn stack(&mut self) -> &mut Self { 156 | self.0 157 | .field(LogField::new("stack", format!("{:?}", Backtrace::new()))); 158 | self 159 | } 160 | } 161 | 162 | /// A specialized span log builder for [the standard error log fields]. 163 | /// 164 | /// This builder automatically inserts the field `LogField::new("event", "error")`. 165 | /// 166 | /// [the standard error log fields]: https://github.com/opentracing/specification/blob/master/semantic_conventions.md#log-fields-table 167 | #[derive(Debug)] 168 | pub struct StdErrorLogFieldsBuilder<'a>(&'a mut LogBuilder); 169 | impl<'a> StdErrorLogFieldsBuilder<'a> { 170 | /// Adds the field `LogField::new("error.kind", kind)`. 171 | /// 172 | /// `kind` is the type or "kind" of an error. 173 | /// 174 | /// E.g., `"Exception"`, `"OSError"` 175 | pub fn kind(&mut self, kind: T) -> &mut Self 176 | where 177 | T: Into>, 178 | { 179 | self.0.field(LogField::new("error.kind", kind)); 180 | self 181 | } 182 | 183 | /// Adds the field `LogField::new("message", message)`. 184 | /// 185 | /// `message` is a concise, human-readable, one-line message explaining the event. 186 | /// 187 | /// E.g., `"Could not connect to backend"`, `"Cache invalidation succeeded"` 188 | pub fn message(&mut self, message: T) -> &mut Self 189 | where 190 | T: Into>, 191 | { 192 | self.0.field(LogField::new("message", message)); 193 | self 194 | } 195 | 196 | #[cfg(feature = "stacktrace")] 197 | /// Adds the field `LogField::new("stack", {stack trace})`. 198 | pub fn stack(&mut self) -> &mut Self { 199 | self.0 200 | .field(LogField::new("stack", format!("{:?}", Backtrace::new()))); 201 | self 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /src/sampler.rs: -------------------------------------------------------------------------------- 1 | //! `Sampler` trait and its built-in implementations. 2 | use crate::span::CandidateSpan; 3 | use crate::{ErrorKind, Result}; 4 | use rand::{self, Rng}; 5 | 6 | /// `Sampler` decides whether a new trace should be sampled or not. 7 | pub trait Sampler { 8 | /// This method decides whether a trace with given `span` should be sampled. 9 | fn is_sampled(&self, span: &CandidateSpan) -> bool; 10 | 11 | /// Returns the sampler that samples a trace if `self` or `other` decides to sample it. 12 | fn or(self, other: U) -> OrSampler 13 | where 14 | Self: Sized, 15 | U: Sampler, 16 | { 17 | OrSampler(self, other) 18 | } 19 | 20 | /// Returns the sampler that samples a trace if both of `self` and `other` decides to sample it. 21 | fn and(self, other: U) -> AndSampler 22 | where 23 | Self: Sized, 24 | U: Sampler, 25 | { 26 | AndSampler(self, other) 27 | } 28 | 29 | /// Converts into `BoxSampler`. 30 | fn boxed(self) -> BoxSampler 31 | where 32 | Self: Sized + Send + Sync + 'static, 33 | { 34 | Box::new(self) 35 | } 36 | } 37 | impl Sampler for BoxSampler { 38 | fn is_sampled(&self, span: &CandidateSpan) -> bool { 39 | (**self).is_sampled(span) 40 | } 41 | fn boxed(self) -> BoxSampler 42 | where 43 | Self: Sized + Send + 'static, 44 | { 45 | self 46 | } 47 | } 48 | 49 | /// Boxed version of `Sampler`. 50 | pub type BoxSampler = Box + Send + Sync + 'static>; 51 | 52 | /// This samples a certain percentage of traces. 53 | #[derive(Debug, Clone)] 54 | pub struct ProbabilisticSampler { 55 | sampling_rate: f64, 56 | } 57 | impl ProbabilisticSampler { 58 | /// Makes a new `ProbabilisticSampler` instance. 59 | /// 60 | /// # Errors 61 | /// 62 | /// If `sampling_rate` is not in the range `0.0...1.0`, 63 | /// it will return an error with the kind `ErrorKind::InvalidInput`. 64 | pub fn new(sampling_rate: f64) -> Result { 65 | track_assert!(0.0 <= sampling_rate, ErrorKind::InvalidInput); 66 | track_assert!(sampling_rate <= 1.0, ErrorKind::InvalidInput); 67 | Ok(ProbabilisticSampler { sampling_rate }) 68 | } 69 | } 70 | impl Sampler for ProbabilisticSampler { 71 | fn is_sampled(&self, _span: &CandidateSpan) -> bool { 72 | rand::thread_rng().gen_range(0.0..1.0) < self.sampling_rate 73 | } 74 | } 75 | 76 | /// This samples traces which have one or more references. 77 | #[derive(Debug, Clone)] 78 | pub struct PassiveSampler; 79 | impl Sampler for PassiveSampler { 80 | fn is_sampled(&self, span: &CandidateSpan) -> bool { 81 | !span.references().is_empty() 82 | } 83 | } 84 | 85 | /// This samples no traces. 86 | #[derive(Debug, Clone)] 87 | pub struct NullSampler; 88 | impl Sampler for NullSampler { 89 | fn is_sampled(&self, _span: &CandidateSpan) -> bool { 90 | false 91 | } 92 | } 93 | 94 | /// This samples all traces. 95 | #[derive(Debug, Clone)] 96 | pub struct AllSampler; 97 | impl Sampler for AllSampler { 98 | fn is_sampled(&self, _span: &CandidateSpan) -> bool { 99 | true 100 | } 101 | } 102 | 103 | /// `or` combinator. 104 | #[derive(Debug, Clone)] 105 | pub struct OrSampler(A, B); 106 | impl Sampler for OrSampler 107 | where 108 | A: Sampler, 109 | B: Sampler, 110 | { 111 | fn is_sampled(&self, span: &CandidateSpan) -> bool { 112 | self.0.is_sampled(span) || self.1.is_sampled(span) 113 | } 114 | } 115 | 116 | /// `and` combinator. 117 | #[derive(Debug, Clone)] 118 | pub struct AndSampler(A, B); 119 | impl Sampler for AndSampler 120 | where 121 | A: Sampler, 122 | B: Sampler, 123 | { 124 | fn is_sampled(&self, span: &CandidateSpan) -> bool { 125 | self.0.is_sampled(span) && self.1.is_sampled(span) 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/span.rs: -------------------------------------------------------------------------------- 1 | //! Span. 2 | use crate::carrier; 3 | use crate::convert::MaybeAsRef; 4 | use crate::log::{Log, LogBuilder, StdErrorLogFieldsBuilder}; 5 | use crate::sampler::{AllSampler, Sampler}; 6 | use crate::tag::{StdTag, Tag, TagValue}; 7 | use crate::Result; 8 | use std::borrow::Cow; 9 | use std::io::{Read, Write}; 10 | use std::time::SystemTime; 11 | 12 | /// Finished span receiver. 13 | pub type SpanReceiver = crossbeam_channel::Receiver>; 14 | /// Sender of finished spans to the destination channel. 15 | pub type SpanSender = crossbeam_channel::Sender>; 16 | 17 | /// Span. 18 | /// 19 | /// When this span is dropped, it will be converted to `FinishedSpan` and 20 | /// it will be sent to the associated `SpanReceiver`. 21 | #[derive(Debug)] 22 | pub struct Span(Option>); 23 | impl Span { 24 | /// Makes an inactive span. 25 | /// 26 | /// This span is never traced. 27 | /// 28 | /// # Examples 29 | /// 30 | /// ``` 31 | /// use rustracing::span::Span; 32 | /// 33 | /// let span = Span::<()>::inactive(); 34 | /// assert!(! span.is_sampled()); 35 | /// ``` 36 | pub fn inactive() -> Self { 37 | Span(None) 38 | } 39 | 40 | /// Returns a handle of this span. 41 | pub fn handle(&self) -> SpanHandle 42 | where 43 | T: Clone, 44 | { 45 | SpanHandle( 46 | self.0 47 | .as_ref() 48 | .map(|inner| (inner.context.clone(), inner.span_tx.clone())), 49 | ) 50 | } 51 | 52 | /// Returns `true` if this span is sampled (i.e., being traced). 53 | pub fn is_sampled(&self) -> bool { 54 | self.0.is_some() 55 | } 56 | 57 | /// Returns the context of this span. 58 | pub fn context(&self) -> Option<&SpanContext> { 59 | self.0.as_ref().map(|x| &x.context) 60 | } 61 | 62 | /// Sets the operation name of this span. 63 | pub fn set_operation_name(&mut self, f: F) 64 | where 65 | F: FnOnce() -> N, 66 | N: Into>, 67 | { 68 | if let Some(inner) = self.0.as_mut() { 69 | inner.operation_name = f().into(); 70 | } 71 | } 72 | 73 | /// Sets the start time of this span. 74 | pub fn set_start_time(&mut self, f: F) 75 | where 76 | F: FnOnce() -> SystemTime, 77 | { 78 | if let Some(inner) = self.0.as_mut() { 79 | inner.start_time = f(); 80 | } 81 | } 82 | 83 | /// Sets the finish time of this span. 84 | pub fn set_finish_time(&mut self, f: F) 85 | where 86 | F: FnOnce() -> SystemTime, 87 | { 88 | if let Some(inner) = self.0.as_mut() { 89 | inner.finish_time = Some(f()); 90 | } 91 | } 92 | 93 | /// Sets the tag to this span. 94 | pub fn set_tag(&mut self, f: F) 95 | where 96 | F: FnOnce() -> Tag, 97 | { 98 | use std::iter::once; 99 | self.set_tags(|| once(f())); 100 | } 101 | 102 | /// Sets the tags to this span. 103 | pub fn set_tags(&mut self, f: F) 104 | where 105 | F: FnOnce() -> I, 106 | I: IntoIterator, 107 | { 108 | if let Some(inner) = self.0.as_mut() { 109 | for tag in f() { 110 | inner.tags.retain(|x| x.name() != tag.name()); 111 | inner.tags.push(tag); 112 | } 113 | } 114 | } 115 | 116 | /// Sets the baggage item to this span. 117 | pub fn set_baggage_item(&mut self, f: F) 118 | where 119 | F: FnOnce() -> BaggageItem, 120 | { 121 | if let Some(inner) = self.0.as_mut() { 122 | let item = f(); 123 | inner.context.baggage_items.retain(|x| x.name != item.name); 124 | inner.context.baggage_items.push(item); 125 | } 126 | } 127 | 128 | /// Gets the baggage item that has the name `name`. 129 | pub fn get_baggage_item(&self, name: &str) -> Option<&BaggageItem> { 130 | if let Some(inner) = self.0.as_ref() { 131 | inner.context.baggage_items.iter().find(|x| x.name == name) 132 | } else { 133 | None 134 | } 135 | } 136 | 137 | /// Logs structured data. 138 | pub fn log(&mut self, f: F) 139 | where 140 | F: FnOnce(&mut LogBuilder), 141 | { 142 | if let Some(inner) = self.0.as_mut() { 143 | let mut builder = LogBuilder::new(); 144 | f(&mut builder); 145 | if let Some(log) = builder.finish() { 146 | inner.logs.push(log); 147 | } 148 | } 149 | } 150 | 151 | /// Logs an error. 152 | /// 153 | /// This is a simple wrapper of `log` method 154 | /// except that the `StdTag::error()` tag will be set in this method. 155 | pub fn error_log(&mut self, f: F) 156 | where 157 | F: FnOnce(&mut StdErrorLogFieldsBuilder), 158 | { 159 | if let Some(inner) = self.0.as_mut() { 160 | let mut builder = LogBuilder::new(); 161 | f(&mut builder.error()); 162 | if let Some(log) = builder.finish() { 163 | inner.logs.push(log); 164 | } 165 | if !inner.tags.iter().any(|x| x.name() == "error") { 166 | inner.tags.push(StdTag::error()); 167 | } 168 | } 169 | } 170 | 171 | /// Starts a `ChildOf` span if this span is sampled. 172 | pub fn child(&self, operation_name: N, f: F) -> Span 173 | where 174 | N: Into>, 175 | T: Clone, 176 | F: FnOnce(StartSpanOptions) -> Span, 177 | { 178 | self.handle().child(operation_name, f) 179 | } 180 | 181 | /// Starts a `FollowsFrom` span if this span is sampled. 182 | pub fn follower(&self, operation_name: N, f: F) -> Span 183 | where 184 | N: Into>, 185 | T: Clone, 186 | F: FnOnce(StartSpanOptions) -> Span, 187 | { 188 | self.handle().follower(operation_name, f) 189 | } 190 | 191 | pub(crate) fn new( 192 | operation_name: Cow<'static, str>, 193 | start_time: SystemTime, 194 | references: Vec>, 195 | tags: Vec, 196 | state: T, 197 | baggage_items: Vec, 198 | span_tx: SpanSender, 199 | ) -> Self { 200 | let context = SpanContext::new(state, baggage_items); 201 | let inner = SpanInner { 202 | operation_name, 203 | start_time, 204 | finish_time: None, 205 | references, 206 | tags, 207 | logs: Vec::new(), 208 | context, 209 | span_tx, 210 | }; 211 | Span(Some(inner)) 212 | } 213 | } 214 | impl Drop for Span { 215 | fn drop(&mut self) { 216 | if let Some(inner) = self.0.take() { 217 | let finished = FinishedSpan { 218 | operation_name: inner.operation_name, 219 | start_time: inner.start_time, 220 | finish_time: inner.finish_time.unwrap_or_else(SystemTime::now), 221 | references: inner.references, 222 | tags: inner.tags, 223 | logs: inner.logs, 224 | context: inner.context, 225 | }; 226 | let _ = inner.span_tx.try_send(finished); 227 | } 228 | } 229 | } 230 | impl MaybeAsRef> for Span { 231 | fn maybe_as_ref(&self) -> Option<&SpanContext> { 232 | self.context() 233 | } 234 | } 235 | 236 | #[derive(Debug)] 237 | struct SpanInner { 238 | operation_name: Cow<'static, str>, 239 | start_time: SystemTime, 240 | finish_time: Option, 241 | references: Vec>, 242 | tags: Vec, 243 | logs: Vec, 244 | context: SpanContext, 245 | span_tx: SpanSender, 246 | } 247 | 248 | /// Finished span. 249 | #[derive(Debug)] 250 | pub struct FinishedSpan { 251 | operation_name: Cow<'static, str>, 252 | start_time: SystemTime, 253 | finish_time: SystemTime, 254 | references: Vec>, 255 | tags: Vec, 256 | logs: Vec, 257 | context: SpanContext, 258 | } 259 | impl FinishedSpan { 260 | /// Returns the operation name of this span. 261 | pub fn operation_name(&self) -> &str { 262 | self.operation_name.as_ref() 263 | } 264 | 265 | /// Returns the start time of this span. 266 | pub fn start_time(&self) -> SystemTime { 267 | self.start_time 268 | } 269 | 270 | /// Returns the finish time of this span. 271 | pub fn finish_time(&self) -> SystemTime { 272 | self.finish_time 273 | } 274 | 275 | /// Returns the logs recorded during this span. 276 | pub fn logs(&self) -> &[Log] { 277 | &self.logs 278 | } 279 | 280 | /// Returns the tags of this span. 281 | pub fn tags(&self) -> &[Tag] { 282 | &self.tags 283 | } 284 | 285 | /// Returns the references of this span. 286 | pub fn references(&self) -> &[SpanReference] { 287 | &self.references 288 | } 289 | 290 | /// Returns the context of this span. 291 | pub fn context(&self) -> &SpanContext { 292 | &self.context 293 | } 294 | } 295 | 296 | /// Span context. 297 | /// 298 | /// Each `SpanContext` encapsulates the following state: 299 | /// 300 | /// - `T`: OpenTracing-implementation-dependent state (for example, trace and span ids) needed to refer to a distinct `Span` across a process boundary 301 | /// - `BaggageItems`: These are just key:value pairs that cross process boundaries 302 | #[derive(Debug, Clone)] 303 | pub struct SpanContext { 304 | state: T, 305 | baggage_items: Vec, 306 | } 307 | impl SpanContext { 308 | /// Makes a new `SpanContext` instance. 309 | pub fn new(state: T, mut baggage_items: Vec) -> Self { 310 | baggage_items.reverse(); 311 | baggage_items.sort_by(|a, b| a.name().cmp(b.name())); 312 | baggage_items.dedup_by(|a, b| a.name() == b.name()); 313 | SpanContext { 314 | state, 315 | baggage_items, 316 | } 317 | } 318 | 319 | /// Returns the implementation-dependent state of this context. 320 | pub fn state(&self) -> &T { 321 | &self.state 322 | } 323 | 324 | /// Returns the baggage items associated with this context. 325 | pub fn baggage_items(&self) -> &[BaggageItem] { 326 | &self.baggage_items 327 | } 328 | 329 | /// Injects this context to the **Text Map** `carrier`. 330 | pub fn inject_to_text_map(&self, carrier: &mut C) -> Result<()> 331 | where 332 | C: carrier::TextMap, 333 | T: carrier::InjectToTextMap, 334 | { 335 | track!(T::inject_to_text_map(self, carrier)) 336 | } 337 | 338 | /// Injects this context to the **HTTP Header** `carrier`. 339 | pub fn inject_to_http_header(&self, carrier: &mut C) -> Result<()> 340 | where 341 | C: carrier::SetHttpHeaderField, 342 | T: carrier::InjectToHttpHeader, 343 | { 344 | track!(T::inject_to_http_header(self, carrier)) 345 | } 346 | 347 | /// Injects this context to the **Binary** `carrier`. 348 | pub fn inject_to_binary(&self, carrier: &mut C) -> Result<()> 349 | where 350 | C: Write, 351 | T: carrier::InjectToBinary, 352 | { 353 | track!(T::inject_to_binary(self, carrier)) 354 | } 355 | 356 | /// Extracts a context from the **Text Map** `carrier`. 357 | pub fn extract_from_text_map(carrier: &C) -> Result> 358 | where 359 | C: carrier::TextMap, 360 | T: carrier::ExtractFromTextMap, 361 | { 362 | track!(T::extract_from_text_map(carrier)) 363 | } 364 | 365 | /// Extracts a context from the **HTTP Header** `carrier`. 366 | pub fn extract_from_http_header<'a, C>(carrier: &'a C) -> Result> 367 | where 368 | C: carrier::IterHttpHeaderFields<'a>, 369 | T: carrier::ExtractFromHttpHeader<'a, C>, 370 | { 371 | track!(T::extract_from_http_header(carrier)) 372 | } 373 | 374 | /// Extracts a context from the **Binary** `carrier`. 375 | pub fn extract_from_binary(carrier: &mut C) -> Result> 376 | where 377 | C: Read, 378 | T: carrier::ExtractFromBinary, 379 | { 380 | track!(T::extract_from_binary(carrier)) 381 | } 382 | } 383 | impl MaybeAsRef> for SpanContext { 384 | fn maybe_as_ref(&self) -> Option<&Self> { 385 | Some(self) 386 | } 387 | } 388 | 389 | /// Baggage item. 390 | /// 391 | /// `BaggageItem`s are key:value string pairs that apply to a `Span`, its `SpanContext`, 392 | /// and all `Span`s which directly or transitively reference the local `Span`. 393 | /// That is, `BaggageItem`s propagate in-band along with the trace itself. 394 | /// 395 | /// `BaggageItem`s enable powerful functionality given a full-stack OpenTracing integration 396 | /// (for example, arbitrary application data from a mobile app can make it, transparently, 397 | /// all the way into the depths of a storage system), 398 | /// and with it some powerful costs: use this feature with care. 399 | /// 400 | /// Use this feature thoughtfully and with care. 401 | /// Every key and value is copied into every local and remote child of the associated `Span`, 402 | /// and that can add up to a lot of network and cpu overhead. 403 | #[derive(Debug, Clone)] 404 | pub struct BaggageItem { 405 | name: String, 406 | value: String, 407 | } 408 | impl BaggageItem { 409 | /// Makes a new `BaggageItem` instance. 410 | pub fn new(name: &str, value: &str) -> Self { 411 | BaggageItem { 412 | name: name.to_owned(), 413 | value: value.to_owned(), 414 | } 415 | } 416 | 417 | /// Returns the name of this item. 418 | pub fn name(&self) -> &str { 419 | &self.name 420 | } 421 | 422 | /// Returns the value of this item. 423 | pub fn value(&self) -> &str { 424 | &self.value 425 | } 426 | } 427 | 428 | /// Span reference. 429 | #[derive(Debug, Clone)] 430 | #[allow(missing_docs)] 431 | pub enum SpanReference { 432 | ChildOf(T), 433 | FollowsFrom(T), 434 | } 435 | impl SpanReference { 436 | /// Returns the span context state of this reference. 437 | pub fn span(&self) -> &T { 438 | match *self { 439 | SpanReference::ChildOf(ref x) | SpanReference::FollowsFrom(ref x) => x, 440 | } 441 | } 442 | 443 | /// Returns `true` if this is a `ChildOf` reference. 444 | pub fn is_child_of(&self) -> bool { 445 | matches!(*self, SpanReference::ChildOf(_)) 446 | } 447 | 448 | /// Returns `true` if this is a `FollowsFrom` reference. 449 | pub fn is_follows_from(&self) -> bool { 450 | matches!(*self, SpanReference::FollowsFrom(_)) 451 | } 452 | } 453 | 454 | /// Candidate span for tracing. 455 | #[derive(Debug)] 456 | pub struct CandidateSpan<'a, T: 'a> { 457 | tags: &'a [Tag], 458 | references: &'a [SpanReference], 459 | baggage_items: &'a [BaggageItem], 460 | } 461 | impl<'a, T: 'a> CandidateSpan<'a, T> { 462 | /// Returns the tags of this span. 463 | pub fn tags(&self) -> &[Tag] { 464 | self.tags 465 | } 466 | 467 | /// Returns the references of this span. 468 | pub fn references(&self) -> &[SpanReference] { 469 | self.references 470 | } 471 | 472 | /// Returns the baggage items of this span. 473 | pub fn baggage_items(&self) -> &[BaggageItem] { 474 | self.baggage_items 475 | } 476 | } 477 | 478 | /// Options for starting a span. 479 | #[derive(Debug)] 480 | pub struct StartSpanOptions<'a, S: 'a, T: 'a> { 481 | operation_name: Cow<'static, str>, 482 | start_time: Option, 483 | tags: Vec, 484 | references: Vec>, 485 | baggage_items: Vec, 486 | span_tx: &'a SpanSender, 487 | sampler: &'a S, 488 | } 489 | impl<'a, S: 'a, T: 'a> StartSpanOptions<'a, S, T> 490 | where 491 | S: Sampler, 492 | { 493 | /// Sets the start time of this span. 494 | pub fn start_time(mut self, time: SystemTime) -> Self { 495 | self.start_time = Some(time); 496 | self 497 | } 498 | 499 | /// Sets the tag to this span. 500 | pub fn tag(mut self, tag: Tag) -> Self { 501 | self.tags.push(tag); 502 | self 503 | } 504 | 505 | /// Adds the `ChildOf` reference to this span. 506 | pub fn child_of(mut self, context: &C) -> Self 507 | where 508 | C: MaybeAsRef>, 509 | T: Clone, 510 | { 511 | if let Some(context) = context.maybe_as_ref() { 512 | let reference = SpanReference::ChildOf(context.state().clone()); 513 | self.references.push(reference); 514 | self.baggage_items 515 | .extend(context.baggage_items().iter().cloned()); 516 | } 517 | self 518 | } 519 | 520 | /// Adds the `FollowsFrom` reference to this span. 521 | pub fn follows_from(mut self, context: &C) -> Self 522 | where 523 | C: MaybeAsRef>, 524 | T: Clone, 525 | { 526 | if let Some(context) = context.maybe_as_ref() { 527 | let reference = SpanReference::FollowsFrom(context.state().clone()); 528 | self.references.push(reference); 529 | self.baggage_items 530 | .extend(context.baggage_items().iter().cloned()); 531 | } 532 | self 533 | } 534 | 535 | /// Starts a new span. 536 | pub fn start(mut self) -> Span 537 | where 538 | T: for<'b> From>, 539 | { 540 | self.normalize(); 541 | if !self.is_sampled() { 542 | return Span(None); 543 | } 544 | let state = T::from(self.span()); 545 | Span::new( 546 | self.operation_name, 547 | self.start_time.unwrap_or_else(SystemTime::now), 548 | self.references, 549 | self.tags, 550 | state, 551 | self.baggage_items, 552 | self.span_tx.clone(), 553 | ) 554 | } 555 | 556 | /// Starts a new span with the explicit `state`. 557 | pub fn start_with_state(mut self, state: T) -> Span { 558 | self.normalize(); 559 | if !self.is_sampled() { 560 | return Span(None); 561 | } 562 | Span::new( 563 | self.operation_name, 564 | self.start_time.unwrap_or_else(SystemTime::now), 565 | self.references, 566 | self.tags, 567 | state, 568 | self.baggage_items, 569 | self.span_tx.clone(), 570 | ) 571 | } 572 | 573 | pub(crate) fn new(operation_name: N, span_tx: &'a SpanSender, sampler: &'a S) -> Self 574 | where 575 | N: Into>, 576 | { 577 | StartSpanOptions { 578 | operation_name: operation_name.into(), 579 | start_time: None, 580 | tags: Vec::new(), 581 | references: Vec::new(), 582 | baggage_items: Vec::new(), 583 | span_tx, 584 | sampler, 585 | } 586 | } 587 | 588 | fn normalize(&mut self) { 589 | self.tags.reverse(); 590 | self.tags.sort_by(|a, b| a.name().cmp(b.name())); 591 | self.tags.dedup_by(|a, b| a.name() == b.name()); 592 | 593 | self.baggage_items.reverse(); 594 | self.baggage_items.sort_by(|a, b| a.name().cmp(b.name())); 595 | self.baggage_items.dedup_by(|a, b| a.name() == b.name()); 596 | } 597 | 598 | fn span(&self) -> CandidateSpan { 599 | CandidateSpan { 600 | references: &self.references, 601 | tags: &self.tags, 602 | baggage_items: &self.baggage_items, 603 | } 604 | } 605 | 606 | fn is_sampled(&self) -> bool { 607 | if let Some(&TagValue::Integer(n)) = self 608 | .tags 609 | .iter() 610 | .find(|t| t.name() == "sampling.priority") 611 | .map(|t| t.value()) 612 | { 613 | n > 0 614 | } else { 615 | self.sampler.is_sampled(&self.span()) 616 | } 617 | } 618 | } 619 | 620 | /// Immutable handle of `Span`. 621 | #[derive(Debug, Clone)] 622 | pub struct SpanHandle(Option<(SpanContext, SpanSender)>); 623 | impl SpanHandle { 624 | /// Returns `true` if this span is sampled (i.e., being traced). 625 | pub fn is_sampled(&self) -> bool { 626 | self.0.is_some() 627 | } 628 | 629 | /// Returns the context of this span. 630 | pub fn context(&self) -> Option<&SpanContext> { 631 | self.0.as_ref().map(|(context, _)| context) 632 | } 633 | 634 | /// Gets the baggage item that has the name `name`. 635 | pub fn get_baggage_item(&self, name: &str) -> Option<&BaggageItem> { 636 | if let Some(context) = self.context() { 637 | context.baggage_items.iter().find(|x| x.name == name) 638 | } else { 639 | None 640 | } 641 | } 642 | 643 | /// Starts a `ChildOf` span if this span is sampled. 644 | pub fn child(&self, operation_name: N, f: F) -> Span 645 | where 646 | N: Into>, 647 | T: Clone, 648 | F: FnOnce(StartSpanOptions) -> Span, 649 | { 650 | if let Some((context, span_tx)) = self.0.as_ref() { 651 | let options = 652 | StartSpanOptions::new(operation_name, span_tx, &AllSampler).child_of(context); 653 | f(options) 654 | } else { 655 | Span::inactive() 656 | } 657 | } 658 | 659 | /// Starts a `FollowsFrom` span if this span is sampled. 660 | pub fn follower(&self, operation_name: N, f: F) -> Span 661 | where 662 | N: Into>, 663 | T: Clone, 664 | F: FnOnce(StartSpanOptions) -> Span, 665 | { 666 | if let Some((context, span_tx)) = self.0.as_ref() { 667 | let options = 668 | StartSpanOptions::new(operation_name, span_tx, &AllSampler).follows_from(context); 669 | f(options) 670 | } else { 671 | Span::inactive() 672 | } 673 | } 674 | } 675 | -------------------------------------------------------------------------------- /src/tag.rs: -------------------------------------------------------------------------------- 1 | //! Span tag. 2 | use std::borrow::Cow; 3 | use std::net::{IpAddr, SocketAddr}; 4 | 5 | /// Span tag. 6 | #[derive(Debug, Clone)] 7 | pub struct Tag { 8 | name: Cow<'static, str>, 9 | value: TagValue, 10 | } 11 | impl Tag { 12 | /// # Examples 13 | /// 14 | /// ``` 15 | /// use rustracing::tag::{Tag, TagValue}; 16 | /// 17 | /// let tag = Tag::new("foo", "bar"); 18 | /// assert_eq!(tag.name(), "foo"); 19 | /// assert_eq!(tag.value(), &TagValue::from("bar")); 20 | /// ``` 21 | pub fn new(name: N, value: V) -> Self 22 | where 23 | N: Into>, 24 | V: Into, 25 | { 26 | Tag { 27 | name: name.into(), 28 | value: value.into(), 29 | } 30 | } 31 | 32 | /// Returns the name of this tag. 33 | pub fn name(&self) -> &str { 34 | self.name.as_ref() 35 | } 36 | 37 | /// Returns the value of this tag. 38 | pub fn value(&self) -> &TagValue { 39 | &self.value 40 | } 41 | } 42 | 43 | /// Span tag value. 44 | #[derive(Debug, Clone, PartialEq, PartialOrd)] 45 | #[allow(missing_docs)] 46 | pub enum TagValue { 47 | String(Cow<'static, str>), 48 | Boolean(bool), 49 | Integer(i64), 50 | Float(f64), 51 | } 52 | impl From<&'static str> for TagValue { 53 | fn from(f: &'static str) -> Self { 54 | TagValue::String(Cow::Borrowed(f)) 55 | } 56 | } 57 | impl From for TagValue { 58 | fn from(f: String) -> Self { 59 | TagValue::String(Cow::Owned(f)) 60 | } 61 | } 62 | impl From> for TagValue { 63 | fn from(f: Cow<'static, str>) -> Self { 64 | TagValue::String(f) 65 | } 66 | } 67 | impl From for TagValue { 68 | fn from(f: bool) -> Self { 69 | TagValue::Boolean(f) 70 | } 71 | } 72 | impl From for TagValue { 73 | fn from(f: i64) -> Self { 74 | TagValue::Integer(f) 75 | } 76 | } 77 | impl From for TagValue { 78 | fn from(f: f64) -> Self { 79 | TagValue::Float(f) 80 | } 81 | } 82 | 83 | /// [Standard span tags][tags]. 84 | /// [tags]: https://github.com/opentracing/specification/blob/master/semantic_conventions.md#span-tags-table 85 | #[derive(Debug)] 86 | pub struct StdTag; 87 | impl StdTag { 88 | /// Makes a `"component"` tag. 89 | /// 90 | /// It indicates the software package, framework, library, 91 | /// or module that generated the associated `Span`. 92 | /// 93 | /// E.g., `"grpc"`, `"django"`, `"JDBI"`. 94 | pub fn component(value: V) -> Tag 95 | where 96 | V: Into>, 97 | { 98 | Tag::new("component", value.into()) 99 | } 100 | 101 | /// Makes a `"db.instance"` tag. 102 | /// 103 | /// It indicates database instance name. 104 | /// 105 | /// E.g., In java, if the jdbc.url=`"jdbc:mysql://127.0.0.1:3306/customers"`, 106 | /// the instance name is `"customers"`. 107 | pub fn db_instance(value: V) -> Tag 108 | where 109 | V: Into>, 110 | { 111 | Tag::new("db.instance", value.into()) 112 | } 113 | 114 | /// Makes a `"db.statement"` tag. 115 | /// 116 | /// It indicates a database statement for the given database type. 117 | /// 118 | /// E.g., 119 | /// for db.type=`"sql"`, `"SELECT * FROM wuser_table"`; 120 | /// for db.type=`"redis"`, `"SET mykey 'WuValue'"`. 121 | pub fn db_statement(value: V) -> Tag 122 | where 123 | V: Into>, 124 | { 125 | Tag::new("db.statement", value.into()) 126 | } 127 | 128 | /// Makes a `"db.type"` tag. 129 | /// 130 | /// It indicates database type. 131 | /// 132 | /// For any SQL database, `"sql"`. 133 | /// For others, the lower-case database category, e.g. `"cassandra"`, `"hbase"`, or `"redis"`. 134 | pub fn db_type(value: V) -> Tag 135 | where 136 | V: Into>, 137 | { 138 | Tag::new("db.type", value.into()) 139 | } 140 | 141 | /// Makes a `"db.user"` tag. 142 | /// 143 | /// It indicates username for accessing database. 144 | /// 145 | /// E.g., `"readonly_user"` or `"reporting_user"` 146 | pub fn db_user(value: V) -> Tag 147 | where 148 | V: Into>, 149 | { 150 | Tag::new("db.user", value.into()) 151 | } 152 | 153 | /// Makes a `"error"` tag that has the value `true`. 154 | /// 155 | /// It indicates the application considers the operation represented by the `Span` to have failed. 156 | pub fn error() -> Tag { 157 | Tag::new("error", true) 158 | } 159 | 160 | /// Makes a `"http.method"` tag. 161 | /// 162 | /// It indicates HTTP method of the request for the associated `Span`. 163 | /// 164 | /// E.g., `"GET"`, `"POST"` 165 | pub fn http_method(value: V) -> Tag 166 | where 167 | V: Into>, 168 | { 169 | Tag::new("http.method", value.into()) 170 | } 171 | 172 | /// Makes a `"http.status_code"` tag. 173 | /// 174 | /// It indicates HTTP response status code for the associated `Span`. 175 | /// 176 | /// E.g., 200, 503, 404 177 | pub fn http_status_code(value: u16) -> Tag { 178 | Tag::new("http.status_code", i64::from(value)) 179 | } 180 | 181 | /// Makes a `"http.url"` tag. 182 | /// 183 | /// It indicates URL of the request being handled in this segment of the trace, in standard URI format. 184 | /// 185 | /// E.g., `"https://domain.net/path/to?resource=here"` 186 | pub fn http_url(value: V) -> Tag 187 | where 188 | V: Into>, 189 | { 190 | Tag::new("http.url", value.into()) 191 | } 192 | 193 | /// Makes a `"message_bus.destination" tag. 194 | /// 195 | /// It indicates an address at which messages can be exchanged. 196 | /// 197 | /// E.g. A Kafka record has an associated `"topic name"` that can be extracted by 198 | /// the instrumented producer or consumer and stored using this tag. 199 | pub fn message_bus_destination(value: V) -> Tag 200 | where 201 | V: Into>, 202 | { 203 | Tag::new("message_bus.destination", value.into()) 204 | } 205 | 206 | /// Makes a `"peer.address"` tag. 207 | /// 208 | /// It indicates remote "address", suitable for use in a networking client library. 209 | /// 210 | /// This may be a `"ip:port"`, a bare `"hostname"`, a FQDN, 211 | /// or even a JDBC substring like `"mysql://prod-db:3306"`. 212 | pub fn peer_address(value: V) -> Tag 213 | where 214 | V: Into>, 215 | { 216 | Tag::new("peer.address", value.into()) 217 | } 218 | 219 | /// Makes a `"peer.hostname"` tag. 220 | /// 221 | /// It indicates remote hostname. 222 | /// 223 | /// E.g., `"opentracing.io"`, `"internal.dns.name"` 224 | pub fn peer_hostname(value: V) -> Tag 225 | where 226 | V: Into>, 227 | { 228 | Tag::new("peer.hostname", value.into()) 229 | } 230 | 231 | /// Makes a `"peer.ipXX"` and `"peer.port"` tags. 232 | pub fn peer_addr(value: SocketAddr) -> Vec { 233 | vec![Self::peer_ip(value.ip()), Self::peer_port(value.port())] 234 | } 235 | 236 | /// Makes a tag which has the name either `"peer.ipv4"` or `"peer.ipv6"` depending on the value. 237 | /// 238 | /// It indicates remote IP address. 239 | /// 240 | /// E.g., `"127.0.0.1"`, `"2001:0db8:85a3:0000:0000:8a2e:0370:7334"` 241 | pub fn peer_ip(value: IpAddr) -> Tag { 242 | match value { 243 | IpAddr::V4(v) => Tag::new("peer.ipv4", v.to_string()), 244 | IpAddr::V6(v) => Tag::new("peer.ipv6", v.to_string()), 245 | } 246 | } 247 | 248 | /// Makes a `"peer.port"` tag. 249 | /// 250 | /// It indicates remote port. 251 | /// 252 | /// E.g., `80` 253 | pub fn peer_port(value: u16) -> Tag { 254 | Tag::new("peer.port", i64::from(value)) 255 | } 256 | 257 | /// Makes a `"peer.service"` tag. 258 | /// 259 | /// It indicates remote service name (for some unspecified definition of `"service"`). 260 | /// 261 | /// E.g., `"elasticsearch"`, `"a_custom_microservice"`, `"memcache"` 262 | pub fn peer_service(value: V) -> Tag 263 | where 264 | V: Into>, 265 | { 266 | Tag::new("peer.service", value.into()) 267 | } 268 | 269 | /// Makes a `"samplingpriority"` tag. 270 | /// 271 | /// If greater than `0`, a hint to the `Tracer` to do its best to capture the trace. 272 | /// If `0`, a hint to the trace to not-capture the trace. 273 | /// If absent, the `Tracer` should use its default sampling mechanism. 274 | pub fn sampling_priority(value: u32) -> Tag { 275 | Tag::new("sampling.priority", i64::from(value)) 276 | } 277 | 278 | /// Makes a `"span.ind"` tag. 279 | /// 280 | /// Either `"client"` or `"server"` for the appropriate roles in an RPC, 281 | /// and `"producer"` or `"consumer"` for the appropriate roles in a messaging scenario. 282 | pub fn span_kind(value: V) -> Tag 283 | where 284 | V: Into>, 285 | { 286 | Tag::new("span.kind", value.into()) 287 | } 288 | } 289 | -------------------------------------------------------------------------------- /src/tracer.rs: -------------------------------------------------------------------------------- 1 | use crate::sampler::Sampler; 2 | use crate::span::{SpanReceiver, SpanSender, StartSpanOptions}; 3 | use std::borrow::Cow; 4 | use std::sync::Arc; 5 | 6 | /// Tracer. 7 | /// 8 | /// # Examples 9 | /// 10 | /// ``` 11 | /// use rustracing::Tracer; 12 | /// use rustracing::sampler::AllSampler; 13 | /// 14 | /// let (span_tx, span_rx) = crossbeam_channel::bounded(10); 15 | /// let tracer = Tracer::with_sender(AllSampler, span_tx); 16 | /// { 17 | /// let _span = tracer.span("foo").start_with_state(()); 18 | /// } 19 | /// let span = span_rx.try_recv().unwrap(); 20 | /// assert_eq!(span.operation_name(), "foo"); 21 | /// ``` 22 | #[derive(Debug)] 23 | pub struct Tracer { 24 | sampler: Arc, 25 | span_tx: SpanSender, 26 | } 27 | impl, T> Tracer { 28 | /// This constructor is mainly for backward compatibility, it has the same interface 29 | /// as in previous versions except the type of `SpanReceiver`. 30 | /// It builds an unbounded channel which may cause memory issues if there is no reader, 31 | /// prefer `with_sender()` alternative with a bounded one. 32 | pub fn new(sampler: S) -> (Self, SpanReceiver) { 33 | let (span_tx, span_rx) = crossbeam_channel::unbounded(); 34 | (Self::with_sender(sampler, span_tx), span_rx) 35 | } 36 | 37 | /// Makes a new `Tracer` instance. 38 | pub fn with_sender(sampler: S, span_tx: SpanSender) -> Self { 39 | Tracer { 40 | sampler: Arc::new(sampler), 41 | span_tx, 42 | } 43 | } 44 | 45 | /// Returns `StartSpanOptions` for starting a span which has the name `operation_name`. 46 | pub fn span(&self, operation_name: N) -> StartSpanOptions 47 | where 48 | N: Into>, 49 | { 50 | StartSpanOptions::new(operation_name, &self.span_tx, &self.sampler) 51 | } 52 | } 53 | impl Tracer { 54 | /// Clone with the given `sampler`. 55 | pub fn clone_with_sampler>(&self, sampler: U) -> Tracer { 56 | Tracer { 57 | sampler: Arc::new(sampler), 58 | span_tx: self.span_tx.clone(), 59 | } 60 | } 61 | } 62 | impl Clone for Tracer { 63 | fn clone(&self) -> Self { 64 | Tracer { 65 | sampler: Arc::clone(&self.sampler), 66 | span_tx: self.span_tx.clone(), 67 | } 68 | } 69 | } 70 | --------------------------------------------------------------------------------