Skip to main content

telemetry_rust/middleware/aws/instrumentation/fluent_builder/
mod.rs

1use crate::{Context, future::InstrumentedFutureContext, middleware::aws::*};
2
3mod utils;
4
5#[cfg(feature = "aws-dynamodb")]
6mod dynamodb;
7#[cfg(feature = "aws-firehose")]
8mod firehose;
9#[cfg(feature = "aws-s3")]
10mod s3;
11#[cfg(feature = "aws-sagemaker-runtime")]
12mod sagemaker_runtime;
13#[cfg(feature = "aws-sns")]
14mod sns;
15#[cfg(feature = "aws-sqs")]
16mod sqs;
17
18/// A trait for AWS service clients that can be instrumented with OpenTelemetry tracing.
19///
20/// This trait provides methods to build spans for AWS operations and instrument the
21/// fluent builders returned by AWS SDK operations. The instrumentation automatically
22/// extracts both input attributes (from the fluent builder configuration) and output
23/// attributes (from the operation response) following OpenTelemetry semantic conventions.
24///
25/// # Example
26///
27/// ```rust
28/// use aws_sdk_dynamodb::{Client as DynamoClient, types::AttributeValue};
29/// use telemetry_rust::middleware::aws::AwsBuilderInstrument;
30///
31/// async fn query_table() -> Result<i32, Box<dyn std::error::Error>> {
32///     let config = aws_config::load_from_env().await;
33///     let dynamo_client = DynamoClient::new(&config);
34///
35///     let resp = dynamo_client
36///         .query()
37///         .table_name("table_name")
38///         .index_name("my_index")
39///         .key_condition_expression("PK = :pk")
40///         .expression_attribute_values(":pk", AttributeValue::S("Test".to_string()))
41///         .consistent_read(true)
42///         .projection_expression("id,name")
43///         .instrument()
44///         .send()
45///         .await?;
46///
47///     // Automatically extracts span attributes from the builder:
48///     // - aws.dynamodb.table_name: "table_name"
49///     // - aws.dynamodb.index_name: "my_index"
50///     // - aws.dynamodb.consistent_read: true
51///     // - aws.dynamodb.projection: "id,name"
52///     //
53///     // And from the AWS output:
54///     // - aws.dynamodb.count: number of items returned
55///     // - aws.dynamodb.scanned_count: number of items scanned
56///
57///     println!("DynamoDB items: {:#?}", resp.items());
58///     Ok(resp.count())
59/// }
60/// ```
61///
62/// # Comparison with Manual Instrumentation
63///
64/// This trait provides automatic instrumentation as an alternative to manual instrumentation
65/// using [`AwsInstrument`]. The automatic approach extracts attributes based on OpenTelemetry
66/// semantic conventions without requiring explicit attribute specification:
67///
68/// ```rust
69/// # use aws_sdk_dynamodb::Client as DynamoClient;
70/// # use telemetry_rust::middleware::aws::{AwsBuilderInstrument, AwsInstrument, DynamodbSpanBuilder};
71/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
72/// # let config = aws_config::load_from_env().await;
73/// # let dynamo_client = DynamoClient::new(&config);
74/// // Automatic instrumentation (recommended)
75/// let _ = dynamo_client
76///     .get_item()
77///     .table_name("table")
78///     .instrument() // All attributes extracted automatically
79///     .send()
80///     .await?;
81///
82/// // Manual instrumentation (more control, more verbose)
83/// let _ = dynamo_client
84///     .get_item()
85///     .table_name("table")
86///     .send()
87///     .instrument(DynamodbSpanBuilder::get_item("table"))
88///     .await?;
89/// # Ok(())
90/// # }
91/// ```
92pub trait AwsBuilderInstrument<'a>
93where
94    Self: Sized,
95{
96    /// Builds an AWS span for the specific operation represented by this builder.
97    ///
98    /// Returns an [`AwsSpanBuilder`] that contains the necessary span attributes
99    /// and metadata for the AWS operation.
100    fn build_aws_span(&self) -> AwsSpanBuilder<'a>;
101
102    /// Instruments this fluent builder with OpenTelemetry tracing.
103    ///
104    /// Returns an [`InstrumentedFluentBuilder`] that will automatically create
105    /// and manage spans when the operation is executed.
106    fn instrument(self) -> InstrumentedFluentBuilder<'a, Self> {
107        let span = self.build_aws_span();
108        InstrumentedFluentBuilder::new(self, span)
109    }
110}
111
112/// A wrapper that instruments AWS fluent builders with OpenTelemetry tracing.
113///
114/// This struct wraps AWS SDK fluent builders and automatically creates spans
115/// when operations are executed, providing distributed tracing capabilities
116/// for AWS service calls.
117pub struct InstrumentedFluentBuilder<'a, T: AwsBuilderInstrument<'a>> {
118    inner: T,
119    span: AwsSpanBuilder<'a>,
120}
121
122impl<'a, T: AwsBuilderInstrument<'a>> InstrumentedFluentBuilder<'a, T> {
123    /// Creates a new instrumented fluent builder.
124    ///
125    /// # Arguments
126    /// * `inner` - The AWS SDK fluent builder to wrap
127    /// * `span` - The span builder with AWS operation metadata
128    pub fn new(inner: T, span: AwsSpanBuilder<'a>) -> Self {
129        Self { inner, span }
130    }
131
132    /// Sets the OpenTelemetry context for this instrumented builder.
133    ///
134    /// # Arguments
135    /// * `context` - The OpenTelemetry context to use for span creation
136    pub fn context(mut self, context: &'a Context) -> Self {
137        self.span = self.span.context(context);
138        self
139    }
140
141    /// Sets the OpenTelemetry context for this instrumented builder.
142    ///
143    /// # Arguments
144    /// * `context` - Optional OpenTelemetry context to use for span creation
145    pub fn set_context(mut self, context: Option<&'a Context>) -> Self {
146        self.span = self.span.set_context(context);
147        self
148    }
149}
150
151pub(super) struct FluentBuilderSpan(AwsSpan);
152
153/// A trait for extracting OpenTelemetry attributes from AWS operation output objects.
154///
155/// This trait enables AWS SDK operation outputs to contribute additional telemetry data
156/// to spans after operations complete. It's automatically used by [`AwsBuilderInstrument`]
157/// to extract meaningful span attributes like item counts, error rates,
158/// and other response metadata that enhances observability.
159///
160/// # Usage
161///
162/// Can be used together with [`AwsBuilderInstrument::build_aws_span`] when access to the
163/// underlying [`AwsSpan`] is required.
164///
165/// # Example
166///
167/// ```rust
168/// use aws_sdk_dynamodb::{Client as DynamoClient, types::ReturnConsumedCapacity};
169/// use telemetry_rust::{
170///     KeyValue,
171///     middleware::aws::{AwsBuilderInstrument, InstrumentedFluentBuilderOutput},
172///     semconv,
173/// };
174///
175/// async fn query_table() -> Result<usize, Box<dyn std::error::Error>> {
176///     let config = aws_config::load_from_env().await;
177///     let dynamo_client = DynamoClient::new(&config);
178///
179///     let statement = "SELECT * FROM test";
180///     let query = dynamo_client
181///         .execute_statement()
182///         .statement(statement)
183///         .return_consumed_capacity(ReturnConsumedCapacity::Total);
184///
185///     let mut span = query
186///         // Extract span from fluent builder
187///         .build_aws_span()
188///         // Set additional attributes
189///         .attribute(KeyValue::new(semconv::DB_QUERY_TEXT, statement))
190///         // Start the span
191///         .start();
192///
193///     let result = query.send().await;
194///     if let Ok(output) = result.as_ref() {
195///         // Extract span attributes from ExecuteStatement output
196///         span.set_attributes(output.extract_attributes());
197///         // Set additional attributes
198///         span.set_attribute(KeyValue::new(
199///             semconv::AWS_DYNAMODB_CONSUMED_CAPACITY,
200///             format!("{:?}", output.consumed_capacity().unwrap()),
201///         ));
202///     }
203///     // End the span
204///     span.end(&result);
205///
206///     let items = result?.items.unwrap_or_default();
207///
208///     println!("DynamoDB items: {items:#?}");
209///     Ok(items.len())
210/// }
211/// ```
212///
213/// # Implementation Notes
214///
215/// Implementations should extract relevant attributes following OpenTelemetry semantic
216/// conventions for the specific AWS service. The extracted attributes will be added
217/// to the span after the operation completes successfully.
218pub trait InstrumentedFluentBuilderOutput {
219    /// Extracts OpenTelemetry attributes from the AWS operation output.
220    ///
221    /// Return an iterator of [`KeyValue`] pairs representing span attributes.
222    ///
223    /// The default implementation returns no attributes, which is appropriate for
224    /// operations that don't have meaningful response metrics to extract.
225    fn extract_attributes(&self) -> impl IntoIterator<Item = KeyValue> {
226        None
227    }
228}
229
230impl<T, E> InstrumentedFutureContext<Result<T, E>> for FluentBuilderSpan
231where
232    T: RequestId + InstrumentedFluentBuilderOutput,
233    E: RequestId + Error,
234{
235    fn on_result(mut self, result: &Result<T, E>) {
236        if let Ok(output) = result {
237            self.0.set_attributes(output.extract_attributes());
238        }
239        self.0.on_result(result)
240    }
241}
242
243/// Generates [`super::InstrumentedFluentBuilder`] implementation for AWS SDK operations.
244macro_rules! instrument_aws_operation {
245    ($sdk:ident::operation::$op:ident, $builder:ident, $output:ident, $error:ident) => {
246        use $sdk::operation::$op::builders::$builder;
247        use $sdk::operation::$op::$output;
248        impl
249            super::InstrumentedFluentBuilder<'_, $sdk::operation::$op::builders::$builder>
250        {
251            /// Executes the AWS operation with instrumentation.
252            ///
253            /// This method creates a span for the operation and executes it within
254            /// that span context, providing automatic distributed tracing.
255            pub async fn send(
256                self,
257            ) -> Result<
258                $sdk::operation::$op::$output,
259                $sdk::error::SdkError<$sdk::operation::$op::$error>,
260            > {
261                let span = self.span.start();
262                $crate::future::InstrumentedFuture::new(
263                    self.inner.send(),
264                    super::FluentBuilderSpan(span),
265                )
266                .await
267            }
268        }
269    };
270    ($sdk:ident::operation::$op:ident) => {
271        paste::paste! {
272            instrument_aws_operation!(
273                $sdk::operation::$op,
274                [<$op:camel FluentBuilder>],
275                [<$op:camel Output>],
276                [<$op:camel Error>]
277            );
278        }
279    };
280}
281
282pub(super) use instrument_aws_operation;