Skip to content
Merged
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
5 changes: 5 additions & 0 deletions tracing-actix-web/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Changelog

## Unreleased

- Support Opentelemetry 0.31
17 changes: 11 additions & 6 deletions tracing-actix-web/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,11 @@ readme = "README.md"
description = "Structured logging middleware for actix-web."
keywords = ["http", "actix-web", "tracing", "logging"]
categories = ["asynchronous", "web-programming"]

repository.workspace = true
homepage.workspace = true
license.workspace = true
edition.workspace = true
rust-version.workspace = true
repository = "https://github.com/actix/actix-extras"
homepage = "https://actix.rs"
license = "MIT OR Apache-2.0"
edition = "2021"
rust-version = "1.82"

[features]
default = ["emit_event_on_error"]
Expand Down Expand Up @@ -96,6 +95,10 @@ opentelemetry_0_30 = [
"opentelemetry_0_30_pkg",
"tracing-opentelemetry_0_31_pkg",
]
opentelemetry_0_31 = [
"opentelemetry_0_31_pkg",
"tracing-opentelemetry_0_32_pkg",
]
emit_event_on_error = []
uuid_v7 = ["uuid/v7"]

Expand Down Expand Up @@ -123,6 +126,7 @@ opentelemetry_0_27_pkg = { package = "opentelemetry", version = "0.27", optional
opentelemetry_0_28_pkg = { package = "opentelemetry", version = "0.28", optional = true }
opentelemetry_0_29_pkg = { package = "opentelemetry", version = "0.29", optional = true }
opentelemetry_0_30_pkg = { package = "opentelemetry", version = "0.30", optional = true }
opentelemetry_0_31_pkg = { package = "opentelemetry", version = "0.31", optional = true }
tracing-opentelemetry_0_12_pkg = { package = "tracing-opentelemetry", version = "0.12", optional = true }
tracing-opentelemetry_0_13_pkg = { package = "tracing-opentelemetry", version = "0.13", optional = true }
tracing-opentelemetry_0_14_pkg = { package = "tracing-opentelemetry", version = "0.14", optional = true }
Expand All @@ -141,6 +145,7 @@ tracing-opentelemetry_0_28_pkg = { package = "tracing-opentelemetry", version =
tracing-opentelemetry_0_29_pkg = { package = "tracing-opentelemetry", version = "0.29", optional = true }
tracing-opentelemetry_0_30_pkg = { package = "tracing-opentelemetry", version = "0.30", optional = true }
tracing-opentelemetry_0_31_pkg = { package = "tracing-opentelemetry", version = "0.31", optional = true }
tracing-opentelemetry_0_32_pkg = { package = "tracing-opentelemetry", version = "0.32", optional = true }

[dev-dependencies]
actix-web = { version = "4", default-features = false, features = ["macros"] }
Expand Down
1 change: 1 addition & 0 deletions tracing-actix-web/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ actix-web = "4"
- `opentelemetry_0_28`: same as above but using `opentelemetry` 0.28;
- `opentelemetry_0_29`: same as above but using `opentelemetry` 0.29;
- `opentelemetry_0_30`: same as above but using `opentelemetry` 0.30;
- `opentelemetry_0_31`: same as above but using `opentelemetry` 0.31;
- `emit_event_on_error`: emit a [`tracing`] event when request processing fails with an error (enabled by default).
- `uuid_v7`: use the UUID v7 implementation inside [`RequestId`] instead of UUID v4 (disabled by default).
## Quickstart
Expand Down
12 changes: 6 additions & 6 deletions tracing-actix-web/examples/custom-root-span/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ edition = "2021"

[dependencies]
actix-web = "4"
opentelemetry = "0.30"
opentelemetry-otlp = { version = "0.30", features = ["grpc-tonic"] }
opentelemetry_sdk = { version = "0.30", features = ["rt-tokio-current-thread"] }
opentelemetry-semantic-conventions = "0.30"
tracing-opentelemetry = "0.31"
opentelemetry = "0.31"
opentelemetry-otlp = { version = "0.31", features = ["grpc-tonic"] }
opentelemetry_sdk = { version = "0.31", features = ["rt-tokio-current-thread"] }
opentelemetry-semantic-conventions = "0.31"
tracing-opentelemetry = "0.32"
tracing = "0.1"
tracing-actix-web = { path = "../..", features = ["opentelemetry_0_30"] }
tracing-actix-web = { path = "../..", features = ["opentelemetry_0_31"] }
tracing-bunyan-formatter = "0.3"
tracing-subscriber = { version = "0.3", features = ["registry", "env-filter"] }
15 changes: 8 additions & 7 deletions tracing-actix-web/examples/custom-root-span/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
use actix_web::body::MessageBody;
use actix_web::dev::{ServiceRequest, ServiceResponse};
use actix_web::{web, App, Error, HttpServer};
use opentelemetry::trace::TracerProvider;
use opentelemetry::{global, KeyValue};
use std::{io, sync::LazyLock};

use actix_web::{
body::MessageBody,
dev::{ServiceRequest, ServiceResponse},
web, App, Error, HttpServer,
};
use opentelemetry::{global, trace::TracerProvider, KeyValue};
use opentelemetry_otlp::WithExportConfig;
use opentelemetry_sdk::{propagation::TraceContextPropagator, Resource};
use opentelemetry_semantic_conventions::resource;
use std::io;
use std::sync::LazyLock;
use tracing::Span;
use tracing_actix_web::{DefaultRootSpanBuilder, RootSpan, RootSpanBuilder, TracingLogger};
use tracing_bunyan_formatter::{BunyanFormattingLayer, JsonStorageLayer};
Expand Down
12 changes: 6 additions & 6 deletions tracing-actix-web/examples/opentelemetry/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ license = "MIT/Apache-2.0"

[dependencies]
actix-web = "4"
opentelemetry = "0.30"
opentelemetry-otlp = { version = "0.30", features = ["grpc-tonic"] }
opentelemetry_sdk = { version = "0.30", features = ["rt-tokio-current-thread"] }
opentelemetry-semantic-conventions = "0.30"
opentelemetry = "0.31"
opentelemetry-otlp = { version = "0.31", features = ["grpc-tonic"] }
opentelemetry_sdk = { version = "0.31", features = ["rt-tokio-current-thread"] }
opentelemetry-semantic-conventions = "0.31"
tracing = "0.1"
tracing-actix-web = { path = "../..", features = ["opentelemetry_0_30"] }
tracing-actix-web = { path = "../..", features = ["opentelemetry_0_31"] }
tracing-bunyan-formatter = "0.3"
tracing-opentelemetry = "0.31"
tracing-opentelemetry = "0.32"
tracing-subscriber = { version = "0.3", features = ["registry", "env-filter"] }
7 changes: 3 additions & 4 deletions tracing-actix-web/examples/opentelemetry/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
use std::{io, sync::LazyLock};

use actix_web::{web, App, HttpServer};
use opentelemetry::trace::TracerProvider;
use opentelemetry::{global, KeyValue};
use opentelemetry::{global, trace::TracerProvider, KeyValue};
use opentelemetry_otlp::WithExportConfig;
use opentelemetry_sdk::{propagation::TraceContextPropagator, Resource};
use opentelemetry_semantic_conventions::resource;
use std::io;
use std::sync::LazyLock;
use tracing_actix_web::TracingLogger;
use tracing_bunyan_formatter::{BunyanFormattingLayer, JsonStorageLayer};
use tracing_subscriber::{layer::SubscriberExt, EnvFilter, Registry};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use std::io;

use actix_web::{
dev::Service,
http::header::{HeaderName, HeaderValue},
web, App, HttpMessage, HttpServer,
};
use std::io;
use tracing_actix_web::{RequestId, TracingLogger};

async fn hello() -> &'static str {
Expand Down
25 changes: 14 additions & 11 deletions tracing-actix-web/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
//! - `opentelemetry_0_28`: same as above but using `opentelemetry` 0.28;
//! - `opentelemetry_0_29`: same as above but using `opentelemetry` 0.29;
//! - `opentelemetry_0_30`: same as above but using `opentelemetry` 0.30;
//! - `opentelemetry_0_31`: same as above but using `opentelemetry` 0.31;
//! - `emit_event_on_error`: emit a [`tracing`] event when request processing fails with an error (enabled by default).
//! - `uuid_v7`: use the UUID v7 implementation inside [`RequestId`] instead of UUID v4 (disabled by default).
//!
Expand Down Expand Up @@ -64,20 +65,20 @@
//! [`TracingLogger`] is built on top of [`tracing`], a modern instrumentation framework with
//! [a vibrant ecosystem](https://github.com/tokio-rs/tracing#related-crates).
//!
//! `tracing-actix-web`'s documentation provides a crash course in how to use [`tracing`] to instrument an `actix-web` application.
//! `tracing-actix-web`'s documentation provides a crash course in how to use [`tracing`] to instrument an `actix-web` application.
//! If you want to learn more check out ["Are we observable yet?"](https://www.lpalmieri.com/posts/2020-09-27-zero-to-production-4-are-we-observable-yet/) -
//! it provides an in-depth introduction to the crate and the problems it solves within the bigger picture of [observability](https://docs.honeycomb.io/learning-about-observability/).
//!
//! ## The root span
//!
//! [`tracing::Span`] is the key abstraction in [`tracing`]: it represents a unit of work in your system.
//! [`tracing::Span`] is the key abstraction in [`tracing`]: it represents a unit of work in your system.
//! A [`tracing::Span`] has a beginning and an end. It can include one or more **child spans** to represent sub-unit
//! of works within a larger task.
//!
//! When your application receives a request, [`TracingLogger`] creates a new span - we call it the **[root span]**.
//! When your application receives a request, [`TracingLogger`] creates a new span - we call it the **[root span]**.
//! All the spans created _while_ processing the request will be children of the root span.
//!
//! [`tracing`] empowers us to attach structured properties to a span as a collection of key-value pairs.
//! [`tracing`] empowers us to attach structured properties to a span as a collection of key-value pairs.
//! Those properties can then be queried in a variety of tools (e.g. ElasticSearch, Honeycomb, DataDog) to
//! understand what is happening in your system.
//!
Expand All @@ -103,11 +104,11 @@
//! let another_way = TracingLogger::<DefaultRootSpanBuilder>::new();
//! ```
//!
//! We are delegating the construction of the root span to [`DefaultRootSpanBuilder`].
//! We are delegating the construction of the root span to [`DefaultRootSpanBuilder`].
//! [`DefaultRootSpanBuilder`] captures, out of the box, several dimensions that are usually relevant when looking at an HTTP
//! API: method, version, route, etc. - check out its documentation for an extensive list.
//!
//! You can customise the root span by providing your own implementation of the [`RootSpanBuilder`] trait.
//! You can customise the root span by providing your own implementation of the [`RootSpanBuilder`] trait.
//! Let's imagine, for example, that our system cares about a client identifier embedded inside an authorization header.
//! We could add a `client_id` property to the root span using a custom builder, `DomainRootSpanBuilder`:
//!
Expand All @@ -132,7 +133,7 @@
//! let custom_middleware = TracingLogger::<DomainRootSpanBuilder>::new();
//! ```
//!
//! There is an issue, though: `client_id` is the _only_ property we are capturing.
//! There is an issue, though: `client_id` is the _only_ property we are capturing.
//! With `DomainRootSpanBuilder`, as it is, we do not get any of that useful HTTP-related information provided by
//! [`DefaultRootSpanBuilder`].
//!
Expand Down Expand Up @@ -164,7 +165,7 @@
//! [`root_span!`] is a macro provided by `tracing-actix-web`: it creates a new span by combining all the HTTP properties tracked
//! by [`DefaultRootSpanBuilder`] with the custom ones you specify when calling it (e.g. `client_id` in our example).
//!
//! We need to use a macro because `tracing` requires all the properties attached to a span to be declared upfront, when the span is created.
//! We need to use a macro because `tracing` requires all the properties attached to a span to be declared upfront, when the span is created.
//! You cannot add new ones afterwards. This makes it extremely fast, but it pushes us to reach for macros when we need some level of
//! composition.
//!
Expand Down Expand Up @@ -200,7 +201,7 @@
//!
//! ## The [`RootSpan`] extractor
//!
//! It often happens that not all information about a task is known upfront, encoded in the incoming request.
//! It often happens that not all information about a task is known upfront, encoded in the incoming request.
//! You can use the [`RootSpan`] extractor to grab the root span in your handlers and attach more information
//! to your root span as it becomes available:
//!
Expand Down Expand Up @@ -272,10 +273,10 @@
//! To fulfill a request you often have to perform additional I/O operations - e.g. calls to other REST or gRPC APIs, database queries, etc.
//! **Distributed tracing** is the standard approach to **trace** a single request across the entirety of your stack.
//!
//! `tracing-actix-web` provides support for distributed tracing by supporting the [OpenTelemetry standard](https://opentelemetry.io/).
//! `tracing-actix-web` provides support for distributed tracing by supporting the [OpenTelemetry standard](https://opentelemetry.io/).
//! `tracing-actix-web` follows [OpenTelemetry's semantic convention](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/overview.md#spancontext)
//! for field names.
//! Furthermore, it provides an `opentelemetry_0_17` feature flag to automatically performs trace propagation: it tries to extract the OpenTelemetry context out of the headers of incoming requests and, when it finds one, it sets it as the remote context for the current root span. The context is then propagated to your downstream dependencies if your HTTP or gRPC clients are OpenTelemetry-aware - e.g. using [`reqwest-middleware` and `reqwest-tracing`](https://github.com/TrueLayer/reqwest-middleware) if you are using `reqwest` as your HTTP client.
//! Furthermore, it provides an `opentelemetry_0_17` feature flag to automatically performs trace propagation: it tries to extract the OpenTelemetry context out of the headers of incoming requests and, when it finds one, it sets it as the remote context for the current root span. The context is then propagated to your downstream dependencies if your HTTP or gRPC clients are OpenTelemetry-aware - e.g. using [`reqwest-middleware` and `reqwest-tracing`](https://github.com/TrueLayer/reqwest-middleware) if you are using `reqwest` as your HTTP client.
//! You can then find all logs for the same request across all the services it touched by looking for the `trace_id`, automatically logged by `tracing-actix-web`.
//!
//! If you add [`tracing-opentelemetry::OpenTelemetryLayer`](https://docs.rs/tracing-opentelemetry/0.17.0/tracing_opentelemetry/struct.OpenTelemetryLayer.html)
Expand Down Expand Up @@ -319,6 +320,7 @@ mutually_exclusive_features::none_or_one_of!(
"opentelemetry_0_28",
"opentelemetry_0_29",
"opentelemetry_0_30",
"opentelemetry_0_31",
);

#[cfg(any(
Expand All @@ -340,5 +342,6 @@ mutually_exclusive_features::none_or_one_of!(
feature = "opentelemetry_0_28",
feature = "opentelemetry_0_29",
feature = "opentelemetry_0_30",
feature = "opentelemetry_0_31",
))]
mod otel;
32 changes: 21 additions & 11 deletions tracing-actix-web/src/middleware.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,27 @@
use crate::{DefaultRootSpanBuilder, RequestId, RootSpan, RootSpanBuilder};
use actix_web::body::{BodySize, MessageBody};
use actix_web::dev::{Service, ServiceRequest, ServiceResponse, Transform};
use actix_web::http::StatusCode;
use actix_web::web::Bytes;
use actix_web::{Error, HttpMessage, ResponseError};
use std::future::{ready, Future, Ready};
use std::pin::Pin;
use std::task::{Context, Poll};
use std::{
future::{ready, Future, Ready},
pin::Pin,
task::{Context, Poll},
};

