1
0
mirror of https://github.com/actix/actix-extras.git synced 2025-07-01 20:25:09 +02:00

Adopt OpenTelemetry's semantic convention (#15)

* Add http.route.

* Align all fields with OpenTelemetry's semantic conventions.

* Add span kind.

* Emit event for errors.
Add OTEL status code.

* Create otel.status_code field as empty.

* Fix errors.

* Add (feature-gated) support for OpenTelemetry span propagation.

* Capture the trace id as an attribute on the span.

* Change message.

* Log the newly-generated trace id if there is no parent context.

* Define a root_span macro as a stepping stone to allow crate users to add their own fields to the root span.

* Add comments.

* mut is no longer necessary.

* Allow users to customise generation of the root span. Split recording fields on span end from emission of log record. Make log record on error optional via feature flag.

* Provide constructor + default implementation.

* Explode into multiple modules.
Fix various paths/private imports in root_span.

* Rename module to root_span_macro.

* Add a new extractor to retrieve the root span.

* Document crate.

* Docs!

* Add section on OTEL.

* Mention actix-web-opentelemetry.

* Add OpenTelemetry example.

* Improve readme.

* Add custom root span example.

Co-authored-by: LukeMathWalker <contact@palmieri.com>
This commit is contained in:
Luca Palmieri
2021-04-25 12:19:27 +01:00
committed by GitHub
parent 1025372493
commit 7da6ea91ac
14 changed files with 1057 additions and 207 deletions

View File

