mockforge_graphql/
graphql_tracing.rs1use mockforge_tracing::{create_request_span, record_error, record_success, Protocol};
4use opentelemetry::{global::BoxedSpan, trace::Span, KeyValue};
5use std::collections::HashMap;
6use tracing::debug;
7
8pub fn create_graphql_span(
10 operation_type: &str,
11 operation_name: Option<&str>,
12 query: &str,
13) -> BoxedSpan {
14 let span_name = if let Some(name) = operation_name {
15 format!("GraphQL {} {}", operation_type, name)
16 } else {
17 format!("GraphQL {}", operation_type)
18 };
19
20 let mut attributes = vec![
21 KeyValue::new("graphql.operation.type", operation_type.to_string()),
22 KeyValue::new("graphql.document", query.to_string()),
23 ];
24
25 if let Some(name) = operation_name {
26 attributes.push(KeyValue::new("graphql.operation.name", name.to_string()));
27 }
28
29 create_request_span(Protocol::GraphQL, &span_name, attributes)
30}
31
32pub fn record_graphql_success(
34 span: &mut BoxedSpan,
35 duration_ms: u64,
36 field_count: usize,
37 resolver_calls: usize,
38) {
39 let attributes = vec![
40 KeyValue::new("graphql.duration_ms", duration_ms as i64),
41 KeyValue::new("graphql.fields_resolved", field_count as i64),
42 KeyValue::new("graphql.resolver_calls", resolver_calls as i64),
43 ];
44
45 record_success(span, attributes);
46
47 debug!(
48 duration_ms = duration_ms,
49 field_count = field_count,
50 resolver_calls = resolver_calls,
51 "GraphQL query completed successfully"
52 );
53}
54
55pub fn record_graphql_error(
57 span: &mut BoxedSpan,
58 error_message: &str,
59 error_path: Option<Vec<String>>,
60 duration_ms: u64,
61) {
62 span.set_attribute(KeyValue::new("graphql.duration_ms", duration_ms as i64));
63 span.set_attribute(KeyValue::new("graphql.error.message", error_message.to_string()));
64
65 if let Some(path) = error_path {
66 span.set_attribute(KeyValue::new("graphql.error.path", format!("{:?}", path)));
67 }
68
69 record_error(span, error_message);
70
71 debug!(error_message = error_message, duration_ms = duration_ms, "GraphQL query failed");
72}
73
74pub fn create_resolver_span(parent_type: &str, field_name: &str) -> BoxedSpan {
76 create_request_span(
77 Protocol::GraphQL,
78 &format!("Resolve {}.{}", parent_type, field_name),
79 vec![
80 KeyValue::new("graphql.resolver.parent_type", parent_type.to_string()),
81 KeyValue::new("graphql.resolver.field_name", field_name.to_string()),
82 ],
83 )
84}
85
86pub fn record_resolver_success(span: &mut BoxedSpan, duration_us: u64) {
88 let attributes = vec![KeyValue::new(
89 "graphql.resolver.duration_us",
90 duration_us as i64,
91 )];
92
93 record_success(span, attributes);
94}
95
96pub fn record_resolver_error(span: &mut BoxedSpan, error_message: &str) {
98 record_error(span, error_message);
99}
100
101pub fn extract_graphql_trace_context(headers: &HashMap<String, String>) -> opentelemetry::Context {
103 mockforge_tracing::extract_trace_context(headers)
104}
105
106pub fn inject_graphql_trace_context(
108 ctx: &opentelemetry::Context,
109 headers: &mut HashMap<String, String>,
110) {
111 mockforge_tracing::inject_trace_context(ctx, headers);
112}
113
114#[cfg(test)]
115mod tests {
116 use super::*;
117 use opentelemetry::global;
118 use opentelemetry_sdk::propagation::TraceContextPropagator;
119
120 #[test]
121 fn test_create_graphql_span() {
122 global::set_text_map_propagator(TraceContextPropagator::new());
123
124 let query = "query GetUser($id: ID!) { user(id: $id) { name } }";
125 let mut span = create_graphql_span("query", Some("GetUser"), query);
126 record_graphql_success(&mut span, 150, 3, 5);
127 }
128
129 #[test]
130 fn test_create_resolver_span() {
131 global::set_text_map_propagator(TraceContextPropagator::new());
132
133 let mut span = create_resolver_span("User", "name");
134 record_resolver_success(&mut span, 250);
135 }
136
137 #[test]
138 fn test_graphql_trace_context_propagation() {
139 global::set_text_map_propagator(TraceContextPropagator::new());
140
141 let mut headers = HashMap::new();
142 headers.insert(
143 "traceparent".to_string(),
144 "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01".to_string(),
145 );
146
147 let ctx = extract_graphql_trace_context(&headers);
148
149 let mut output_headers = HashMap::new();
150 inject_graphql_trace_context(&ctx, &mut output_headers);
151
152 assert!(output_headers.contains_key("traceparent"));
153 }
154}