Skip to content

Commit c256ade

Browse files
Tracing propogation'
1 parent 6e980cb commit c256ade

File tree

4 files changed

+157
-21
lines changed

4 files changed

+157
-21
lines changed

Cargo.lock

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

processed_data/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ async-graphql-axum = { version = "7.0.2" }
1616
aws-credential-types = { version = "0.56.0" }
1717
aws-sdk-s3 = { version = "0.29.0" }
1818
axum = { version = "0.7.4", features = ["ws"] }
19+
axum-extra = { version = "0.9.3", features = ["typed-header"] }
20+
axum-tracing-opentelemetry = { version = "0.18.0" }
1921
chrono = { version = "0.4.35" }
2022
clap = { version = "4.5.2", features = ["derive", "env"] }
2123
derive_more = { version = "0.99.17" }

processed_data/src/main.rs

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@
77
mod built_info;
88
/// GraphQL resolvers
99
mod graphql;
10+
/// An [`axum::handler::Handler`] for GraphQL
11+
mod route_handlers;
1012

11-
use async_graphql::{extensions::Tracing, http::GraphiQLSource, SDLExportOptions};
12-
use async_graphql_axum::{GraphQL, GraphQLSubscription};
13+
use async_graphql::{http::GraphiQLSource, SDLExportOptions};
1314
use aws_credential_types::{provider::SharedCredentialsProvider, Credentials};
1415
use aws_sdk_s3::{config::Region, Client};
1516
use axum::{response::Html, routing::get, Router};
17+
use axum_tracing_opentelemetry::middleware::{OtelAxumLayer, OtelInResponseLayer};
1618
use clap::{ArgAction::SetTrue, Parser};
1719
use derive_more::{Deref, FromStr, Into};
1820
use graphql::{root_schema_builder, RootSchema};
@@ -26,10 +28,12 @@ use std::{
2628
time::Duration,
2729
};
2830
use tokio::net::TcpListener;
29-
use tracing::instrument;
31+
use tracing::{info, instrument};
3032
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
3133
use url::Url;
3234

