Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

change: full rework of TraceId (breaking change) #15

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions tracing-honeycomb/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ libhoney-rust = "0.1.3"
rand = "0.7"
chrono = "0.4.9"
parking_lot = { version = "0.11.1", optional = true }
uuid = { version = "0.8.1", features = ["v4"] }
sha-1 = "0.9.4"

[dev-dependencies]
tracing-attributes = "0.1.5"
Expand Down
2 changes: 1 addition & 1 deletion tracing-honeycomb/examples/async_tracing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use tracing_subscriber::registry;

#[instrument]
async fn spawn_children(n: u32, process_name: String) {
register_dist_tracing_root(TraceId::generate(), None).unwrap();
register_dist_tracing_root(TraceId::new(), None).unwrap();

for _ in 0..n {
spawn_child_process(&process_name).await;
Expand Down
16 changes: 16 additions & 0 deletions tracing-honeycomb/src/deterministic_sampler.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
use sha1::{Digest, Sha1};

use crate::TraceId;

/// A port of beeline-nodejs's code for the same functionality.
///
/// Samples deterministically on a given TraceId via a SHA-1 hash.
///
/// https://github.com/honeycombio/beeline-nodejs/blob/main/lib/deterministic_sampler.js
pub(crate) fn sample(sample_rate: u32, trace_id: &TraceId) -> bool {
let sum = Sha1::digest(trace_id.as_ref());
// Since we are operating on u32's in rust, there is no need for the original's `>>> 0`.
let upper_bound = std::u32::MAX / sample_rate;

u32::from_be_bytes([sum[0], sum[1], sum[2], sum[3]]) <= upper_bound
}
134 changes: 12 additions & 122 deletions tracing-honeycomb/src/honeycomb.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
use crate::visitor::{event_to_values, span_to_values, HoneycombVisitor};
use libhoney::FieldHolder;
use std::collections::HashMap;
use std::str::FromStr;
use tracing_distributed::{Event, Span, Telemetry};

#[cfg(feature = "use_parking_lot")]
use parking_lot::Mutex;
#[cfg(not(feature = "use_parking_lot"))]
use std::sync::Mutex;

use crate::{SpanId, TraceId};

/// Telemetry capability that publishes events and spans to Honeycomb.io.
#[derive(Debug)]
pub struct HoneycombTelemetry {
honeycomb_client: Mutex<libhoney::Client<libhoney::transmission::Transmission>>,
sample_rate: Option<u128>,
sample_rate: Option<u32>,
}

impl HoneycombTelemetry {
pub(crate) fn new(cfg: libhoney::Config, sample_rate: Option<u128>) -> Self {
pub(crate) fn new(cfg: libhoney::Config, sample_rate: Option<u32>) -> Self {
let honeycomb_client = libhoney::init(cfg);

// publishing requires &mut so just mutex-wrap it
Expand All @@ -30,7 +31,7 @@ impl HoneycombTelemetry {
}
}

fn report_data(&self, data: HashMap<String, ::libhoney::Value>) {
fn report_data(&self, data: HashMap<String, libhoney::Value>) {
// succeed or die. failure is unrecoverable (mutex poisoned)
#[cfg(not(feature = "use_parking_lot"))]
let mut client = self.honeycomb_client.lock().unwrap();
Expand All @@ -47,10 +48,11 @@ impl HoneycombTelemetry {
}
}

fn should_report(&self, trace_id: TraceId) -> bool {
match self.sample_rate {
Some(sample_rate) => trace_id.0 % sample_rate == 0,
None => true,
fn should_report(&self, trace_id: &TraceId) -> bool {
if let Some(sample_rate) = self.sample_rate {
crate::deterministic_sampler::sample(sample_rate, trace_id)
} else {
false
}
}
}
Expand All @@ -65,128 +67,16 @@ impl Telemetry for HoneycombTelemetry {
}

fn report_span(&self, span: Span<Self::Visitor, Self::SpanId, Self::TraceId>) {
if self.should_report(span.trace_id) {
if self.should_report(&span.trace_id) {
let data = span_to_values(span);
self.report_data(data);
}
}

fn report_event(&self, event: Event<Self::Visitor, Self::SpanId, Self::TraceId>) {
if self.should_report(event.trace_id) {
if self.should_report(&event.trace_id) {
let data = event_to_values(event);
self.report_data(data);
}
}
}

/// Unique Span identifier.
///
/// Combines a span's `tracing::Id` with an instance identifier to avoid id collisions in distributed scenarios.
///
/// `Display` and `FromStr` are guaranteed to round-trip.
#[derive(PartialEq, Eq, Hash, Clone, Debug)]
pub struct SpanId {
pub(crate) tracing_id: tracing::Id,
pub(crate) instance_id: u64,
}

impl SpanId {
/// Metadata field name associated with `SpanId` values.
pub fn meta_field_name() -> &'static str {
"span-id"
}
}

#[derive(PartialEq, Eq, Clone, Debug)]
pub enum ParseSpanIdError {
ParseIntError(std::num::ParseIntError),
FormatError,
}

impl FromStr for SpanId {
type Err = ParseSpanIdError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut iter = s.split('-');
let s1 = iter.next().ok_or(ParseSpanIdError::FormatError)?;
let u1 = u64::from_str_radix(s1, 10).map_err(ParseSpanIdError::ParseIntError)?;
let s2 = iter.next().ok_or(ParseSpanIdError::FormatError)?;
let u2 = u64::from_str_radix(s2, 10).map_err(ParseSpanIdError::ParseIntError)?;

Ok(SpanId {
tracing_id: tracing::Id::from_u64(u1),
instance_id: u2,
})
}
}

impl std::fmt::Display for SpanId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}-{}", self.tracing_id.into_u64(), self.instance_id)
}
}

/// A Honeycomb Trace ID.
///
/// Uniquely identifies a single distributed trace.
///
/// `Display` and `FromStr` are guaranteed to round-trip.
#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)]
pub struct TraceId(pub(crate) u128);

impl TraceId {
/// Metadata field name associated with this `TraceId` values.
pub fn meta_field_name() -> &'static str {
"trace-id"
}

/// Generate a random trace ID by using a thread-level RNG to generate a u128
pub fn generate() -> Self {
use rand::Rng;
let u: u128 = rand::thread_rng().gen();

TraceId(u)
}
}

impl FromStr for TraceId {
type Err = std::num::ParseIntError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
let u = u128::from_str_radix(s, 10)?;
Ok(Self(u))
}
}

