tracing_actix_web/
lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
//! `tracing-actix-web` provides [`TracingLogger`], a middleware to collect telemetry data from applications
//! built on top of the [`actix-web`] framework.
//!
//! > `tracing-actix-web` was initially developed for the telemetry chapter of [Zero to Production In Rust](https://zero2prod.com), a hands-on introduction to backend development using the Rust programming language.
//!
//! # Getting started
//!
//! ## How to install
//!
//! Add `tracing-actix-web` to your dependencies:
//!
//! ```toml
//! [dependencies]
//! # ...
//! tracing-actix-web = "0.7"
//! tracing = "0.1"
//! actix-web = "4"
//! ```
//!
//! `tracing-actix-web` exposes three feature flags:
//!
//! - `opentelemetry_0_13`: attach [OpenTelemetry](https://github.com/open-telemetry/opentelemetry-rust)'s context to the root span using `opentelemetry` 0.13;
//! - `opentelemetry_0_14`: same as above but using `opentelemetry` 0.14;
//! - `opentelemetry_0_15`: same as above but using `opentelemetry` 0.15;
//! - `opentelemetry_0_16`: same as above but using `opentelemetry` 0.16;
//! - `opentelemetry_0_17`: same as above but using `opentelemetry` 0.17;
//! - `opentelemetry_0_18`: same as above but using `opentelemetry` 0.18;
//! - `opentelemetry_0_19`: same as above but using `opentelemetry` 0.19;
//! - `opentelemetry_0_20`: same as above but using `opentelemetry` 0.20;
//! - `opentelemetry_0_21`: same as above but using `opentelemetry` 0.21;
//! - `opentelemetry_0_22`: same as above but using `opentelemetry` 0.22;
//! - `opentelemetry_0_23`: same as above but using `opentelemetry` 0.23;
//! - `opentelemetry_0_24`: same as above but using `opentelemetry` 0.24;
//! - `opentelemetry_0_25`: same as above but using `opentelemetry` 0.25;
//! - `opentelemetry_0_26`: same as above but using `opentelemetry` 0.26;
//! - `opentelemetry_0_27`: same as above but using `opentelemetry` 0.27;
//! - `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
//!
//! ```rust,compile_fail
//! use actix_web::{App, web, HttpServer};
//! use tracing_actix_web::TracingLogger;
//!
//! let server = HttpServer::new(|| {
//!     App::new()
//!         // Mount `TracingLogger` as a middleware
//!         .wrap(TracingLogger::default())
//!         .service( /*  */ )
//! });
//! ```
//!
//! Check out [the examples on GitHub](https://github.com/LukeMathWalker/tracing-actix-web/tree/main/examples) to get a taste of how [`TracingLogger`] can be used to observe and monitor your
//! application.
//!
//! # From zero to hero: a crash course in observability
//!
//! ## `tracing`: who art thou?
//!
//! [`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.  
//! 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.  
//! 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]**.  
//! 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.  
//! Those properties can then be queried in a variety of tools (e.g. ElasticSearch, Honeycomb, DataDog) to
//! understand what is happening in your system.
//!
//! ## Customisation via [`RootSpanBuilder`]
//!
//! Troubleshooting becomes much easier when the root span has a _rich context_ - e.g. you can understand most of what
//! happened when processing the request just by looking at the properties attached to the corresponding root span.
//!
//! You might have heard of this technique as the [canonical log line pattern](https://stripe.com/blog/canonical-log-lines),
//! popularised by Stripe. It is more recently discussed in terms of [high-cardinality events](https://www.honeycomb.io/blog/observability-a-manifesto/)
//! by Honeycomb and other vendors in the observability space.
//!
//! [`TracingLogger`] gives you a chance to use the very same pattern: you can customise the properties attached
//! to the root span in order to capture the context relevant to your specific domain.
//!
//! [`TracingLogger::default`] is equivalent to:
//!
//! ```rust
//! use tracing_actix_web::{TracingLogger, DefaultRootSpanBuilder};
//!
//! // Two ways to initialise TracingLogger with the default root span builder
//! let default = TracingLogger::default();
//! let another_way = TracingLogger::<DefaultRootSpanBuilder>::new();
//! ```
//!
//! 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.  
//! 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`:
//!
//! ```rust
//! use actix_web::body::MessageBody;
//! use actix_web::dev::{ServiceResponse, ServiceRequest};
//! use actix_web::Error;
//! use tracing_actix_web::{TracingLogger, DefaultRootSpanBuilder, RootSpanBuilder};
//! use tracing::Span;
//!
//! pub struct DomainRootSpanBuilder;
//!
//! impl RootSpanBuilder for DomainRootSpanBuilder {
//!     fn on_request_start(request: &ServiceRequest) -> Span {
//!         let client_id: &str = todo!("Somehow extract it from the authorization header");
//!         tracing::info_span!("Request", client_id)
//!     }
//!
//!     fn on_request_end<B: MessageBody>(_span: Span, _outcome: &Result<ServiceResponse<B>, Error>) {}
//! }
//!
//! let custom_middleware = TracingLogger::<DomainRootSpanBuilder>::new();
//! ```
//!
//! 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`].
//!
//! We can do better!
//!
//! ```rust
//! use actix_web::body::MessageBody;
//! use actix_web::dev::{ServiceResponse, ServiceRequest};
//! use actix_web::Error;
//! use tracing_actix_web::{TracingLogger, DefaultRootSpanBuilder, RootSpanBuilder};
//! use tracing::Span;
//!
//! pub struct DomainRootSpanBuilder;
//!
//! impl RootSpanBuilder for DomainRootSpanBuilder {
//!     fn on_request_start(request: &ServiceRequest) -> Span {
//!         let client_id: &str = todo!("Somehow extract it from the authorization header");
//!         tracing_actix_web::root_span!(request, client_id)
//!     }
//!
//!     fn on_request_end<B: MessageBody>(span: Span, outcome: &Result<ServiceResponse<B>, Error>) {
//!         DefaultRootSpanBuilder::on_request_end(span, outcome);
//!     }
//! }
//!
//! let custom_middleware = TracingLogger::<DomainRootSpanBuilder>::new();
//! ```
//!
//! [`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.  
//! 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.
//!
//! [`root_span!`] exposes more or less the same knob you can find on `tracing`'s `span!` macro. You can, for example, customise
//! the span level:
//!
//! ```rust
//! use actix_web::body::MessageBody;
//! use actix_web::dev::{ServiceResponse, ServiceRequest};
//! use actix_web::Error;
//! use tracing_actix_web::{TracingLogger, DefaultRootSpanBuilder, RootSpanBuilder, Level};
//! use tracing::Span;
//!
//! pub struct CustomLevelRootSpanBuilder;
//!
//! impl RootSpanBuilder for CustomLevelRootSpanBuilder {
//!     fn on_request_start(request: &ServiceRequest) -> Span {
//!         let level = if request.path() == "/health_check" {
//!             Level::DEBUG
//!         } else {
//!             Level::INFO
//!         };
//!         tracing_actix_web::root_span!(level = level, request)
//!     }
//!
//!     fn on_request_end<B: MessageBody>(span: Span, outcome: &Result<ServiceResponse<B>, Error>) {
//!         DefaultRootSpanBuilder::on_request_end(span, outcome);
//!     }
//! }
//!
//! let custom_middleware = TracingLogger::<CustomLevelRootSpanBuilder>::new();
//! ```
//!
//! ## The [`RootSpan`] extractor
//!
//! 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:
//!
//! ```rust
//! use actix_web::body::MessageBody;
//! use actix_web::dev::{ServiceResponse, ServiceRequest};
//! use actix_web::{Error, HttpResponse};
//! use tracing_actix_web::{RootSpan, DefaultRootSpanBuilder, RootSpanBuilder};
//! use tracing::Span;
//! use actix_web::get;
//! use tracing_actix_web::RequestId;
//! use uuid::Uuid;
//!
//! #[get("/")]
//! async fn handler(root_span: RootSpan) -> HttpResponse {
//!     let application_id: &str = todo!("Some domain logic");
//!     // Record the property value against the root span
//!     root_span.record("application_id", &application_id);
//!
//!     // [...]
//!     # todo!()
//! }
//!
//! pub struct DomainRootSpanBuilder;
//!
//! impl RootSpanBuilder for DomainRootSpanBuilder {
//!     fn on_request_start(request: &ServiceRequest) -> Span {
//!         let client_id: &str = todo!("Somehow extract it from the authorization header");
//!         // All fields you want to capture must be declared upfront.
//!         // If you don't know the value (yet), use tracing's `Empty`
//!         tracing_actix_web::root_span!(
//!             request,
//!             client_id, application_id = tracing::field::Empty
//!         )
//!     }
//!
//!     fn on_request_end<B: MessageBody>(span: Span, response: &Result<ServiceResponse<B>, Error>) {
//!         DefaultRootSpanBuilder::on_request_end(span, response);
//!     }
//! }
//! ```
//!
//! # Unique identifiers
//!
//! ## Request Id
//!
//! `tracing-actix-web` generates a unique identifier for each incoming request, the **request id**.
//!
//! You can extract the request id using the [`RequestId`] extractor:
//!
//! ```rust
//! use actix_web::get;
//! use tracing_actix_web::RequestId;
//! use uuid::Uuid;
//!
//! #[get("/")]
//! async fn index(request_id: RequestId) -> String {
//!     format!("{}", request_id)
//! }
//! ```
//!
//! The request id is meant to identify all operations related to a particular request **within the boundary of your API**.
//! If you need to **trace** a request across multiple services (e.g. in a microservice architecture), you want to look at the `trace_id` field - see the next section on OpenTelemetry for more details.
//!
//! Optionally, using the `uuid_v7` feature flag will allow [`RequestId`] to use UUID v7 instead of the currently used UUID v4.
//!
//! ## Trace Id
//!
//! 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` 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.  
//! 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)
//! in your `tracing::Subscriber` you will be able to export the root span (and all its children) as OpenTelemetry spans.
//!
//! Check out the [relevant example in the GitHub repository](https://github.com/LukeMathWalker/tracing-actix-web/tree/main/examples/opentelemetry) for reference.
//!
//! [root span]: crate::RootSpan
//! [`actix-web`]: https://docs.rs/actix-web/4.0.0-beta.13/actix_web/index.html
mod middleware;
mod request_id;
mod root_span;
mod root_span_builder;

