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