opentelemetry-instrumentation-botocore 0.51b0__py3-none-any.whl → 0.52b0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -76,6 +76,38 @@ for example:
76
76
  )
77
77
  ec2 = self.session.create_client("ec2", region_name="us-west-2")
78
78
  ec2.describe_instances()
79
+
80
+ Extensions
81
+ ----------
82
+
83
+ The instrumentation supports creating extensions for AWS services for enriching what is collected. We have extensions
84
+ for the following AWS services:
85
+
86
+ - Bedrock Runtime
87
+ - DynamoDB
88
+ - Lambda
89
+ - SNS
90
+ - SQS
91
+
92
+ Bedrock Runtime
93
+ ***************
94
+
95
+ This extension implements the GenAI semantic conventions for the following API calls:
96
+
97
+ - Converse
98
+ - ConverseStream
99
+ - InvokeModel
100
+ - InvokeModelWithResponseStream
101
+
102
+ For the Converse and ConverseStream APIs tracing, events and metrics are implemented.
103
+
104
+ For the InvokeModel and InvokeModelWithResponseStream APIs tracing, events and metrics implemented only for a subset of
105
+ the available models, namely:
106
+ - Amazon Titan models
107
+ - Amazon Nova models
108
+ - Anthropic Claude
109
+
110
+ There is no support for tool calls with Amazon Models for the InvokeModel and InvokeModelWithResponseStream APIs.
79
111
  """
80
112
 
81
113
  import logging
@@ -86,9 +118,15 @@ from botocore.endpoint import Endpoint
86
118
  from botocore.exceptions import ClientError
87
119
  from wrapt import wrap_function_wrapper
88
120
 
89
- from opentelemetry.instrumentation.botocore.extensions import _find_extension
121
+ from opentelemetry._events import get_event_logger
122
+ from opentelemetry.instrumentation.botocore.extensions import (
123
+ _find_extension,
124
+ _has_extension,
125
+ )
90
126
  from opentelemetry.instrumentation.botocore.extensions.types import (
91
127
  _AwsSdkCallContext,
128
+ _AwsSdkExtension,
129
+ _BotocoreInstrumentorContext,
92
130
  )
93
131
  from opentelemetry.instrumentation.botocore.package import _instruments
94
132
  from opentelemetry.instrumentation.botocore.version import __version__
@@ -98,6 +136,7 @@ from opentelemetry.instrumentation.utils import (
98
136
  suppress_http_instrumentation,
99
137
  unwrap,
100
138
  )
139
+ from opentelemetry.metrics import Instrument, Meter, get_meter
101
140
  from opentelemetry.propagators.aws.aws_xray_propagator import AwsXRayPropagator
102
141
  from opentelemetry.semconv.trace import SpanAttributes
103
142
  from opentelemetry.trace import get_tracer
@@ -123,12 +162,15 @@ class BotocoreInstrumentor(BaseInstrumentor):
123
162
 
124
163
  def _instrument(self, **kwargs):
125
164
  # pylint: disable=attribute-defined-outside-init
126
- self._tracer = get_tracer(
127
- __name__,
128
- __version__,
129
- kwargs.get("tracer_provider"),
130
- schema_url="https://opentelemetry.io/schemas/1.11.0",
131
- )
165
+
166
+ # tracers are lazy initialized per-extension in _get_tracer
167
+ self._tracers = {}
168
+ # event_loggers are lazy initialized per-extension in _get_event_logger
169
+ self._event_loggers = {}
170
+ # meters are lazy initialized per-extension in _get_meter
171
+ self._meters = {}
172
+ # metrics are lazy initialized per-extension in _get_metrics
173
+ self._metrics: Dict[str, Dict[str, Instrument]] = {}
132
174
 
133
175
  self.request_hook = kwargs.get("request_hook")
134
176
  self.response_hook = kwargs.get("response_hook")
@@ -137,6 +179,10 @@ class BotocoreInstrumentor(BaseInstrumentor):
137
179
  if propagator is not None:
138
180
  self.propagator = propagator
139
181
 
182
+ self.tracer_provider = kwargs.get("tracer_provider")
183
+ self.event_logger_provider = kwargs.get("event_logger_provider")
184
+ self.meter_provider = kwargs.get("meter_provider")
185
+
140
186
  wrap_function_wrapper(
141
187
  "botocore.client",
142
188
  "BaseClient._make_api_call",
@@ -149,6 +195,82 @@ class BotocoreInstrumentor(BaseInstrumentor):
149
195
  self._patched_endpoint_prepare_request,
150
196
  )
151
197
 
198
+ @staticmethod
199
+ def _get_instrumentation_name(extension: _AwsSdkExtension) -> str:
200
+ has_extension = _has_extension(extension._call_context)
201
+ return (
202
+ f"{__name__}.{extension._call_context.service}"
203
+ if has_extension
204
+ else __name__
205
+ )
206
+
207
+ def _get_tracer(self, extension: _AwsSdkExtension):
208
+ """This is a multiplexer in order to have a tracer per extension"""
209
+
210
+ instrumentation_name = self._get_instrumentation_name(extension)
211
+ tracer = self._tracers.get(instrumentation_name)
212
+ if tracer:
213
+ return tracer
214
+
215
+ schema_version = extension.tracer_schema_version()
216
+ self._tracers[instrumentation_name] = get_tracer(
217
+ instrumentation_name,
218
+ __version__,
219
+ self.tracer_provider,
220
+ schema_url=f"https://opentelemetry.io/schemas/{schema_version}",
221
+ )
222
+ return self._tracers[instrumentation_name]
223
+
224
+ def _get_event_logger(self, extension: _AwsSdkExtension):
225
+ """This is a multiplexer in order to have an event logger per extension"""
226
+
227
+ instrumentation_name = self._get_instrumentation_name(extension)
228
+ event_logger = self._event_loggers.get(instrumentation_name)
229
+ if event_logger:
230
+ return event_logger
231
+
232
+ schema_version = extension.event_logger_schema_version()
233
+ self._event_loggers[instrumentation_name] = get_event_logger(
234
+ instrumentation_name,
235
+ "",
236
+ schema_url=f"https://opentelemetry.io/schemas/{schema_version}",
237
+ event_logger_provider=self.event_logger_provider,
238
+ )
239
+
240
+ return self._event_loggers[instrumentation_name]
241
+
242
+ def _get_meter(self, extension: _AwsSdkExtension):
243
+ """This is a multiplexer in order to have a meter per extension"""
244
+
245
+ instrumentation_name = self._get_instrumentation_name(extension)
246
+ meter = self._meters.get(instrumentation_name)
247
+ if meter:
248
+ return meter
249
+
250
+ schema_version = extension.meter_schema_version()
251
+ self._meters[instrumentation_name] = get_meter(
252
+ instrumentation_name,
253
+ "",
254
+ schema_url=f"https://opentelemetry.io/schemas/{schema_version}",
255
+ meter_provider=self.meter_provider,
256
+ )
257
+
258
+ return self._meters[instrumentation_name]
259
+
260
+ def _get_metrics(
261
+ self, extension: _AwsSdkExtension, meter: Meter
262
+ ) -> Dict[str, Instrument]:
263
+ """This is a multiplexer for lazy initialization of metrics required by extensions"""
264
+ instrumentation_name = self._get_instrumentation_name(extension)
265
+ metrics = self._metrics.get(instrumentation_name)
266
+ if metrics is not None:
267
+ return metrics
268
+
269
+ self._metrics.setdefault(instrumentation_name, {})
270
+ metrics = self._metrics[instrumentation_name]
271
+ _safe_invoke(extension.setup_metrics, meter, metrics)
272
+ return metrics
273
+
152
274
  def _uninstrument(self, **kwargs):
153
275
  unwrap(BaseClient, "_make_api_call")
154
276
  unwrap(Endpoint, "prepare_request")
@@ -190,7 +312,15 @@ class BotocoreInstrumentor(BaseInstrumentor):
190
312
  _safe_invoke(extension.extract_attributes, attributes)
191
313
  end_span_on_exit = extension.should_end_span_on_exit()
192
314
 
193
- with self._tracer.start_as_current_span(
315
+ tracer = self._get_tracer(extension)
316
+ event_logger = self._get_event_logger(extension)
317
+ meter = self._get_meter(extension)
318
+ metrics = self._get_metrics(extension, meter)
319
+ instrumentor_ctx = _BotocoreInstrumentorContext(
320
+ event_logger=event_logger,
321
+ metrics=metrics,
322
+ )
323
+ with tracer.start_as_current_span(
194
324
  call_context.span_name,
195
325
  kind=call_context.span_kind,
196
326
  attributes=attributes,
@@ -198,7 +328,7 @@ class BotocoreInstrumentor(BaseInstrumentor):
198
328
  # at a later time after the stream has been consumed
199
329
  end_on_exit=end_span_on_exit,
200
330
  ) as span:
201
- _safe_invoke(extension.before_service_call, span)
331
+ _safe_invoke(extension.before_service_call, span, instrumentor_ctx)
202
332
  self._call_request_hook(span, call_context)
203
333
 
204
334
  try:
@@ -209,12 +339,16 @@ class BotocoreInstrumentor(BaseInstrumentor):
209
339
  except ClientError as error:
210
340
  result = getattr(error, "response", None)
211
341
  _apply_response_attributes(span, result)
212
- _safe_invoke(extension.on_error, span, error)
342
+ _safe_invoke(
343
+ extension.on_error, span, error, instrumentor_ctx
344
+ )
213
345
  raise
214
346
  _apply_response_attributes(span, result)
215
- _safe_invoke(extension.on_success, span, result)
347
+ _safe_invoke(
348
+ extension.on_success, span, result, instrumentor_ctx
349
+ )
216
350
  finally:
217
- _safe_invoke(extension.after_service_call)
351
+ _safe_invoke(extension.after_service_call, instrumentor_ctx)
218
352
  self._call_response_hook(span, call_context, result)
219
353
 
220
354
  return result
@@ -40,6 +40,10 @@ _KNOWN_EXTENSIONS = {
40
40
  }
41
41
 
42
42
 
43
+ def _has_extension(call_context: _AwsSdkCallContext) -> bool:
44
+ return call_context.service in _KNOWN_EXTENSIONS
45
+
46
+
43
47
  def _find_extension(call_context: _AwsSdkCallContext) -> _AwsSdkExtension:
44
48
  try:
45
49
  loader = _KNOWN_EXTENSIONS.get(call_context.service)