pub use middleware::{StreamSpan, TracingLogger};
pub use request_id::RequestId;
pub use root_span::RootSpan;
pub use root_span_builder::{DefaultRootSpanBuilder, RootSpanBuilder};
// Re-exporting the `Level` enum since it's used in our `root_span!` macro
pub use tracing::Level;

#[doc(hidden)]
pub mod root_span_macro;

mutually_exclusive_features::none_or_one_of!(
    "opentelemetry_0_13",
    "opentelemetry_0_14",
    "opentelemetry_0_15",
    "opentelemetry_0_16",
    "opentelemetry_0_17",
    "opentelemetry_0_18",
    "opentelemetry_0_19",
    "opentelemetry_0_20",
    "opentelemetry_0_21",
    "opentelemetry_0_22",
    "opentelemetry_0_23",
    "opentelemetry_0_24",
    "opentelemetry_0_25",
    "opentelemetry_0_26",
    "opentelemetry_0_27",
);

#[cfg(any(
    feature = "opentelemetry_0_13",
    feature = "opentelemetry_0_14",
    feature = "opentelemetry_0_15",
    feature = "opentelemetry_0_16",
    feature = "opentelemetry_0_17",
    feature = "opentelemetry_0_18",
    feature = "opentelemetry_0_19",
    feature = "opentelemetry_0_20",
    feature = "opentelemetry_0_21",
    feature = "opentelemetry_0_22",
    feature = "opentelemetry_0_23",
    feature = "opentelemetry_0_24",
    feature = "opentelemetry_0_25",
    feature = "opentelemetry_0_26",
    feature = "opentelemetry_0_27",
))]
mod otel;