35+
use crate::route_handlers::GraphQLHandler;
36+
3337
/// A service providing Beamline ISPyB data collected during sessions
3438
#[derive(Debug, Parser)]
3539
#[command(author, version, about, long_about=None)]
@@ -127,41 +131,40 @@ struct SchemaArgs {
127131
/// Creates a connection pool to access the database
128132
#[instrument(skip(database_url))]
129133
async fn setup_database(database_url: Url) -> Result<DatabaseConnection, TransactionError<DbErr>> {
130-
let connection_options = ConnectOptions::new(database_url.to_string());
134+
info!("Connecting to database at {database_url}");
135+
let connection_options = ConnectOptions::new(database_url.to_string())
136+
.sqlx_logging_level(tracing::log::LevelFilter::Debug)
137+
.to_owned();
131138
let connection = Database::connect(connection_options).await?;
139+
info!("Database connection established: {connection:?}");
132140
Ok(connection)
133141
}
134142

135143
/// Creates an [`axum::Router`] serving GraphiQL, synchronous GraphQL and GraphQL subscriptions
136144
fn setup_router(schema: RootSchema) -> Router {
137145
#[allow(clippy::missing_docs_in_private_items)]
138146
const GRAPHQL_ENDPOINT: &str = "/";
139-
#[allow(clippy::missing_docs_in_private_items)]
140-
const SUBSCRIPTION_ENDPOINT: &str = "/ws";
141147

142148
Router::new()
143149
.route(
144150
GRAPHQL_ENDPOINT,
145151
get(Html(
146-
GraphiQLSource::build()
147-
.endpoint(GRAPHQL_ENDPOINT)
148-
.subscription_endpoint(SUBSCRIPTION_ENDPOINT)
149-
.finish(),
152+
GraphiQLSource::build().endpoint(GRAPHQL_ENDPOINT).finish(),
150153
))
151-
.post_service(GraphQL::new(schema.clone())),
154+
.post(GraphQLHandler::new(schema)),
152155
)
153-
.route_service(SUBSCRIPTION_ENDPOINT, GraphQLSubscription::new(schema))
156+
.layer(OtelInResponseLayer)
157+
.layer(OtelAxumLayer::default())
154158
}
155159

156160
/// Serves the endpoints on the specified port forever
157161
async fn serve(router: Router, port: u16) -> Result<(), std::io::Error> {
158162
let socket_addr = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, port));
159163
let listener = TcpListener::bind(socket_addr).await?;
160-
println!("GraphiQL IDE: {}", socket_addr);
164+
println!("Serving API & GraphQL UI at {}", socket_addr);
161165
axum::serve(listener, router.into_make_service()).await?;
162166
Ok(())
163167
}
164-
165168
/// Sets up Logging & Tracing using opentelemetry if available
166169
fn setup_telemetry(
167170
log_level: tracing::Level,
@@ -180,6 +183,9 @@ fn setup_telemetry(
180183
),
181184
]);
182185
let (metrics_layer, tracing_layer) = if let Some(otel_collector_url) = otel_collector_url {
186+
opentelemetry::global::set_text_map_propagator(
187+
opentelemetry_sdk::propagation::TraceContextPropagator::default(),
188+
);
183189
(
184190
Some(tracing_opentelemetry::MetricsLayer::new(
185191
opentelemetry_otlp::new_pipeline()
@@ -232,13 +238,7 @@ async fn main() {
232238
Cli::Serve(args) => {
233239
setup_telemetry(args.log_level, args.otel_collector_url).unwrap();
234240
let database = setup_database(args.database_url).await.unwrap();
235-
let s3_client = aws_sdk_s3::Client::from_s3_client_args(args.s3_client);
236-
let schema = root_schema_builder()
237-
.extension(Tracing)
238-
.data(database)
239-
.data(s3_client)
240-
.data(args.s3_bucket)
241-
.finish();
241+
let schema = root_schema_builder().data(database).finish();
242242
let router = setup_router(schema);
243243
serve(router, args.port).await.unwrap();
244244
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
use async_graphql::Executor;
2+
use async_graphql_axum::{GraphQLRequest, GraphQLResponse};
3+
use axum::{
4+
extract::Request,
5+
handler::Handler,
6+
http::StatusCode,
7+
response::{IntoResponse, Response},
8+
RequestExt,
9+
};
10+
use axum_extra::{
11+
headers::{authorization::Bearer, Authorization},
12+
TypedHeader,
13+
};
14+
use std::{future::Future, pin::Pin};
15+
16+
/// An [`Handler`] which executes an [`Executor`] including the [`Authorization<Bearer>`] in the [`async_graphql::Context`]
17+
#[derive(Debug, Clone)]
18+
pub struct GraphQLHandler<E: Executor> {
19+
/// The GraphQL executor used to process the request
20+
executor: E,
21+
}
22+
23+
impl<E: Executor> GraphQLHandler<E> {
24+
/// Constructs an instance of the handler with the provided schema.
25+
pub fn new(executor: E) -> Self {
26+
Self { executor }
27+
}
28+
}
29+
30+
impl<S, E> Handler<((),), S> for GraphQLHandler<E>
31+
where
32+
E: Executor,
33+
{
34+
type Future = Pin<Box<dyn Future<Output = Response> + Send + 'static>>;
35+
36+
fn call(self, mut req: Request, _state: S) -> Self::Future {
37+
Box::pin(async move {
38+
let token = req
39+
.extract_parts::<TypedHeader<Authorization<Bearer>>>()
40+
.await
41+
.ok()
42+
.map(|token| token.0);
43+
let request = req.extract::<GraphQLRequest, _>().await;
44+
match request {
45+
Ok(request) => GraphQLResponse::from(
46+
self.executor
47+
.execute(request.into_inner().data(token))
48+
.await,
49+
)
50+
.into_response(),
51+
Err(err) => (StatusCode::BAD_REQUEST, err.0.to_string()).into_response(),
52+
}
53+
})
54+
}
55+
}

0 commit comments

Comments
 (0)