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}