Skip to content

Commit d44d192

Browse files
authored
Feature/opentelemetry 0.28 (#15)
* opentelemetry 0.28 * Use cache mount in Dockerfile
1 parent 633077b commit d44d192

13 files changed

+322
-372
lines changed

Cargo.lock

Lines changed: 226 additions & 301 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ members = [
66
]
77

88
[workspace.dependencies]
9-
axum = { version = "0.8" }
9+
axum = { version = "0.8.1" }
1010
tower = "0.5.1"
1111
reqwest = "0.12.7"
12-
opentelemetry = "0.27.1"
12+
opentelemetry = "0.28.0"
1313
tracing = { version = "0.1.41" }
14-
tracing-opentelemetry = { version = "0.28.0" }
14+
tracing-opentelemetry = { version = "0.29.0" }

Dockerfile

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,22 @@
1-
ARG RUST_VERSION=1.84.0
1+
ARG RUST_VERSION=1.84
22

33
FROM rust:${RUST_VERSION}-bookworm AS builder
44
WORKDIR /usr/src/bookapp
5-
COPY . .
65
ENV SQLX_OFFLINE=true
7-
RUN cargo install --path bookapp
6+
7+
# Copy the full source and build the app in release mode
8+
COPY . .
9+
10+
RUN --mount=type=cache,target=/usr/local/cargo/registry \
11+
--mount=type=cache,target=/usr/src/bookapp/target \
12+
cargo build --release --package bookapp && \
13+
mv /usr/src/bookapp/target/release/bookapp /bookapp
14+
815

916
FROM debian:bookworm-slim
1017
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates wget && rm -rf /var/lib/apt/lists/*
11-
COPY --from=builder /usr/local/cargo/bin/bookapp /usr/local/bin/bookapp
18+
19+
COPY --from=builder /bookapp /usr/local/bin/bookapp
1220

1321
ENV RUST_LOG="info,sqlx=info,bookapp=debug,backend=debug"
1422
CMD ["bookapp"]

bookapp/Cargo.toml

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,19 @@ edition = "2021"
66

77
[dependencies]
88
client = { path = "../client" }
9-
tower-otel-http-metrics = { version = "0.10.0", features = ["axum"] }
9+
tower-otel-http-metrics = { version = "0.13.0", features = ["axum"] }
1010

11-
anyhow = "1.0.95"
11+
anyhow = "1.0.97"
1212
axum = { workspace = true, features = ["macros", "matched-path", "tracing" ] }
1313
dotenv = "0.15.0"
1414
opentelemetry = { workspace = true}
15-
opentelemetry-otlp = { version="0.27.0" , features = [
15+
opentelemetry-otlp = { version="0.28.0" , features = [
1616
"serialize",
1717
"reqwest-client",
18+
"grpc-tonic",
1819
]}
19-
opentelemetry_sdk = { version="0.27.1", features = ["trace", "opentelemetry-http", "rt-tokio", "tracing"]}
20-
serde = "1.0.217"
20+
opentelemetry_sdk = { version="0.28.0", features = ["trace", "opentelemetry-http", "rt-tokio", "tracing"]}
21+
serde = "1.0.218"
2122
sqlx = { version = "0.8.3", features = ["runtime-tokio", "postgres"] }
2223
tokio = { version = "1.43", features = ["full", "tracing"] }
2324
tower = {workspace = true}
@@ -27,23 +28,24 @@ tracing-opentelemetry = { workspace = true , features=["async-trait"]}
2728
tracing-subscriber = { version = "0.3.19", features=["fmt", "env-filter", "json", "tracing-log"] }
2829
reqwest = {workspace = true}
2930

30-
reqwest-middleware = { version="0.4.0", features = ["json"] }
31-
reqwest-tracing = { version="0.5.5", features = ["opentelemetry_0_27"] }
31+
reqwest-middleware = { version="0.4.1", features = ["json"] }
32+
reqwest-tracing = { version="0.5.6", features = ["opentelemetry_0_28"] }
3233

3334
futures = "0.3.31"
3435

35-
axum-tracing-opentelemetry = { version = "0.25", features = [] }
36-
tracing-opentelemetry-instrumentation-sdk = { version = "0.24.2", features = ["tracing_level_info"] }
36+
axum-tracing-opentelemetry = { version = "0.26.1", features = [] }
37+
38+
tracing-opentelemetry-instrumentation-sdk = { version = "0.26.0", features = ["tracing_level_info"] }
3739

3840

3941
rdkafka = { version = "0.37.0", features = ["tokio"] }
40-
serde_json = "1.0.137"
41-
opentelemetry-http = "0.27.0"
42+
serde_json = "1.0.140"
43+
opentelemetry-http = "0.28.0"
4244
tracing-loki = "0.2.6"
4345
hostname = "0.4.0"
44-
opentelemetry-appender-tracing = "0.27.0"
46+
opentelemetry-appender-tracing = "0.28.1"
4547
hyper = "1.6.0"
4648
rand = "0.9.0"
4749
matchit = "^0.8"
4850
console-subscriber = "0.4.1"
49-
async-trait = "0.1.86"
51+
async-trait = "0.1.87"

bookapp/src/book_ingestion.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ pub async fn run_consumer() -> Result<()> {
176176
};
177177

178178
// Create a new root span via tracing:
179-
let span = tracing::info_span!("book_ingestion", "otel.kind" = "consumer",);
179+
let span = tracing::info_span!("book_ingestion", "otel.kind" = "Consumer");
180180

181181
// Extract tracing context from headers
182182
let headers = m.headers();

bookapp/src/db.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use tracing::{debug, info};
88
pub struct BookCreateIn {
99
pub title: String,
1010
pub author: String,
11-
#[serde(skip_serializing_if = "Option::is_none")]
11+
#[serde(skip_serializing_if = "Option::is_none", default)]
1212
pub status: Option<BookStatus>,
1313
}
1414

@@ -78,22 +78,24 @@ pub async fn get_book(connection_pool: &PgPool, id: i32) -> Result<Book> {
7878
.await?)
7979
}
8080

81+
#[tracing::instrument(skip(connection_pool), name = "create_book_in_db")]
8182
pub async fn create_book(
8283
connection_pool: &PgPool,
8384
author: String,
8485
title: String,
85-
status: Option<BookStatus>,
86+
status: BookStatus,
8687
) -> Result<i32> {
8788
Ok(sqlx::query!(
8889
r#"insert into books (title, author, status) VALUES ($1, $2, $3) returning id"#,
8990
title,
9091
author,
91-
status as Option<BookStatus>,
92+
status as BookStatus,
9293
)
9394
.fetch_one(connection_pool)
9495
.await?
9596
.id)
9697
}
98+
9799
pub async fn delete_book(connection_pool: &PgPool, id: i32) -> Result<()> {
98100
sqlx::query!("delete from books where id=$1", id)
99101
.execute(connection_pool)

bookapp/src/error_injection_middleware.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -325,8 +325,8 @@ pub async fn error_injection_middleware(
325325
tracing::Span::current().record("error_rate", &config.error_rate);
326326

327327
// Generate a random number between 0.0 and 1.0
328-
let mut rng = rand::thread_rng();
329-
let random_value: f64 = rng.gen();
328+
let mut rng = rand::rng();
329+
let random_value: f64 = rng.random();
330330

331331
if random_value < config.error_rate {
332332
tracing::debug!(
@@ -414,6 +414,6 @@ pub fn error_injection_service(
414414
) -> Router {
415415
Router::new()
416416
.route("/", get(get_all_configs_handler).post(create_config))
417-
.route("/:id", put(update_config).delete(delete_config))
417+
.route("/{id}", put(update_config).delete(delete_config))
418418
.layer(Extension(error_injection_store))
419419
}

bookapp/src/main.rs

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,14 @@ fn router(connection_pool: PgPool, producer: FutureProducer) -> Router {
3636
.layer(Extension(producer))
3737
// Our custom error injection layer can inject errors
3838
// This layer itself can be traced - so needs to be added before our OtelAxumLayer
39-
// .layer(axum::middleware::from_fn_with_state(
40-
// error_injection_store.clone(),
41-
// error_injection_middleware::error_injection_middleware,
42-
// ))
43-
// .nest_service(
44-
// "/error-injection",
45-
// error_injection_middleware::error_injection_service(error_injection_store.clone()),
46-
// )
39+
.layer(axum::middleware::from_fn_with_state(
40+
error_injection_store.clone(),
41+
error_injection_middleware::error_injection_middleware,
42+
))
43+
.nest_service(
44+
"/error-injection",
45+
error_injection_middleware::error_injection_service(error_injection_store.clone()),
46+
)
4747
.layer(Extension(connection_pool))
4848
// This layer creates a new Tracing span called "request" for each request,
4949
// it logs headers etc but on its own doesn't do the OTEL trace context propagation.
@@ -60,7 +60,7 @@ fn router(connection_pool: PgPool, producer: FutureProducer) -> Router {
6060
// as long as not filtered out!
6161
.layer(OtelAxumLayer::default())
6262
.layer(
63-
tower_otel_http_metrics::HTTPMetricsLayerBuilder::new()
63+
tower_otel_http_metrics::HTTPMetricsLayerBuilder::builder()
6464
.with_meter(opentelemetry::global::meter(env!("CARGO_CRATE_NAME")))
6565
.build()
6666
.expect("Failed to build otel metrics layer"),
@@ -79,7 +79,7 @@ async fn main() -> Result<()> {
7979
let enable_kafka_producer =
8080
std::env::var("ENABLE_KAFKA_PRODUCER").unwrap_or_else(|_| "false".to_string()) == "true";
8181

82-
tracing_config::init_tracing();
82+
let (trace_provider, meter_provider, log_provider) = tracing_config::init_tracing();
8383

8484
// Init db
8585
info!("Setting up Database");
@@ -129,7 +129,9 @@ async fn main() -> Result<()> {
129129

130130
info!("Shutting down OpenTelemetry");
131131

132-
global::shutdown_tracer_provider();
132+
trace_provider.shutdown()?;
133+
meter_provider.shutdown()?;
134+
log_provider.shutdown()?;
133135

134136
Ok(())
135137
}

bookapp/src/rest.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use axum::http::StatusCode;
55
use axum::routing::{delete, get, patch, post};
66
use axum::{extract, http::Request, Extension, Json, Router};
77

8-
use opentelemetry::trace::TraceContextExt;
8+
use opentelemetry::trace::{TraceContextExt, SpanKind};
99
use rdkafka::producer::FutureProducer;
1010
use sqlx::PgPool;
1111
use tracing::{Instrument, Level};
@@ -56,7 +56,7 @@ async fn get_all_books(Extension(con): Extension<PgPool>) -> Result<Json<Vec<Boo
5656
}
5757
}
5858

59-
#[tracing::instrument(fields(otel.kind = "client"))]
59+
#[tracing::instrument(fields(book_id, otel.kind = "Client"))]
6060
async fn get_book_details_with_progenitor_client(
6161
book_id: i32,
6262
) -> Result<client::ResponseValue<client::types::Book>, client::Error> {
@@ -137,16 +137,16 @@ async fn create_book(
137137
Extension(producer): Extension<FutureProducer>,
138138
Json(book): Json<BookCreateIn>,
139139
) -> Result<Json<i32>, StatusCode> {
140-
if let Ok(new_id) = db::create_book(&con, book.author, book.title, book.status).await {
140+
let status = book.status.unwrap_or(BookStatus::Available);
141+
if let Ok(new_id) = db::create_book(&con, book.author, book.title, status).await {
141142
queue_background_ingestion_task(&producer, new_id).await;
142-
143143
Ok(Json(new_id))
144144
} else {
145-
Err(StatusCode::NOT_FOUND)
145+
Err(StatusCode::INTERNAL_SERVER_ERROR)
146146
}
147147
}
148148

149-
#[tracing::instrument(skip(producer), fields(otel.kind = "producer"))]
149+
#[tracing::instrument(skip(producer), fields(otel.kind = "Producer"))]
150150
async fn queue_background_ingestion_task(producer: &FutureProducer, new_id: i32) {
151151
// Prepare message
152152
let book_message = crate::book_ingestion::BookIngestionMessage { book_id: new_id };

bookapp/src/tracing_config.rs

Lines changed: 34 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,26 @@
11
use opentelemetry::trace::TracerProvider;
22
use opentelemetry_otlp::{LogExporter, WithExportConfig};
3-
use opentelemetry_sdk::logs::LoggerProvider;
4-
use opentelemetry_sdk::metrics::{PeriodicReader, SdkMeterProvider};
3+
use opentelemetry_sdk::logs::SdkLoggerProvider;
4+
use opentelemetry_sdk::metrics::{SdkMeterProvider};
55
use opentelemetry_sdk::propagation::TraceContextPropagator;
6+
use opentelemetry_sdk::trace::SdkTracerProvider;
67
use tracing_subscriber::layer::SubscriberExt;
78
use tracing_subscriber::Layer;
89

910
fn init_meter_provider(
10-
) -> Result<opentelemetry_sdk::metrics::SdkMeterProvider, opentelemetry_sdk::metrics::MetricError> {
11+
) -> Result<SdkMeterProvider, opentelemetry_sdk::metrics::MetricError> {
1112
let exporter = opentelemetry_otlp::MetricExporter::builder()
1213
.with_tonic()
1314
.with_timeout(std::time::Duration::from_secs(10))
1415
.build()?;
1516

16-
let reader = PeriodicReader::builder(exporter, opentelemetry_sdk::runtime::Tokio).build();
17-
1817
let provider = SdkMeterProvider::builder()
19-
.with_reader(reader)
20-
.with_resource(opentelemetry_sdk::Resource::default())
21-
.with_resource(opentelemetry_sdk::Resource::new(vec![
22-
opentelemetry::KeyValue::new("service.name", "bookapp"),
23-
]))
18+
.with_periodic_exporter(exporter)
19+
.with_resource(
20+
opentelemetry_sdk::Resource::builder()
21+
.with_attributes(vec![opentelemetry::KeyValue::new("service.name", "bookapp")])
22+
.build()
23+
)
2424
.build();
2525

2626
let cloned_provider = provider.clone();
@@ -29,38 +29,46 @@ fn init_meter_provider(
2929
}
3030

3131
fn init_logger_provider(
32-
) -> Result<opentelemetry_sdk::logs::LoggerProvider, opentelemetry_sdk::logs::LogError> {
32+
) -> Result<opentelemetry_sdk::logs::SdkLoggerProvider, opentelemetry_sdk::logs::LogError> {
3333
// Note Opentelemetry does not provide a global API to manage the logger provider.
34-
let exporter = LogExporter::builder().with_tonic().build()?;
35-
36-
let provider = LoggerProvider::builder()
37-
.with_batch_exporter(exporter, opentelemetry_sdk::runtime::Tokio)
38-
.build();
34+
let exporter = LogExporter::builder()
35+
.with_tonic()
36+
.build()?;
3937

40-
Ok(provider)
38+
Ok(SdkLoggerProvider::builder()
39+
//.with_resource()
40+
.with_batch_exporter(exporter)
41+
.build())
4142
}
4243

43-
pub fn init_tracing() {
44+
pub fn init_tracing() -> (
45+
SdkTracerProvider, SdkMeterProvider, SdkLoggerProvider
46+
) {
4447
opentelemetry::global::set_text_map_propagator(TraceContextPropagator::new());
4548

4649
// Metrics
4750
let meter_provider = init_meter_provider().unwrap();
48-
let opentelemetry_metrics_layer = tracing_opentelemetry::MetricsLayer::new(meter_provider);
51+
let opentelemetry_metrics_layer = tracing_opentelemetry::MetricsLayer::new(meter_provider.clone());
4952

5053
// Tracing
5154
// Uses OTEL_EXPORTER_OTLP_TRACES_ENDPOINT
52-
// Assumes a GRPC endpoint (e.g port 4317)
55+
// Assumes a GRPC endpoint (e.g., port 4317)
5356
let exporter = opentelemetry_otlp::SpanExporter::builder()
5457
.with_tonic()
5558
.build()
56-
.unwrap();
59+
.expect("Failed to create OTLP span exporter");
5760

58-
let tracer_provider = opentelemetry_sdk::trace::TracerProvider::builder()
59-
.with_batch_exporter(exporter, opentelemetry_sdk::runtime::Tokio)
61+
let tracer_provider = SdkTracerProvider::builder()
62+
.with_batch_exporter(exporter)
6063
//.with_resource(opentelemetry_sdk::Resource::default())
6164
.build();
6265

6366
// Explicitly set the tracer provider globally
67+
// Setting global tracer provider is required if other parts of the application
68+
// uses global::tracer() or global::tracer_with_version() to get a tracer.
69+
// Cloning simply creates a new reference to the same tracer provider. It is
70+
// important to hold on to the tracer_provider here, to invoke
71+
// shutdown on it when application ends.
6472
opentelemetry::global::set_tracer_provider(tracer_provider.clone());
6573

6674
// Filter the tracing layer - we can add custom filters that only impact the tracing layer
@@ -127,4 +135,7 @@ pub fn init_tracing() {
127135

128136
// Set the subscriber as the global default
129137
tracing::subscriber::set_global_default(subscriber).expect("Failed to set subscriber");
138+
139+
// Return the tracer, meter and logger provider as a tuple for shutdown
140+
(tracer_provider, meter_provider, log_provider)
130141
}

client/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ edition = "2021"
88
[dependencies]
99
tracing = { workspace = true }
1010
reqwest = { workspace = true, features = ["json", "stream"] }
11-
serde = { version = "1.0.217", features = ["derive", "rc"] }
12-
serde_json = { version = "1.0.138" }
11+
serde = { version = "1.0.218", features = ["derive", "rc"] }
12+
serde_json = { version = "1.0.140" }
1313
opentelemetry = { workspace = true}
1414
tracing-opentelemetry = { workspace = true, features=["async-trait"] }
1515
progenitor-client = { version = "0.9.1" }

docker-compose.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ services:
8181

8282

8383
telemetry:
84-
image: grafana/otel-lgtm:0.8.4
84+
image: grafana/otel-lgtm:0.8.5
8585
depends_on:
8686
db:
8787
condition: service_healthy

requests/get.http

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ GET http://localhost:8000/books
44
Accept: application/json
55

66
### GET a book
7-
GET http://localhost:8000/books/98
7+
GET http://localhost:8000/books/1
88
Accept: application/json
99

1010
### Create a book

0 commit comments

Comments
 (0)