async_graphql/extensions/
apollo_tracing.rs1use std::sync::Arc;
2
3use chrono::{DateTime, Utc};
4use futures_util::lock::Mutex;
5use serde::{Serialize, Serializer, ser::SerializeMap};
6
7use crate::{
8 Response, ServerResult, Value,
9 extensions::{
10 Extension, ExtensionContext, ExtensionFactory, NextExecute, NextResolve, ResolveInfo,
11 },
12 value,
13};
14
15struct ResolveState {
16 path: Vec<String>,
17 field_name: String,
18 parent_type: String,
19 return_type: String,
20 start_time: DateTime<Utc>,
21 end_time: DateTime<Utc>,
22 start_offset: i64,
23}
24
25impl Serialize for ResolveState {
26 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
27 let mut map = serializer.serialize_map(None)?;
28 map.serialize_entry("path", &self.path)?;
29 map.serialize_entry("fieldName", &self.field_name)?;
30 map.serialize_entry("parentType", &self.parent_type)?;
31 map.serialize_entry("returnType", &self.return_type)?;
32 map.serialize_entry("startOffset", &self.start_offset)?;
33 map.serialize_entry(
34 "duration",
35 &(self.end_time - self.start_time).num_nanoseconds(),
36 )?;
37 map.end()
38 }
39}
40
41#[cfg_attr(docsrs, doc(cfg(feature = "apollo_tracing")))]
50pub struct ApolloTracing;
51
52impl ExtensionFactory for ApolloTracing {
53 fn create(&self) -> Arc<dyn Extension> {
54 Arc::new(ApolloTracingExtension {
55 inner: Mutex::new(Inner {
56 start_time: Utc::now(),
57 end_time: Utc::now(),
58 resolves: Default::default(),
59 }),
60 })
61 }
62}
63
64struct Inner {
65 start_time: DateTime<Utc>,
66 end_time: DateTime<Utc>,
67 resolves: Vec<ResolveState>,
68}
69
70struct ApolloTracingExtension {
71 inner: Mutex<Inner>,
72}
73
74#[async_trait::async_trait]
75impl Extension for ApolloTracingExtension {
76 async fn execute(
77 &self,
78 ctx: &ExtensionContext<'_>,
79 operation_name: Option<&str>,
80 next: NextExecute<'_>,
81 ) -> Response {
82 self.inner.lock().await.start_time = Utc::now();
83 let resp = next.run(ctx, operation_name).await;
84
85 let mut inner = self.inner.lock().await;
86 inner.end_time = Utc::now();
87 inner
88 .resolves
89 .sort_by(|a, b| a.start_offset.cmp(&b.start_offset));
90 resp.extension(
91 "tracing",
92 value!({
93 "version": 1,
94 "startTime": inner.start_time.to_rfc3339(),
95 "endTime": inner.end_time.to_rfc3339(),
96 "duration": (inner.end_time - inner.start_time).num_nanoseconds(),
97 "execution": {
98 "resolvers": inner.resolves
99 }
100 }),
101 )
102 }
103
104 async fn resolve(
105 &self,
106 ctx: &ExtensionContext<'_>,
107 info: ResolveInfo<'_>,
108 next: NextResolve<'_>,
109 ) -> ServerResult<Option<Value>> {
110 let path = info.path_node.to_string_vec();
111 let field_name = info.path_node.field_name().to_string();
112 let parent_type = info.parent_type.to_string();
113 let return_type = info.return_type.to_string();
114 let start_time = Utc::now();
115 let start_offset = (start_time - self.inner.lock().await.start_time)
116 .num_nanoseconds()
117 .unwrap();
118
119 let res = next.run(ctx, info).await;
120 let end_time = Utc::now();
121
122 self.inner.lock().await.resolves.push(ResolveState {
123 path,
124 field_name,
125 parent_type,
126 return_type,
127 start_time,
128 end_time,
129 start_offset,
130 });
131 res
132 }
133}