Skip to content

Commit 6cd697a

Browse files
authored
Add (optional) OpenTelemetry + Xray integration (#95) (#96)
This introduces all the necessary code to be able to send traces to AWS Xray via `tracing-opentelemetry`. It can be optionally enabled using the `xray` feature defined on this crate. Also update the README.md with instructions on how to enable and use this.
1 parent 4b4ecf0 commit 6cd697a

File tree

4 files changed

+106
-6
lines changed

4 files changed

+106
-6
lines changed

.github/workflows/ci.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,14 @@ jobs:
1515
- uses: actions/checkout@v2
1616
- name: Build
1717
run: cargo build --verbose
18+
- name: Build with OTel feature
19+
run: cargo build --verbose --features otel
1820
- name: Run tests
1921
run: cargo test --verbose
22+
- name: Run tests with OTel feature
23+
run: cargo test --verbose --features otel
2024
- name: rustfmt
2125
uses: actions-rs/cargo@v1
2226
with:
2327
command: fmt
24-
args: --all -- --check
28+
args: --all -- --check

Cargo.toml

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,25 @@ path = "src/bin/server.rs"
2424
async-stream = "0.3.0"
2525
atoi = "0.3.2"
2626
bytes = "1"
27+
rand = "0.8.5"
2728
structopt = "0.3.14"
2829
tokio = { version = "1", features = ["full"] }
2930
tokio-stream = "0.1"
30-
tracing = "0.1.13"
31+
tracing = "0.1.34"
3132
tracing-futures = { version = "0.2.3" }
32-
tracing-subscriber = "0.2.2"
33+
tracing-subscriber = { version = "0.3.11", features = ["env-filter"] }
34+
# Implements the types defined in the OTel spec
35+
opentelemetry = { version = "0.17.0", optional = true }
36+
# Integration between the tracing crate and the opentelemetry crate
37+
tracing-opentelemetry = { version = "0.17.2", optional = true }
38+
# Provides a "propagator" to pass along an XrayId across services
39+
opentelemetry-aws = { version = "0.5.0", optional = true }
40+
# Allows you to send data to the OTel collector
41+
opentelemetry-otlp = { version = "0.10.0", optional = true }
3342

3443
[dev-dependencies]
3544
# Enable test-utilities in dev mode only. This is mostly for tests.
3645
tokio = { version = "1", features = ["test-util"] }
46+
47+
[features]
48+
otel = ["dep:opentelemetry", "dep:tracing-opentelemetry", "dep:opentelemetry-aws", "dep:opentelemetry-otlp"]

README.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,31 @@ cargo run --bin mini-redis-cli set foo bar
5959
cargo run --bin mini-redis-cli get foo
6060
```
6161

62+
## OpenTelemetry
63+
64+
If you are running many instances of your application (which is usually the case
65+
when you are developing a cloud service, for example), you need a way to get all
66+
of your trace data out of your host and into a centralized place. There are many
67+
options here, such as Prometheus, Jaeger, DataDog, Honeycomb, AWS X-Ray etc.
68+
69+
We leverage OpenTelemetry, because it's an open standard that allows for a
70+
single data format to be used for all the options mentioned above (and more).
71+
This eliminates the risk of vendor lock-in, since you can switch between
72+
providers if needed.
73+
74+
### AWS X-Ray example
75+
76+
To enable sending traces to X-Ray, use the `otel` feature:
77+
```
78+
RUST_LOG=debug cargo run --bin mini-redis-server --features otel
79+
```
80+
81+
This will switch `tracing` to use `tracing-opentelemetry`. You will need to
82+
have a copy of AWSOtelCollector running on the same host.
83+
84+
For demo purposes, you can follow the setup documented at
85+
https://github.com/aws-observability/aws-otel-collector/blob/main/docs/developers/docker-demo.md#run-a-single-aws-otel-collector-instance-in-docker
86+
6287
## Supported commands
6388

6489
`mini-redis` currently supports the following commands.

src/bin/server.rs

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,27 @@ use structopt::StructOpt;
1212
use tokio::net::TcpListener;
1313
use tokio::signal;
1414

15+
#[cfg(feature = "otel")]
16+
// To be able to set the XrayPropagator
17+
use opentelemetry::global;
18+
#[cfg(feature = "otel")]
19+
// To configure certain options such as sampling rate
20+
use opentelemetry::sdk::trace as sdktrace;
21+
#[cfg(feature = "otel")]
22+
// For passing along the same XrayId across services
23+
use opentelemetry_aws::trace::XrayPropagator;
24+
#[cfg(feature = "otel")]
25+
// To be able to pass along the XrayId across services
26+
#[cfg(feature = "otel")]
27+
// The `Ext` traits are to allow the Registry to accept the
28+
// OpenTelemetry-specific types (such as `OpenTelemetryLayer`)
29+
use tracing_subscriber::{
30+
fmt, layer::SubscriberExt, util::SubscriberInitExt, util::TryInitError, EnvFilter,
31+
};
32+
1533
#[tokio::main]
1634
pub async fn main() -> mini_redis::Result<()> {
17-
// enable logging
18-
// see https://docs.rs/tracing for more info
19-
tracing_subscriber::fmt::try_init()?;
35+
set_up_logging()?;
2036

2137
let cli = Cli::from_args();
2238
let port = cli.port.as_deref().unwrap_or(DEFAULT_PORT);
@@ -35,3 +51,46 @@ struct Cli {
3551
#[structopt(name = "port", long = "--port")]
3652
port: Option<String>,
3753
}
54+
55+
#[cfg(not(feature = "otel"))]
56+
fn set_up_logging() -> mini_redis::Result<()> {
57+
// See https://docs.rs/tracing for more info
58+
tracing_subscriber::fmt::try_init()
59+
}
60+
61+
#[cfg(feature = "otel")]
62+
fn set_up_logging() -> Result<(), TryInitError> {
63+
// Set the global propagator to X-Ray propagator
64+
// Note: If you need to pass the x-amzn-trace-id across services in the same trace,
65+
// you will need this line. However, this requires additional code not pictured here.
66+
// For a full example using hyper, see:
67+
// https://github.com/open-telemetry/opentelemetry-rust/blob/main/examples/aws-xray/src/server.rs#L14-L26
68+
global::set_text_map_propagator(XrayPropagator::default());
69+
70+
let tracer = opentelemetry_otlp::new_pipeline()
71+
.tracing()
72+
.with_exporter(opentelemetry_otlp::new_exporter().tonic())
73+
.with_trace_config(
74+
sdktrace::config()
75+
.with_sampler(sdktrace::Sampler::AlwaysOn)
76+
// Needed in order to convert the trace IDs into an Xray-compatible format
77+
.with_id_generator(sdktrace::XrayIdGenerator::default()),
78+
)
79+
.install_simple()
80+
.expect("Unable to initialize OtlpPipeline");
81+
82+
// Create a tracing layer with the configured tracer
83+
let opentelemetry = tracing_opentelemetry::layer().with_tracer(tracer);
84+
85+
// Parse an `EnvFilter` configuration from the `RUST_LOG`
86+
// environment variable.
87+
let filter = EnvFilter::from_default_env();
88+
89+
// Use the tracing subscriber `Registry`, or any other subscriber
90+
// that impls `LookupSpan`
91+
tracing_subscriber::registry()
92+
.with(opentelemetry)
93+
.with(filter)
94+
.with(fmt::Layer::default())
95+
.try_init()
96+
}

0 commit comments

Comments
 (0)