impl std::fmt::Display for TraceId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}

#[cfg(test)]
mod test {
use super::*;
use proptest::prelude::*;
proptest! {
#[test]
// ua is [1..] and not [0..] because 0 is not a valid tracing::Id (tracing::from_u64 throws on 0)
fn span_id_round_trip(ua in 1u64.., ub in 1u64..) {
let span_id = SpanId {
tracing_id: tracing::Id::from_u64(ua),
instance_id: ub,
};
let s = span_id.to_string();
let res = SpanId::from_str(&s);
assert_eq!(Ok(span_id), res);
}

#[test]
fn trace_id_round_trip(u in 1u128..) {
let trace_id = TraceId(u);
let s = trace_id.to_string();
let res = TraceId::from_str(&s);
assert_eq!(Ok(trace_id), res);
}
}
}
15 changes: 11 additions & 4 deletions tracing-honeycomb/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,21 @@
//!
//! As a tracing layer, `TelemetryLayer` can be composed with other layers to provide stdout logging, filtering, etc.

use rand::{self, Rng};

mod honeycomb;
mod span_id;
mod trace_id;
mod visitor;

pub use crate::honeycomb::{HoneycombTelemetry, SpanId, TraceId};
pub use crate::visitor::HoneycombVisitor;
use rand::{self, Rng};
pub use honeycomb::HoneycombTelemetry;
pub use span_id::SpanId;
pub use trace_id::TraceId;
#[doc(no_inline)]
pub use tracing_distributed::{TelemetryLayer, TraceCtxError};
pub use visitor::HoneycombVisitor;

pub(crate) mod deterministic_sampler;

/// Register the current span as the local root of a distributed trace.
///
Expand Down Expand Up @@ -92,7 +99,7 @@ pub fn new_honeycomb_telemetry_layer(
pub fn new_honeycomb_telemetry_layer_with_trace_sampling(
service_name: &'static str,
honeycomb_config: libhoney::Config,
sample_rate: u128,
sample_rate: u32,
) -> TelemetryLayer<HoneycombTelemetry, SpanId, TraceId> {
let instance_id: u64 = rand::thread_rng().gen();
TelemetryLayer::new(
Expand Down
72 changes: 72 additions & 0 deletions tracing-honeycomb/src/span_id.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
use std::fmt::{self, Display};
use std::str::FromStr;
/// Unique Span identifier.
///
/// Combines a span's `tracing::Id` with an instance identifier to avoid id collisions in distributed scenarios.
///
/// `Display` and `FromStr` are guaranteed to round-trip.
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct SpanId {
pub(crate) tracing_id: tracing::span::Id,
pub(crate) instance_id: u64,
}

impl SpanId {
/// Metadata field name associated with `SpanId` values.
pub fn meta_field_name() -> &'static str {
"span-id"
}
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ParseSpanIdError {
ParseIntError(std::num::ParseIntError),
FormatError,
}

impl FromStr for SpanId {
type Err = ParseSpanIdError;

/// Parses a Span Id from a `{SPAN}-{INSTANCE}` u64 pair, such as `1234567890-1234567890`.
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut iter = s.split('-');
let s1 = iter.next().ok_or(ParseSpanIdError::FormatError)?;
let u1 = u64::from_str_radix(s1, 10).map_err(ParseSpanIdError::ParseIntError)?;
let s2 = iter.next().ok_or(ParseSpanIdError::FormatError)?;
let u2 = u64::from_str_radix(s2, 10).map_err(ParseSpanIdError::ParseIntError)?;

Ok(SpanId {
tracing_id: tracing::Id::from_u64(u1),
instance_id: u2,
})
}
}

impl Display for SpanId {
/// Formats a Span Id as a `{SPAN}-{INSTANCE}` u64 pair, such as `1234567890-1234567890`.
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}-{}", self.tracing_id.into_u64(), self.instance_id)
}
}

#[cfg(test)]
mod test {
use super::*;
use proptest::prelude::*;

use crate::SpanId;

proptest! {
#[test]
// ua is [1..] and not [0..] because 0 is not a valid tracing::Id (tracing::from_u64 throws on 0)
fn span_id_round_trip(ua in 1u64.., ub in 1u64..) {
let span_id = SpanId {
tracing_id: tracing::Id::from_u64(ua),
instance_id: ub,
};
let s = span_id.to_string();
let res = SpanId::from_str(&s);
assert_eq!(Ok(span_id), res);
}
}
}
Loading