async_graphql/extensions/
tracing.rs

1use std::sync::Arc;
2
3use futures_util::{stream::BoxStream, TryFutureExt};
4use tracing_futures::Instrument;
5use tracinglib::{span, Level};
6
7use crate::{
8    extensions::{
9        Extension, ExtensionContext, ExtensionFactory, NextExecute, NextParseQuery, NextRequest,
10        NextResolve, NextSubscribe, NextValidation, ResolveInfo,
11    },
12    parser::types::ExecutableDocument,
13    Response, ServerError, ServerResult, ValidationResult, Value, Variables,
14};
15
16/// Tracing extension
17///
18/// # References
19///
20/// <https://crates.io/crates/tracing>
21///
22/// # Examples
23///
24/// ```no_run
25/// use async_graphql::{extensions::Tracing, *};
26///
27/// #[derive(SimpleObject)]
28/// struct Query {
29///     value: i32,
30/// }
31///
32/// let schema = Schema::build(Query { value: 100 }, EmptyMutation, EmptySubscription)
33///     .extension(Tracing)
34///     .finish();
35///
36/// # tokio::runtime::Runtime::new().unwrap().block_on(async {
37/// schema.execute(Request::new("{ value }")).await;
38/// # });
39/// ```
40#[cfg_attr(docsrs, doc(cfg(feature = "tracing")))]
41pub struct Tracing;
42
43impl ExtensionFactory for Tracing {
44    fn create(&self) -> Arc<dyn Extension> {
45        Arc::new(TracingExtension)
46    }
47}
48
49struct TracingExtension;
50
51#[async_trait::async_trait]
52impl Extension for TracingExtension {
53    async fn request(&self, ctx: &ExtensionContext<'_>, next: NextRequest<'_>) -> Response {
54        next.run(ctx)
55            .instrument(span!(
56                target: "async_graphql::graphql",
57                Level::INFO,
58                "request",
59            ))
60            .await
61    }
62
63    fn subscribe<'s>(
64        &self,
65        ctx: &ExtensionContext<'_>,
66        stream: BoxStream<'s, Response>,
67        next: NextSubscribe<'_>,
68    ) -> BoxStream<'s, Response> {
69        Box::pin(next.run(ctx, stream).instrument(span!(
70            target: "async_graphql::graphql",
71            Level::INFO,
72            "subscribe",
73        )))
74    }
75
76    async fn parse_query(
77        &self,
78        ctx: &ExtensionContext<'_>,
79        query: &str,
80        variables: &Variables,
81        next: NextParseQuery<'_>,
82    ) -> ServerResult<ExecutableDocument> {
83        let span = span!(
84            target: "async_graphql::graphql",
85            Level::INFO,
86            "parse",
87            source = tracinglib::field::Empty
88        );
89        async move {
90            let res = next.run(ctx, query, variables).await;
91            if let Ok(doc) = &res {
92                tracinglib::Span::current()
93                    .record("source", ctx.stringify_execute_doc(doc, variables).as_str());
94            }
95            res
96        }
97        .instrument(span)
98        .await
99    }
100
101    async fn validation(
102        &self,
103        ctx: &ExtensionContext<'_>,
104        next: NextValidation<'_>,
105    ) -> Result<ValidationResult, Vec<ServerError>> {
106        let span = span!(
107            target: "async_graphql::graphql",
108            Level::INFO,
109            "validation"
110        );
111        next.run(ctx).instrument(span).await
112    }
113
114    async fn execute(
115        &self,
116        ctx: &ExtensionContext<'_>,
117        operation_name: Option<&str>,
118        next: NextExecute<'_>,
119    ) -> Response {
120        let span = span!(
121            target: "async_graphql::graphql",
122            Level::INFO,
123            "execute"
124        );
125        next.run(ctx, operation_name).instrument(span).await
126    }
127
128    async fn resolve(
129        &self,
130        ctx: &ExtensionContext<'_>,
131        info: ResolveInfo<'_>,
132        next: NextResolve<'_>,
133    ) -> ServerResult<Option<Value>> {
134        let span = if !info.is_for_introspection {
135            Some(span!(
136                target: "async_graphql::graphql",
137                Level::INFO,
138                "field",
139                path = %info.path_node,
140                parent_type = %info.parent_type,
141                return_type = %info.return_type,
142            ))
143        } else {
144            None
145        };
146
147        let fut = next.run(ctx, info).inspect_err(|err| {
148            tracinglib::info!(
149                target: "async_graphql::graphql",
150                error = %err.message,
151                "error",
152            );
153        });
154        match span {
155            Some(span) => fut.instrument(span).await,
156            None => fut.await,
157        }
158    }
159}