telemetry_rust/middleware/aws/mod.rs
1//! Instrumentation utilities for AWS SDK operations.
2//!
3//! This module provides instrumentation for AWS services,
4//! including span creation and context propagation for AWS SDK operations.
5
6use aws_types::request_id::RequestId;
7use opentelemetry::{
8 global::{self, BoxedSpan, BoxedTracer},
9 trace::{Span as _, SpanBuilder, SpanKind, Status, Tracer},
10};
11use std::error::Error;
12use tracing::Span;
13
14use crate::{Context, KeyValue, OpenTelemetrySpanExt, StringValue, semconv};
15
16#[cfg(feature = "aws-instrumentation")]
17mod instrumentation;
18mod operations;
19
20#[cfg(feature = "aws-instrumentation")]
21pub use instrumentation::AwsInstrument;
22pub use operations::*;
23
24/// A wrapper around an OpenTelemetry span specifically designed for AWS operations.
25///
26/// This struct provides convenient methods for handling AWS-specific span attributes
27/// and status updates, particularly for recording request IDs and error handling.
28pub struct AwsSpan {
29 span: BoxedSpan,
30}
31
32impl AwsSpan {
33 /// Ends the span with AWS response information.
34 ///
35 /// This method finalizes the span by recording the outcome of an AWS operation.
36 /// It automatically extracts request IDs and handles error reporting.
37 ///
38 /// # Arguments
39 ///
40 /// * `aws_response` - The result of the AWS operation, which must implement
41 /// `RequestId` for both success and error cases
42 ///
43 /// # Behavior
44 ///
45 /// - On success: Sets span status to OK and records the request ID
46 /// - On error: Records the error, sets error status, and records the request ID if available
47 pub fn end<T, E>(self, aws_response: &Result<T, E>)
48 where
49 T: RequestId,
50 E: RequestId + Error,
51 {
52 let mut span = self.span;
53 let (status, request_id) = match aws_response {
54 Ok(resp) => (Status::Ok, resp.request_id()),
55 Err(error) => {
56 span.record_error(&error);
57 (Status::error(error.to_string()), error.request_id())
58 }
59 };
60 if let Some(value) = request_id {
61 span.set_attribute(KeyValue::new(semconv::AWS_REQUEST_ID, value.to_owned()));
62 }
63 span.set_status(status);
64 }
65}
66
67impl From<BoxedSpan> for AwsSpan {
68 #[inline]
69 fn from(span: BoxedSpan) -> Self {
70 Self { span }
71 }
72}
73
74/// Builder for creating AWS-specific OpenTelemetry spans.
75///
76/// This builder provides a fluent interface for constructing spans with AWS-specific
77/// attributes and proper span kinds for different types of AWS operations.
78pub struct AwsSpanBuilder<'a> {
79 inner: SpanBuilder,
80 tracer: BoxedTracer,
81 context: Option<&'a Context>,
82}
83
84impl<'a> AwsSpanBuilder<'a> {
85 fn new(
86 span_kind: SpanKind,
87 service: impl Into<StringValue>,
88 method: impl Into<StringValue>,
89 custom_attributes: impl IntoIterator<Item = KeyValue>,
90 ) -> Self {
91 let service: StringValue = service.into();
92 let method: StringValue = method.into();
93 let tracer = global::tracer("aws_sdk");
94 let span_name = format!("{service}.{method}");
95 let mut attributes = vec![
96 KeyValue::new(semconv::RPC_METHOD, method),
97 KeyValue::new(semconv::RPC_SYSTEM, "aws-api"),
98 KeyValue::new(semconv::RPC_SERVICE, service),
99 ];
100 attributes.extend(custom_attributes);
101 let inner = tracer
102 .span_builder(span_name)
103 .with_attributes(attributes)
104 .with_kind(span_kind);
105
106 Self {
107 inner,
108 tracer,
109 context: None,
110 }
111 }
112
113 /// Creates a client span builder for AWS operations.
114 ///
115 /// Client spans represent outbound calls to AWS services from your application.
116 ///
117 /// # Arguments
118 ///
119 /// * `service` - The AWS service name (e.g., "S3", "DynamoDB")
120 /// * `method` - The operation name (e.g., "GetObject", "PutItem")
121 /// * `attributes` - Additional custom attributes for the span
122 pub fn client(
123 service: impl Into<StringValue>,
124 method: impl Into<StringValue>,
125 attributes: impl IntoIterator<Item = KeyValue>,
126 ) -> Self {
127 Self::new(SpanKind::Client, service, method, attributes)
128 }
129
130 /// Creates a producer span builder for AWS operations.
131 ///
132 /// Producer spans represent operations that send messages or data to AWS services.
133 ///
134 /// # Arguments
135 ///
136 /// * `service` - The AWS service name (e.g., "SQS", "SNS")
137 /// * `method` - The operation name (e.g., "SendMessage", "Publish")
138 /// * `attributes` - Additional custom attributes for the span
139 pub fn producer(
140 service: impl Into<StringValue>,
141 method: impl Into<StringValue>,
142 attributes: impl IntoIterator<Item = KeyValue>,
143 ) -> Self {
144 Self::new(SpanKind::Producer, service, method, attributes)
145 }
146
147 /// Creates a consumer span builder for AWS operations.
148 ///
149 /// Consumer spans represent operations that receive messages or data from AWS services.
150 ///
151 /// # Arguments
152 ///
153 /// * `service` - The AWS service name (e.g., "SQS", "Kinesis")
154 /// * `method` - The operation name (e.g., "ReceiveMessage", "GetRecords")
155 /// * `attributes` - Additional custom attributes for the span
156 pub fn consumer(
157 service: impl Into<StringValue>,
158 method: impl Into<StringValue>,
159 attributes: impl IntoIterator<Item = KeyValue>,
160 ) -> Self {
161 Self::new(SpanKind::Consumer, service, method, attributes)
162 }
163
164 /// Adds multiple attributes to the span being built.
165 ///
166 /// # Arguments
167 ///
168 /// * `iter` - An iterator of key-value attributes to add to the span
169 pub fn attributes(mut self, iter: impl IntoIterator<Item = KeyValue>) -> Self {
170 if let Some(attributes) = &mut self.inner.attributes {
171 attributes.extend(iter);
172 }
173 self
174 }
175
176 /// Adds a single attribute to the span being built.
177 ///
178 /// This is a convenience method for adding one attribute at a time.
179 ///
180 /// # Arguments
181 ///
182 /// * `attribute` - The key-value attribute to add to the span
183 #[inline]
184 pub fn attribute(self, attribute: KeyValue) -> Self {
185 self.attributes(std::iter::once(attribute))
186 }
187
188 /// Sets the parent context for the span.
189 ///
190 /// # Arguments
191 ///
192 /// * `context` - The OpenTelemetry context to use as the parent
193 #[inline]
194 pub fn context(mut self, context: &'a Context) -> Self {
195 self.context = Some(context);
196 self
197 }
198
199 /// Optionally sets the parent context for the span.
200 ///
201 /// # Arguments
202 ///
203 /// * `context` - An optional OpenTelemetry context to use as the parent
204 #[inline]
205 pub fn set_context(mut self, context: Option<&'a Context>) -> Self {
206 self.context = context;
207 self
208 }
209
210 #[inline(always)]
211 fn start_with_context(self, parent_cx: &Context) -> AwsSpan {
212 self.inner
213 .start_with_context(&self.tracer, parent_cx)
214 .into()
215 }
216
217 /// Starts the span and returns an AwsSpan.
218 ///
219 /// This method creates and starts the span using either the explicitly set context
220 /// or the current tracing span's context as the parent.
221 #[inline]
222 pub fn start(self) -> AwsSpan {
223 match self.context {
224 Some(context) => self.start_with_context(context),
225 None => self.start_with_context(&Span::current().context()),
226 }
227 }
228}