async_graphql/extensions/
tracing.rs1use std::sync::Arc;
2
3use futures_util::{TryFutureExt, stream::BoxStream};
4use tracing_futures::Instrument;
5use tracinglib::{Level, span};
6
7use crate::{
8 Response, ServerError, ServerResult, ValidationResult, Value, Variables,
9 extensions::{
10 Extension, ExtensionContext, ExtensionFactory, NextExecute, NextParseQuery, NextRequest,
11 NextResolve, NextSubscribe, NextValidation, ResolveInfo,
12 },
13 parser::types::ExecutableDocument,
14 registry::MetaTypeName,
15};
16
17#[cfg_attr(docsrs, doc(cfg(feature = "tracing")))]
42pub struct Tracing;
43
44impl Tracing {
45 pub fn config() -> TracingConfig {
63 TracingConfig::default()
64 }
65}
66
67impl ExtensionFactory for Tracing {
68 fn create(&self) -> Arc<dyn Extension> {
69 Arc::new(TracingExtension {
70 trace_scalars: false,
71 })
72 }
73}
74
75#[cfg_attr(docsrs, doc(cfg(feature = "tracing")))]
77#[derive(Clone, Copy, Debug, Default)]
78pub struct TracingConfig {
79 trace_scalars: bool,
80}
81
82impl TracingConfig {
83 pub fn with_trace_scalars(mut self, trace_scalars: bool) -> Self {
92 self.trace_scalars = trace_scalars;
93 self
94 }
95}
96
97impl ExtensionFactory for TracingConfig {
98 fn create(&self) -> Arc<dyn Extension> {
99 Arc::new(TracingExtension {
100 trace_scalars: self.trace_scalars,
101 })
102 }
103}
104
105struct TracingExtension {
106 trace_scalars: bool,
107}
108
109#[async_trait::async_trait]
110impl Extension for TracingExtension {
111 async fn request(&self, ctx: &ExtensionContext<'_>, next: NextRequest<'_>) -> Response {
112 next.run(ctx)
113 .instrument(span!(
114 target: "async_graphql::graphql",
115 Level::INFO,
116 "request",
117 ))
118 .await
119 }
120
121 fn subscribe<'s>(
122 &self,
123 ctx: &ExtensionContext<'_>,
124 stream: BoxStream<'s, Response>,
125 next: NextSubscribe<'_>,
126 ) -> BoxStream<'s, Response> {
127 Box::pin(next.run(ctx, stream).instrument(span!(
128 target: "async_graphql::graphql",
129 Level::INFO,
130 "subscribe",
131 )))
132 }
133
134 async fn parse_query(
135 &self,
136 ctx: &ExtensionContext<'_>,
137 query: &str,
138 variables: &Variables,
139 next: NextParseQuery<'_>,
140 ) -> ServerResult<ExecutableDocument> {
141 let span = span!(
142 target: "async_graphql::graphql",
143 Level::INFO,
144 "parse",
145 source = tracinglib::field::Empty
146 );
147 async move {
148 let res = next.run(ctx, query, variables).await;
149 if let Ok(doc) = &res {
150 tracinglib::Span::current()
151 .record("source", ctx.stringify_execute_doc(doc, variables).as_str());
152 }
153 res
154 }
155 .instrument(span)
156 .await
157 }
158
159 async fn validation(
160 &self,
161 ctx: &ExtensionContext<'_>,
162 next: NextValidation<'_>,
163 ) -> Result<ValidationResult, Vec<ServerError>> {
164 let span = span!(
165 target: "async_graphql::graphql",
166 Level::INFO,
167 "validation"
168 );
169 next.run(ctx).instrument(span).await
170 }
171
172 async fn execute(
173 &self,
174 ctx: &ExtensionContext<'_>,
175 operation_name: Option<&str>,
176 next: NextExecute<'_>,
177 ) -> Response {
178 let span = span!(
179 target: "async_graphql::graphql",
180 Level::INFO,
181 "execute"
182 );
183 next.run(ctx, operation_name).instrument(span).await
184 }
185
186 async fn resolve(
187 &self,
188 ctx: &ExtensionContext<'_>,
189 info: ResolveInfo<'_>,
190 next: NextResolve<'_>,
191 ) -> ServerResult<Option<Value>> {
192 let should_trace = if info.is_for_introspection {
194 false
195 } else if !self.trace_scalars {
196 let concrete_type = MetaTypeName::concrete_typename(info.return_type);
198 !ctx.schema_env
199 .registry
200 .types
201 .get(concrete_type)
202 .map(|ty| ty.is_leaf())
203 .unwrap_or(false)
204 } else {
205 true
206 };
207
208 let span = if should_trace {
209 Some(span!(
210 target: "async_graphql::graphql",
211 Level::INFO,
212 "field",
213 path = %info.path_node,
214 parent_type = %info.parent_type,
215 return_type = %info.return_type,
216 ))
217 } else {
218 None
219 };
220
221 let fut = next.run(ctx, info).inspect_err(|err| {
222 tracinglib::info!(
223 target: "async_graphql::graphql",
224 error = %err.message,
225 "error",
226 );
227 });
228 match span {
229 Some(span) => fut.instrument(span).await,
230 None => fut.await,
231 }
232 }
233}