@ -0,0 +1,17 @@
[package]
name = "custom-root-span"
version = "0.1.0"
authors = ["LukeMathWalker <contact@lpalmieri.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
actix-web = "4.0.0-beta.6"
opentelemetry = { version = "0.13", features = ["rt-tokio-current-thread"] }
opentelemetry-jaeger = { version = "0.12", features = ["tokio"] }
tracing-opentelemetry = { version = "0.12" }
tracing = "0.1.19"
tracing-subscriber = { version = "0.2.12", features = ["registry", "env-filter"] }
tracing-bunyan-formatter = "0.1.6"
tracing-actix-web = { path = "../.." }

View File

@ -0,0 +1,38 @@
# Custom root span
## Running
You can launch this example with
```bash
cargo run
```
An `actix-web` application will be listening on port `8080`.
You can fire requests to it with:
```bash
curl -v http://localhost:8080/hello
```
```text
Hello world!
```
or
```bash
curl -v http://localhost:8080/hello/my-name
```
```text
Hello my-name!
```
## Visualising traces
Spans will be also printed to the console in JSON format, as structured log records.
You can look at the exported spans in your browser by visiting [http://localhost:16686](http://localhost:16686) if you launch a Jaeger instance:
```bash
docker run -d -p6831:6831/udp -p6832:6832/udp -p16686:16686 jaegertracing/all-in-one:latest
```

View File

@ -0,0 +1,93 @@
use actix_web::dev::{ServiceRequest, ServiceResponse};
use actix_web::{web, App, Error, HttpServer};
use opentelemetry::{
global, runtime::TokioCurrentThread, sdk::propagation::TraceContextPropagator,
};
use std::io;
use tracing::Span;
use tracing_actix_web::{DefaultRootSpanBuilder, RootSpan, RootSpanBuilder, TracingLogger};
use tracing_bunyan_formatter::{BunyanFormattingLayer, JsonStorageLayer};
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::{EnvFilter, Registry};
/// We will define a custom root span builder to capture additional fields, specific
/// to our application, on top of the ones provided by `DefaultRootSpanBuilder` out of the box.
pub struct CustomRootSpanBuilder;
impl RootSpanBuilder for CustomRootSpanBuilder {
fn on_request_start(request: &ServiceRequest) -> Span {
// Not sure why you'd be keen to capture this, but it's an example and we try to keep it simple
let n_headers = request.headers().len();
// We set `cloud_provider` to a constant value.
//
// `name` is not known at this point - we delegate the responsibility to populate it
// to the `personal_hello` handler. We MUST declare the field though, otherwise
// `span.record("caller_name", XXX)` will just be silently ignored by `tracing`.
tracing_actix_web::root_span!(
request,
n_headers,
cloud_provider = "localhost",
caller_name = tracing::field::Empty
)
}
fn on_request_end<B>(span: Span, outcome: &Result<ServiceResponse<B>, Error>) {
// Capture the standard fields when the request finishes.
DefaultRootSpanBuilder::on_request_end(span, outcome);
}
}
async fn hello() -> &'static str {
"Hello world!"
}
async fn personal_hello(root_span: RootSpan, name: web::Path<String>) -> String {
// Add more context to the root span of the request.
root_span.record("caller_name", &name.as_str());
format!("Hello {}!", name)
}
#[actix_web::main]
async fn main() -> io::Result<()> {
init_telemetry();
HttpServer::new(move || {
App::new()
.wrap(TracingLogger::<CustomRootSpanBuilder>::new())
.service(web::resource("/hello").to(hello))
.service(web::resource("/hello/{name}").to(personal_hello))
})
.bind("127.0.0.1:8080")?
.run()
.await?;
// Ensure all spans have been shipped to Jaeger.
opentelemetry::global::shutdown_tracer_provider();
Ok(())
}
/// Init a `tracing` subscriber that prints spans to stdout as well as
/// ships them to Jaeger.
///
/// Check the `opentelemetry` example for more details.
fn init_telemetry() {
let app_name = "tracing-actix-web-demo";
global::set_text_map_propagator(TraceContextPropagator::new());
let tracer = opentelemetry_jaeger::new_pipeline()
.with_service_name(app_name)
.install_batch(TokioCurrentThread)
.expect("Failed to install OpenTelemetry tracer.");
let env_filter = EnvFilter::try_from_default_env().unwrap_or(EnvFilter::new("info"));
let telemetry = tracing_opentelemetry::layer().with_tracer(tracer);
let formatting_layer = BunyanFormattingLayer::new(app_name.into(), std::io::stdout);
let subscriber = Registry::default()
.with(env_filter)
.with(telemetry)
.with(JsonStorageLayer)
.with(formatting_layer);
tracing::subscriber::set_global_default(subscriber)
.expect("Failed to install `tracing` subscriber.")
}

View File

@ -0,0 +1,17 @@
[package]
name = "otel"
version = "0.1.0"
authors = ["Luca Palmieri <rust@lpalmieri.com>"]
edition = "2018"
license = "MIT/Apache-2.0"
[dependencies]
actix-web = "4.0.0-beta.6"
tracing = "0.1.19"
opentelemetry = { version = "0.13", features = ["rt-tokio-current-thread"] }
opentelemetry-jaeger = { version = "0.12", features = ["tokio"] }
tracing-opentelemetry = { version = "0.12" }
tracing-subscriber = { version = "0.2.12", features = ["registry", "env-filter"] }
tracing-bunyan-formatter = "0.1.6"
tracing-actix-web = { path = "../.." }

View File

@ -0,0 +1,33 @@
# OpenTelemetry integration
## Prerequisites
To execute this example you need a running Jaeger instance.
You can launch one using Docker:
```bash
docker run -d -p6831:6831/udp -p6832:6832/udp -p16686:16686 jaegertracing/all-in-one:latest
```
## Running
You can launch this example with
```bash
cargo run
```
An `actix-web` application will be listening on port `8080`.
You can fire requests to it with:
```bash
curl -v http://localhost:8080/hello
```
```text
Hello world!
```
## Traces
You can look at the exported traces in your browser by visiting [http://localhost:16686](http://localhost:16686).
Spans will be also printed to the console in JSON format, as structured log records.

View File

@ -0,0 +1,60 @@
use actix_web::{web, App, HttpServer};
use opentelemetry::{
global, runtime::TokioCurrentThread, sdk::propagation::TraceContextPropagator,
};
use std::io;
use tracing_actix_web::TracingLogger;
use tracing_bunyan_formatter::{BunyanFormattingLayer, JsonStorageLayer};
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::{EnvFilter, Registry};
async fn hello() -> &'static str {
"Hello world!"
}
fn init_telemetry() {
let app_name = "tracing-actix-web-demo";
// Start a new Jaeger trace pipeline.
// Spans are exported in batch - recommended setup for a production application.
global::set_text_map_propagator(TraceContextPropagator::new());
let tracer = opentelemetry_jaeger::new_pipeline()
.with_service_name(app_name)
.install_batch(TokioCurrentThread)
.expect("Failed to install OpenTelemetry tracer.");
// Filter based on level - trace, debug, info, warn, error
// Tunable via `RUST_LOG` env variable
let env_filter = EnvFilter::try_from_default_env().unwrap_or(EnvFilter::new("info"));
// Create a `tracing` layer using the Jaeger tracer
let telemetry = tracing_opentelemetry::layer().with_tracer(tracer);
// Create a `tracing` layer to emit spans as structured logs to stdout
let formatting_layer = BunyanFormattingLayer::new(app_name.into(), std::io::stdout);
// Combined them all together in a `tracing` subscriber
let subscriber = Registry::default()
.with(env_filter)
.with(telemetry)
.with(JsonStorageLayer)
.with(formatting_layer);
tracing::subscriber::set_global_default(subscriber)
.expect("Failed to install `tracing` subscriber.")
}
#[actix_web::main]
async fn main() -> io::Result<()> {
init_telemetry();
HttpServer::new(move || {
App::new()
.wrap(TracingLogger::default())
.service(web::resource("/hello").to(hello))
})
.bind("127.0.0.1:8080")?
.run()
.await?;
// Ensure all spans have been shipped to Jaeger.
opentelemetry::global::shutdown_tracer_provider();
Ok(())
}