#open-telemetry #tracing #instrumentation #spans #logging-tracing #helper #trace

tracing-opentelemetry-instrumentation-sdk

A set of helpers to build OpenTelemetry instrumentation based on tracing crate

20 releases (10 breaking)

0.24.0 Nov 17, 2024
0.21.0 Sep 22, 2024
0.19.0 Jun 30, 2024
0.18.0 Mar 9, 2024
0.12.0 Jul 2, 2023

#75 in Debugging

Download history 54080/week @ 2024-08-19 23501/week @ 2024-08-26 29445/week @ 2024-09-02 31164/week @ 2024-09-09 38216/week @ 2024-09-16 38604/week @ 2024-09-23 37701/week @ 2024-09-30 39369/week @ 2024-10-07 31863/week @ 2024-10-14 40305/week @ 2024-10-21 41806/week @ 2024-10-28 46618/week @ 2024-11-04 35958/week @ 2024-11-11 50354/week @ 2024-11-18 42155/week @ 2024-11-25 56538/week @ 2024-12-02

186,205 downloads per month
Used in 10 crates (4 directly)

CC0 license

31KB
488 lines

tracing-opentelemetry-instrumentation-sdk

Provide a set of helpers to build OpenTelemetry instrumentation based on tracing crate, and following the OpenTelemetry Trace Semantic Conventions.

PS: Contributions are welcome (bug report, improvements, features, ...)

Instrumentation on the caller side of a call is composed of steps:

  • start a span with all the attributes (some set to Empty)
  • inject into the call (via header) the propagation data (if supported)
  • do the call
  • update attributes of the span with response (status,...)

Instrumentation on the callee side of a call is composed of steps:

  • extract info propagated info (from header) (if supported) an create an OpenTelemetry Context
  • start a span with all the attributes (some set to Empty)
  • attach the context as parent on the span
  • do the processing
  • update attributes of the span with response (status,...)

The crates provide helper (or inspiration) to extract/inject context info, start & update span and retrieve context or trace_id during processing (eg to inject trace_id into log, error message,...).

  let trace_id = tracing_opentelemetry_instrumentation_sdk::find_current_trace_id();
  //json!({ "error" :  "xxxxxx", "trace_id": trace_id})

The helpers could be used as is or into middleware build on it (eg: axum-tracing-opentelemetry, tonic-tracing-opentelemetry are middlewares build on top of the helpers provide for http (feature & crate))

Notes

  • tracing-opentelemetry extends tracing to interoperate with OpenTelemetry. But with some constraints:
    • Creation of the OpenTelemetry's span is done when the tracing span is closed. So do not try to interact with OpenTelemetry Span (or SpanBuilder) from inside the tracing span.
    • The OpenTelemetry parent Context (and trace_id) is created on NEW span or inherited from parent span. The parent context can be overwritten after creation, but until then the trace_id is the one from NEW, So tracing's log could report none or not-yet set trace_id on event NEW and the following until update.
    • To define kind, name,... of OpenTelemetry's span from tracing's span used special record's name: otel.name, otel.kind, ...
    • Record in a tracing's Span should be defined at creation time. So some field are created with value tracing::field::Empty to then being updated.
  • Create trace with target otel::tracing (and level trace), to have a common way to enable / to disable

Instrumentations Tips

Until every crates are instrumented

Use tracing::instrumented (no propagation & no update on response)

// basic handmade span far to be compliant with
//[opentelemetry-specification/.../database.md](https://github.com/open-telemetry/opentelemetry-specification/blob/v1.22.0/specification/trace/semantic_conventions/database.md)
fn make_otel_span(db_operation: &str) -> tracing::Span {
    // NO parsing of statement to extract information, not recommended by Specification and time-consuming
    // warning: providing the statement could leek information
    tracing_opentelemetry_instrumentation_sdk::otel_trace_span!(
        "DB request",
        db.system = "postgresql",
        // db.statement = stmt,
        db.operation = db_operation,
        otel.name = db_operation, // should be <db.operation> <db.name>.<db.sql.table>,
        otel.kind = "CLIENT",
        otel.status_code = tracing::field::Empty,
    )
}


      // Insert or update
        sqlx::query!(
                "INSERT INTO ...",
                id,
                sub_key,
                result,
            )
            .execute(&*self.pool)
            .instrument(make_otel_span("INSERT"))
            .await
            .map_err(...)?;

Dependencies

~4.5–6MB
~106K SLoC