use actix_web::{
body::{BodySize, MessageBody},
dev::{Service, ServiceRequest, ServiceResponse, Transform},
web::Bytes,
Error, HttpMessage,
};
use tracing::Span;

use crate::{DefaultRootSpanBuilder, RequestId, RootSpan, RootSpanBuilder};

/// `TracingLogger` is a middleware to capture structured diagnostic when processing an HTTP request.
/// Check the crate-level documentation for an in-depth introduction.
///
/// `TracingLogger` is designed as a drop-in replacement of [`actix-web`]'s [`Logger`].
///
/// # Usage
///
/// Register `TracingLogger` as a middleware for your application using `.wrap` on `App`.
/// Register `TracingLogger` as a middleware for your application using `.wrap` on `App`.
/// In this example we add a [`tracing::Subscriber`] to output structured logs to the console.
///
/// ```rust
Expand Down Expand Up @@ -235,6 +240,7 @@ where
}
}

#[cfg(feature = "emit_event_on_error")]
fn emit_event_on_error<B: 'static>(outcome: &Result<ServiceResponse<B>, actix_web::Error>) {
match outcome {
Ok(response) => {
Expand All @@ -250,7 +256,11 @@ fn emit_event_on_error<B: 'static>(outcome: &Result<ServiceResponse<B>, actix_we
}
}

