xray_lite_aws_sdk/lib.rs
1#![warn(missing_docs)]
2
3//! Extension of `xray-lite` for [AWS SDK for Rust](https://aws.amazon.com/sdk-for-rust/).
4//!
5//! With this crate, you can easily add the X-Ray tracing capability to your
6//! AWS service requests through
7//! [AWS SDK for Rust](https://aws.amazon.com/sdk-for-rust/).
8//! It utilizes the [interceptor](https://docs.rs/aws-smithy-runtime-api/latest/aws_smithy_runtime_api/client/interceptors/trait.Intercept.html)
9//! which can be attached to `CustomizableOperation` available via the
10//! `customize` method of any request builder; e.g.,
11//! [`aws_sdk_s3::operation::get_object::builders::GetObjectFluentBuilder::customize`](https://docs.rs/aws-sdk-s3/latest/aws_sdk_s3/operation/get_object/builders/struct.GetObjectFluentBuilder.html#method.customize)
12//!
13//! The following example shows how to report a subsegment for each attempt of
14//! the S3 GetObject operation:
15//! ```no_run
16//! use aws_config::BehaviorVersion;
17//! use xray_lite::{DaemonClient, SubsegmentContext};
18//! use xray_lite_aws_sdk::ContextExt as _;
19//!
20//! async fn get_object_from_s3() {
21//! let xray_client = DaemonClient::from_lambda_env().unwrap();
22//! let xray_context = SubsegmentContext::from_lambda_env(xray_client).unwrap();
23//!
24//! let config = aws_config::load_defaults(BehaviorVersion::latest()).await;
25//! let s3_client = aws_sdk_s3::Client::new(&config);
26//! s3_client
27//! .get_object()
28//! .bucket("the-bucket-name")
29//! .key("the-object-key")
30//! .customize()
31//! .interceptor(xray_context.intercept_operation("S3", "GetObject"))
32//! .send()
33//! .await
34//! .unwrap();
35//! }
36//! ```
37
38use std::sync::{Arc, Mutex};
39
40use aws_smithy_runtime_api::box_error::BoxError;
41use aws_smithy_runtime_api::client::interceptors::context::{
42 BeforeTransmitInterceptorContextMut, BeforeTransmitInterceptorContextRef,
43 FinalizerInterceptorContextRef,
44};
45use aws_smithy_runtime_api::client::interceptors::Intercept;
46use aws_smithy_runtime_api::client::runtime_components::RuntimeComponents;
47use aws_smithy_types::config_bag::ConfigBag;
48use aws_types::request_id::RequestId;
49
50use xray_lite::{AwsNamespace, Context, Header, SubsegmentSession};
51
52/// Extension of [`Context`].
53///
54/// This trait is automatically implemented for any [`Context`] that satisfies
55/// the bounds.
56pub trait ContextExt: Context + Clone + std::fmt::Debug + Send + Sync + 'static {
57 /// Creates an [`Intercept`](https://docs.rs/aws-smithy-runtime-api/1.1.7/aws_smithy_runtime_api/client/interceptors/trait.Intercept.html)
58 /// for the AWS service request.
59 ///
60 /// A returned `Intercept` implements the following hooks:
61 /// 1. [`read_before_attempt`](https://docs.rs/aws-smithy-runtime-api/1.1.7/aws_smithy_runtime_api/client/interceptors/trait.Intercept.html#method.read_before_attempt):
62 /// Starts a subsegment of the AWS service request
63 /// 2. [`modify_before_transmit`](https://docs.rs/aws-smithy-runtime-api/1.1.7/aws_smithy_runtime_api/client/interceptors/trait.Intercept.html#method.modify_before_transmit):
64 /// Injects the `X-Amzn-Trace-Id` header into the request
65 /// 3. [`read_after_attempt`](https://docs.rs/aws-smithy-runtime-api/1.1.7/aws_smithy_runtime_api/client/interceptors/trait.Intercept.html#method.read_after_attempt):
66 /// Updates the subsegment with the request ID and the response status,
67 /// and reports the subsegment to the X-Ray daemon
68 fn intercept_operation(
69 &self,
70 service: impl Into<String>,
71 operation: impl Into<String>,
72 ) -> impl Intercept + 'static {
73 XrayIntercept::new_with_operation(self.clone(), service, operation)
74 }
75}
76
77impl<T> ContextExt for T where T: Context + Clone + std::fmt::Debug + Send + Sync + 'static {}
78
79#[derive(Debug)]
80struct XrayIntercept<T>
81where
82 T: Context + Clone + std::fmt::Debug + Send + Sync + 'static,
83{
84 context: T,
85 service: String,
86 operation: String,
87 // session is unnecessarily wrapped in Mutex because `Intercept` is
88 // immutable during its method calls.
89 #[allow(clippy::type_complexity)]
90 session: Arc<Mutex<Option<SubsegmentSession<T::Client, AwsNamespace>>>>,
91}
92
93impl<T> XrayIntercept<T>
94where
95 T: Context + Clone + std::fmt::Debug + Send + Sync + 'static,
96{
97 fn new_with_operation(
98 context: T,
99 service: impl Into<String>,
100 operation: impl Into<String>,
101 ) -> Self {
102 Self {
103 context,
104 service: service.into(),
105 operation: operation.into(),
106 session: Arc::new(Mutex::new(None)),
107 }
108 }
109}
110
111impl<T> Intercept for XrayIntercept<T>
112where
113 T: Context + Clone + std::fmt::Debug + Send + Sync + 'static,
114{
115 fn name(&self) -> &'static str {
116 "XrayIntercept"
117 }
118
119 fn read_before_attempt(
120 &self,
121 _context: &BeforeTransmitInterceptorContextRef<'_>,
122 _runtime_components: &RuntimeComponents,
123 _cfg: &mut ConfigBag,
124 ) -> Result<(), BoxError> {
125 let session = self.context.enter_subsegment(AwsNamespace::new(
126 self.service.clone(),
127 self.operation.clone(),
128 ));
129 *self.session.lock().unwrap() = Some(session);
130 Ok(())
131 }
132
133 fn modify_before_transmit(
134 &self,
135 context: &mut BeforeTransmitInterceptorContextMut<'_>,
136 _runtime_components: &RuntimeComponents,
137 _cfg: &mut ConfigBag,
138 ) -> Result<(), BoxError> {
139 let trace_id = self
140 .session
141 .lock()
142 .unwrap()
143 .as_ref()
144 .and_then(|s| s.x_amzn_trace_id());
145 if let Some(trace_id) = trace_id {
146 context
147 .request_mut()
148 .headers_mut()
149 .insert(Header::NAME, trace_id);
150 }
151 Ok(())
152 }
153
154 fn read_after_attempt(
155 &self,
156 context: &FinalizerInterceptorContextRef<'_>,
157 _runtime_components: &RuntimeComponents,
158 _cfg: &mut ConfigBag,
159 ) -> Result<(), BoxError> {
160 let mut session = self.session.lock().unwrap();
161 if let Some(mut session) = session.take() {
162 if let Some(namespace) = session.namespace_mut() {
163 if let Some(response) = context.response() {
164 namespace.response_status(response.status().as_u16());
165 if let Some(request_id) = response.request_id() {
166 namespace.request_id(request_id);
167 }
168 }
169 }
170 }
171 Ok(())
172 }
173}