fn emit_error_event(response_error: &dyn ResponseError, status_code: StatusCode) {
#[cfg(feature = "emit_event_on_error")]
fn emit_error_event(
response_error: &dyn actix_web::ResponseError,
status_code: actix_web::http::StatusCode,
) {
let error_msg_prefix = "Error encountered while processing the incoming HTTP request";
if status_code.is_client_error() {
tracing::warn!("{}: {:?}", error_msg_prefix, response_error);
Expand Down
13 changes: 8 additions & 5 deletions tracing-actix-web/src/otel.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use actix_web::dev::ServiceRequest;

use opentelemetry::propagation::Extractor;
#[cfg(feature = "opentelemetry_0_13")]
use opentelemetry_0_13_pkg as opentelemetry;
#[cfg(feature = "opentelemetry_0_14")]
Expand Down Expand Up @@ -36,7 +36,8 @@ use opentelemetry_0_28_pkg as opentelemetry;
use opentelemetry_0_29_pkg as opentelemetry;
#[cfg(feature = "opentelemetry_0_30")]
use opentelemetry_0_30_pkg as opentelemetry;

#[cfg(feature = "opentelemetry_0_31")]
use opentelemetry_0_31_pkg as opentelemetry;
#[cfg(feature = "opentelemetry_0_13")]
use tracing_opentelemetry_0_12_pkg as tracing_opentelemetry;
#[cfg(feature = "opentelemetry_0_14")]
Expand Down Expand Up @@ -73,8 +74,8 @@ use tracing_opentelemetry_0_29_pkg as tracing_opentelemetry;
use tracing_opentelemetry_0_30_pkg as tracing_opentelemetry;
#[cfg(feature = "opentelemetry_0_30")]
use tracing_opentelemetry_0_31_pkg as tracing_opentelemetry;

use opentelemetry::propagation::Extractor;
#[cfg(feature = "opentelemetry_0_31")]
use tracing_opentelemetry_0_32_pkg as tracing_opentelemetry;

pub(crate) struct RequestHeaderCarrier<'a> {
headers: &'a actix_web::http::header::HeaderMap,
Expand Down Expand Up @@ -103,7 +104,7 @@ pub(crate) fn set_otel_parent(req: &ServiceRequest, span: &tracing::Span) {
let parent_context = opentelemetry::global::get_text_map_propagator(|propagator| {
propagator.extract(&RequestHeaderCarrier::new(req.headers()))
});
span.set_parent(parent_context);
let _ = span.set_parent(parent_context);
// If we have a remote parent span, this will be the parent's trace identifier.
// If not, it will be the newly generated trace identifier with this request as root span.
#[cfg(not(any(
Expand All @@ -121,6 +122,7 @@ pub(crate) fn set_otel_parent(req: &ServiceRequest, span: &tracing::Span) {
feature = "opentelemetry_0_28",
feature = "opentelemetry_0_29",
feature = "opentelemetry_0_30",
feature = "opentelemetry_0_31",
)))]
let trace_id = span.context().span().span_context().trace_id().to_hex();

Expand All @@ -139,6 +141,7 @@ pub(crate) fn set_otel_parent(req: &ServiceRequest, span: &tracing::Span) {
feature = "opentelemetry_0_28",
feature = "opentelemetry_0_29",
feature = "opentelemetry_0_30",
feature = "opentelemetry_0_31",
))]
let trace_id = {
let id = span.context().span().span_context().trace_id();
Expand Down
4 changes: 2 additions & 2 deletions tracing-actix-web/src/request_id.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use actix_web::{dev::Payload, HttpMessage};
use actix_web::{FromRequest, HttpRequest, ResponseError};
use std::future::{ready, Ready};

use actix_web::{dev::Payload, FromRequest, HttpMessage, HttpRequest, ResponseError};
use uuid::Uuid;

/// A unique identifier generated for each incoming request.
Expand Down
4 changes: 2 additions & 2 deletions tracing-actix-web/src/root_span.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use actix_web::{dev::Payload, HttpMessage};
use actix_web::{FromRequest, HttpRequest, ResponseError};
use std::future::{ready, Ready};

use actix_web::{dev::Payload, FromRequest, HttpMessage, HttpRequest, ResponseError};
use tracing::Span;

#[derive(Clone)]
Expand Down
13 changes: 8 additions & 5 deletions tracing-actix-web/src/root_span_builder.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
use crate::root_span;
use actix_web::body::MessageBody;
use actix_web::dev::{ServiceRequest, ServiceResponse};
use actix_web::http::StatusCode;
use actix_web::{Error, ResponseError};
use actix_web::{
body::MessageBody,
dev::{ServiceRequest, ServiceResponse},
http::StatusCode,
Error, ResponseError,
};
use tracing::Span;

use crate::root_span;

/// `RootSpanBuilder` allows you to customise the root span attached by
/// [`TracingLogger`] to incoming requests.
///
Expand Down
Loading