datadog_lambda 8.127.0__tar.gz → 8.129.0__tar.gz
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.
- {datadog_lambda-8.127.0 → datadog_lambda-8.129.0}/PKG-INFO +3 -2
- {datadog_lambda-8.127.0 → datadog_lambda-8.129.0}/README.md +1 -0
- {datadog_lambda-8.127.0 → datadog_lambda-8.129.0}/datadog_lambda/cold_start.py +5 -1
- {datadog_lambda-8.127.0 → datadog_lambda-8.129.0}/datadog_lambda/config.py +11 -0
- {datadog_lambda-8.127.0 → datadog_lambda-8.129.0}/datadog_lambda/tracing.py +247 -19
- datadog_lambda-8.129.0/datadog_lambda/version.py +1 -0
- {datadog_lambda-8.127.0 → datadog_lambda-8.129.0}/pyproject.toml +2 -2
- datadog_lambda-8.127.0/datadog_lambda/version.py +0 -1
- {datadog_lambda-8.127.0 → datadog_lambda-8.129.0}/LICENSE +0 -0
- {datadog_lambda-8.127.0 → datadog_lambda-8.129.0}/LICENSE-3rdparty.csv +0 -0
- {datadog_lambda-8.127.0 → datadog_lambda-8.129.0}/NOTICE +0 -0
- {datadog_lambda-8.127.0 → datadog_lambda-8.129.0}/datadog_lambda/__init__.py +0 -0
- {datadog_lambda-8.127.0 → datadog_lambda-8.129.0}/datadog_lambda/api.py +0 -0
- {datadog_lambda-8.127.0 → datadog_lambda-8.129.0}/datadog_lambda/asm.py +0 -0
- {datadog_lambda-8.127.0 → datadog_lambda-8.129.0}/datadog_lambda/constants.py +0 -0
- {datadog_lambda-8.127.0 → datadog_lambda-8.129.0}/datadog_lambda/dogstatsd.py +0 -0
- {datadog_lambda-8.127.0 → datadog_lambda-8.129.0}/datadog_lambda/durable.py +0 -0
- {datadog_lambda-8.127.0 → datadog_lambda-8.129.0}/datadog_lambda/extension.py +0 -0
- {datadog_lambda-8.127.0 → datadog_lambda-8.129.0}/datadog_lambda/handler.py +0 -0
- {datadog_lambda-8.127.0 → datadog_lambda-8.129.0}/datadog_lambda/logger.py +0 -0
- {datadog_lambda-8.127.0 → datadog_lambda-8.129.0}/datadog_lambda/metric.py +0 -0
- {datadog_lambda-8.127.0 → datadog_lambda-8.129.0}/datadog_lambda/module_name.py +0 -0
- {datadog_lambda-8.127.0 → datadog_lambda-8.129.0}/datadog_lambda/patch.py +0 -0
- {datadog_lambda-8.127.0 → datadog_lambda-8.129.0}/datadog_lambda/span_pointers.py +0 -0
- {datadog_lambda-8.127.0 → datadog_lambda-8.129.0}/datadog_lambda/stats_writer.py +0 -0
- {datadog_lambda-8.127.0 → datadog_lambda-8.129.0}/datadog_lambda/statsd_writer.py +0 -0
- {datadog_lambda-8.127.0 → datadog_lambda-8.129.0}/datadog_lambda/tag_object.py +0 -0
- {datadog_lambda-8.127.0 → datadog_lambda-8.129.0}/datadog_lambda/tags.py +0 -0
- {datadog_lambda-8.127.0 → datadog_lambda-8.129.0}/datadog_lambda/thread_stats_writer.py +0 -0
- {datadog_lambda-8.127.0 → datadog_lambda-8.129.0}/datadog_lambda/trigger.py +0 -0
- {datadog_lambda-8.127.0 → datadog_lambda-8.129.0}/datadog_lambda/wrapper.py +0 -0
- {datadog_lambda-8.127.0 → datadog_lambda-8.129.0}/datadog_lambda/xray.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: datadog_lambda
|
|
3
|
-
Version: 8.
|
|
3
|
+
Version: 8.129.0
|
|
4
4
|
Summary: The Datadog AWS Lambda Library
|
|
5
5
|
Home-page: https://github.com/DataDog/datadog-lambda-python
|
|
6
6
|
License: Apache-2.0
|
|
@@ -32,7 +32,7 @@ Requires-Dist: ujson (>=5.10.0,<5.12.0) ; python_version >= "3.8" and python_ver
|
|
|
32
32
|
Requires-Dist: ujson (>=5.12.0,<6.0.0) ; python_version >= "3.10"
|
|
33
33
|
Requires-Dist: urllib3 (>=1.25.4,<1.27) ; python_version >= "3.8" and python_version < "3.10"
|
|
34
34
|
Requires-Dist: urllib3 (>=2.6.3,<3.0.0) ; python_version >= "3.10"
|
|
35
|
-
Requires-Dist: wrapt (>=1.11.2
|
|
35
|
+
Requires-Dist: wrapt (>=1.11.2,!=2.2.0,<3)
|
|
36
36
|
Project-URL: Repository, https://github.com/DataDog/datadog-lambda-python
|
|
37
37
|
Description-Content-Type: text/markdown
|
|
38
38
|
|
|
@@ -68,6 +68,7 @@ Besides the environment variables supported by dd-trace-py, the datadog-lambda-p
|
|
|
68
68
|
| DD_CAPTURE_LAMBDA_PAYLOAD | [Captures incoming and outgoing AWS Lambda payloads][1] in the Datadog APM spans for Lambda invocations. | `false` |
|
|
69
69
|
| DD_CAPTURE_LAMBDA_PAYLOAD_MAX_DEPTH | Determines the level of detail captured from AWS Lambda payloads, which are then assigned as tags for the `aws.lambda` span. It specifies the nesting depth of the JSON payload structure to process. Once the specified maximum depth is reached, the tag's value is set to the stringified value of any nested elements beyond this level. <br> For example, given the input payload: <pre>{<br> "lv1" : {<br> "lv2": {<br> "lv3": "val"<br> }<br> }<br>}</pre> If the depth is set to `2`, the resulting tag's key is set to `function.request.lv1.lv2` and the value is `{\"lv3\": \"val\"}`. <br> If the depth is set to `0`, the resulting tag's key is set to `function.request` and value is `{\"lv1\":{\"lv2\":{\"lv3\": \"val\"}}}` | `10` |
|
|
70
70
|
| DD_EXCEPTION_REPLAY_ENABLED | When set to `true`, the Lambda will run with Error Tracking Exception Replay enabled, capturing local variables. | `false` |
|
|
71
|
+
| DD_DSM_EXCHANGE_NAME | If you are using Datadog Data Streams Monitoring and propagating context through Amazon Event Bridge, this variable specifies the name of the EventBridge EventBus. The name of the bus is *not* passed through in the event payload and therefore cannot be automatically inferred at consume time. | `false` |
|
|
71
72
|
|
|
72
73
|
|
|
73
74
|
## Opening Issues
|
|
@@ -30,6 +30,7 @@ Besides the environment variables supported by dd-trace-py, the datadog-lambda-p
|
|
|
30
30
|
| DD_CAPTURE_LAMBDA_PAYLOAD | [Captures incoming and outgoing AWS Lambda payloads][1] in the Datadog APM spans for Lambda invocations. | `false` |
|
|
31
31
|
| DD_CAPTURE_LAMBDA_PAYLOAD_MAX_DEPTH | Determines the level of detail captured from AWS Lambda payloads, which are then assigned as tags for the `aws.lambda` span. It specifies the nesting depth of the JSON payload structure to process. Once the specified maximum depth is reached, the tag's value is set to the stringified value of any nested elements beyond this level. <br> For example, given the input payload: <pre>{<br> "lv1" : {<br> "lv2": {<br> "lv3": "val"<br> }<br> }<br>}</pre> If the depth is set to `2`, the resulting tag's key is set to `function.request.lv1.lv2` and the value is `{\"lv3\": \"val\"}`. <br> If the depth is set to `0`, the resulting tag's key is set to `function.request` and value is `{\"lv1\":{\"lv2\":{\"lv3\": \"val\"}}}` | `10` |
|
|
32
32
|
| DD_EXCEPTION_REPLAY_ENABLED | When set to `true`, the Lambda will run with Error Tracking Exception Replay enabled, capturing local variables. | `false` |
|
|
33
|
+
| DD_DSM_EXCHANGE_NAME | If you are using Datadog Data Streams Monitoring and propagating context through Amazon Event Bridge, this variable specifies the name of the EventBridge EventBus. The name of the bus is *not* passed through in the event payload and therefore cannot be automatically inferred at consume time. | `false` |
|
|
33
34
|
|
|
34
35
|
|
|
35
36
|
## Opening Issues
|
|
@@ -202,7 +202,11 @@ class ColdStartTracer(object):
|
|
|
202
202
|
cold_start_span = self.create_cold_start_span(cold_start_span_start_time_ns)
|
|
203
203
|
while root_nodes:
|
|
204
204
|
root_node = root_nodes.pop()
|
|
205
|
-
|
|
205
|
+
# Modules lazily imported during the invocation hang off the function
|
|
206
|
+
# span, not the span active at import time: nesting them under a
|
|
207
|
+
# client span (e.g. a DynamoDB call) makes the backend peer service
|
|
208
|
+
# pipeline map that client's host to the import span's service.
|
|
209
|
+
parent = self.trace_ctx if root_node.context else cold_start_span
|
|
206
210
|
self.trace_tree(root_node, parent)
|
|
207
211
|
self.finish_span(cold_start_span, cold_start_span_end_time_ns)
|
|
208
212
|
|
|
@@ -75,6 +75,13 @@ class Config:
|
|
|
75
75
|
add_span_pointers = _get_env("DD_BOTOCORE_ADD_SPAN_POINTERS", "true", as_bool)
|
|
76
76
|
trace_extractor = _get_env("DD_TRACE_EXTRACTOR")
|
|
77
77
|
|
|
78
|
+
aws_service_representation_enabled = _get_env(
|
|
79
|
+
"DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED", "true", as_bool
|
|
80
|
+
)
|
|
81
|
+
remove_integration_service_names_enabled = _get_env(
|
|
82
|
+
"DD_TRACE_REMOVE_INTEGRATION_SERVICE_NAMES_ENABLED", "false", as_bool
|
|
83
|
+
)
|
|
84
|
+
|
|
78
85
|
enhanced_metrics_enabled = _get_env("DD_ENHANCED_METRICS", "true", as_bool)
|
|
79
86
|
|
|
80
87
|
flush_in_thread = _get_env("DD_FLUSH_IN_THREAD", "false", as_bool)
|
|
@@ -89,6 +96,10 @@ class Config:
|
|
|
89
96
|
data_streams_enabled = _get_env(
|
|
90
97
|
"DD_DATA_STREAMS_ENABLED", "false", as_bool, depends_on_tracing=True
|
|
91
98
|
)
|
|
99
|
+
# EventBridge bus name used as the DSM `exchange` tag. The bus name is not
|
|
100
|
+
# present in the inbound event, so it must be provided explicitly to allow
|
|
101
|
+
# the consume checkpoint to pair with the EventBridge produce checkpoint.
|
|
102
|
+
dsm_exchange_name = _get_env("DD_DSM_EXCHANGE_NAME")
|
|
92
103
|
appsec_enabled = _get_env("DD_APPSEC_ENABLED", "false", as_bool)
|
|
93
104
|
sca_enabled = _get_env("DD_APPSEC_SCA_ENABLED", "false", as_bool)
|
|
94
105
|
|
|
@@ -83,6 +83,47 @@ def _dsm_set_checkpoint(context_json, event_type, arn):
|
|
|
83
83
|
)
|
|
84
84
|
|
|
85
85
|
|
|
86
|
+
def _dsm_set_eventbridge_checkpoint(context_json, detail_type):
|
|
87
|
+
"""Set a DSM consume checkpoint for an EventBridge event.
|
|
88
|
+
|
|
89
|
+
When ddtrace >= 4.14 is present the public ``tags`` parameter is used to
|
|
90
|
+
attach the ``exchange:`` edge tag (sourced from ``DD_DSM_EXCHANGE_NAME``).
|
|
91
|
+
On older installs the checkpoint is still emitted, just without the
|
|
92
|
+
exchange tag.
|
|
93
|
+
"""
|
|
94
|
+
if not config.data_streams_enabled or not detail_type:
|
|
95
|
+
return
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
from ddtrace.data_streams import set_consume_checkpoint
|
|
99
|
+
|
|
100
|
+
carrier_get = lambda k: context_json and context_json.get(k) # noqa: E731
|
|
101
|
+
try:
|
|
102
|
+
tags = (
|
|
103
|
+
["exchange:" + config.dsm_exchange_name]
|
|
104
|
+
if config.dsm_exchange_name
|
|
105
|
+
else None
|
|
106
|
+
)
|
|
107
|
+
set_consume_checkpoint(
|
|
108
|
+
"eventbridge",
|
|
109
|
+
detail_type,
|
|
110
|
+
carrier_get,
|
|
111
|
+
manual_checkpoint=False,
|
|
112
|
+
tags=tags,
|
|
113
|
+
)
|
|
114
|
+
except TypeError:
|
|
115
|
+
# ddtrace < 4.14 has no `tags` parameter. Retry without it, but
|
|
116
|
+
# keep `manual_checkpoint=False` so the checkpoint stays
|
|
117
|
+
# consistent with every other consume checkpoint.
|
|
118
|
+
set_consume_checkpoint(
|
|
119
|
+
"eventbridge", detail_type, carrier_get, manual_checkpoint=False
|
|
120
|
+
)
|
|
121
|
+
except Exception as e:
|
|
122
|
+
logger.debug(
|
|
123
|
+
f"DSM:Failed to set consume checkpoint for eventbridge {detail_type}: {e}"
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
|
|
86
127
|
def _convert_xray_trace_id(xray_trace_id):
|
|
87
128
|
"""
|
|
88
129
|
Convert X-Ray trace id (hex)'s last 63 bits to a Datadog trace id (int).
|
|
@@ -251,11 +292,21 @@ def extract_context_from_sqs_or_sns_event_or_context(
|
|
|
251
292
|
source_arn = ""
|
|
252
293
|
event_type = "sqs" if event_source.equals(EventTypes.SQS) else "sns"
|
|
253
294
|
|
|
254
|
-
# EventBridge => SQS
|
|
295
|
+
# EventBridge => SQS. `dsm_handled` is True when the batch contained at
|
|
296
|
+
# least one EventBridge delivery and DSM checkpoints were already set
|
|
297
|
+
# per-record; in that case the regular SQS path below must not set another
|
|
298
|
+
# checkpoint for the first record or it would be double counted.
|
|
299
|
+
dsm_handled = False
|
|
255
300
|
try:
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
301
|
+
(
|
|
302
|
+
context,
|
|
303
|
+
is_eventbridge_sqs,
|
|
304
|
+
dsm_handled,
|
|
305
|
+
) = _extract_context_from_eventbridge_sqs_event(event)
|
|
306
|
+
if is_eventbridge_sqs:
|
|
307
|
+
if _is_context_complete(context):
|
|
308
|
+
return context
|
|
309
|
+
return extract_context_from_lambda_context(lambda_context)
|
|
259
310
|
except Exception:
|
|
260
311
|
logger.debug("Failed extracting context as EventBridge to SQS.")
|
|
261
312
|
|
|
@@ -312,7 +363,8 @@ def extract_context_from_sqs_or_sns_event_or_context(
|
|
|
312
363
|
"Failed to extract Step Functions context from SQS/SNS event."
|
|
313
364
|
)
|
|
314
365
|
context = propagator.extract(dd_data)
|
|
315
|
-
|
|
366
|
+
if not dsm_handled:
|
|
367
|
+
_dsm_set_checkpoint(dd_data, event_type, source_arn)
|
|
316
368
|
return context
|
|
317
369
|
else:
|
|
318
370
|
# Handle case where trace context is injected into attributes.AWSTraceHeader
|
|
@@ -337,12 +389,14 @@ def extract_context_from_sqs_or_sns_event_or_context(
|
|
|
337
389
|
sampling_priority=float(x_ray_context["sampled"]),
|
|
338
390
|
)
|
|
339
391
|
# Still want to set a DSM checkpoint even if DSM context not propagated
|
|
340
|
-
|
|
392
|
+
if not dsm_handled:
|
|
393
|
+
_dsm_set_checkpoint(None, event_type, source_arn)
|
|
341
394
|
return extract_context_from_lambda_context(lambda_context)
|
|
342
395
|
except Exception as e:
|
|
343
396
|
logger.debug("The trace extractor returned with error %s", e)
|
|
344
397
|
# Still want to set a DSM checkpoint even if DSM context not propagated
|
|
345
|
-
|
|
398
|
+
if not dsm_handled:
|
|
399
|
+
_dsm_set_checkpoint(None, event_type, source_arn)
|
|
346
400
|
return extract_context_from_lambda_context(lambda_context)
|
|
347
401
|
|
|
348
402
|
|
|
@@ -353,22 +407,190 @@ def _extract_context_from_eventbridge_sqs_event(event):
|
|
|
353
407
|
|
|
354
408
|
This is only possible if first record in `Records` contains a
|
|
355
409
|
`body` field which contains the EventBridge `detail` as a JSON string.
|
|
410
|
+
|
|
411
|
+
Returns a tuple ``(context, is_eventbridge_sqs, dsm_handled)``:
|
|
412
|
+
|
|
413
|
+
* ``context`` / ``is_eventbridge_sqs`` describe the trace context and are
|
|
414
|
+
derived only from the first record, since that is the record whose trace
|
|
415
|
+
context becomes the Lambda's parent.
|
|
416
|
+
* ``dsm_handled`` reports whether this function already set DSM checkpoints
|
|
417
|
+
for the batch. It is ``True`` whenever *any* record in the batch is an
|
|
418
|
+
EventBridge delivery, so the caller must not set its own SQS checkpoint
|
|
419
|
+
(which would double count the first record).
|
|
356
420
|
"""
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
421
|
+
records = event.get("Records")
|
|
422
|
+
if not records:
|
|
423
|
+
return None, False, False
|
|
424
|
+
|
|
425
|
+
first_record = records[0]
|
|
426
|
+
dd_context, is_eventbridge_sqs = _extract_eventbridge_sqs_record_context(
|
|
427
|
+
first_record
|
|
428
|
+
)
|
|
429
|
+
|
|
430
|
+
# Set a consume checkpoint for every record in the batch whenever the batch
|
|
431
|
+
# contains at least one EventBridge delivery. Each record is classified
|
|
432
|
+
# independently so a mixed batch (EventBridge deliveries alongside direct
|
|
433
|
+
# SQS sends, in either order) uses the correct carrier per record. The
|
|
434
|
+
# message is consumed from the SQS queue, so it follows SQS conventions
|
|
435
|
+
# (type:sqs, topic:queue ARN).
|
|
436
|
+
dsm_handled = _dsm_set_eventbridge_sqs_batch_checkpoints(records)
|
|
437
|
+
|
|
438
|
+
if not is_eventbridge_sqs:
|
|
439
|
+
return None, False, dsm_handled
|
|
362
440
|
|
|
363
441
|
if is_step_function_event(dd_context):
|
|
364
442
|
try:
|
|
365
|
-
return
|
|
443
|
+
return (
|
|
444
|
+
extract_context_from_step_functions(dd_context, None),
|
|
445
|
+
True,
|
|
446
|
+
dsm_handled,
|
|
447
|
+
)
|
|
366
448
|
except Exception:
|
|
367
449
|
logger.debug(
|
|
368
450
|
"Failed to extract Step Functions context from EventBridge to SQS event."
|
|
369
451
|
)
|
|
370
452
|
|
|
371
|
-
return propagator.extract(dd_context)
|
|
453
|
+
return propagator.extract(dd_context), True, dsm_handled
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
def _dsm_set_eventbridge_sqs_batch_checkpoints(records):
|
|
457
|
+
"""Set a per-record SQS DSM consume checkpoint for an EventBridge -> SQS
|
|
458
|
+
batch.
|
|
459
|
+
|
|
460
|
+
Returns ``True`` when the batch contains at least one EventBridge delivery
|
|
461
|
+
(and checkpoints were therefore this function's responsibility), otherwise
|
|
462
|
+
``False`` so the caller can fall back to its regular SQS checkpoint path.
|
|
463
|
+
Each record is classified independently: EventBridge records use the
|
|
464
|
+
carrier embedded in ``body.detail._datadog`` while other records fall back
|
|
465
|
+
to the SQS ``messageAttributes._datadog`` carrier.
|
|
466
|
+
"""
|
|
467
|
+
if not config.data_streams_enabled:
|
|
468
|
+
return False
|
|
469
|
+
|
|
470
|
+
record_carriers = []
|
|
471
|
+
batch_has_eventbridge = False
|
|
472
|
+
for record in records:
|
|
473
|
+
record_context, is_eventbridge_record = _extract_eventbridge_sqs_record_context(
|
|
474
|
+
record
|
|
475
|
+
)
|
|
476
|
+
if is_eventbridge_record:
|
|
477
|
+
batch_has_eventbridge = True
|
|
478
|
+
else:
|
|
479
|
+
try:
|
|
480
|
+
record_context = _extract_sqs_record_message_attribute_context(record)
|
|
481
|
+
except Exception:
|
|
482
|
+
record_context = None
|
|
483
|
+
record_carriers.append(record_context)
|
|
484
|
+
|
|
485
|
+
if not batch_has_eventbridge:
|
|
486
|
+
return False
|
|
487
|
+
|
|
488
|
+
for record, record_context in zip(records, record_carriers):
|
|
489
|
+
try:
|
|
490
|
+
_dsm_set_checkpoint(record_context, "sqs", record.get("eventSourceARN", ""))
|
|
491
|
+
except Exception:
|
|
492
|
+
logger.debug(
|
|
493
|
+
"Failed to set DSM checkpoint for an EventBridge to SQS record."
|
|
494
|
+
)
|
|
495
|
+
|
|
496
|
+
return True
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
def _extract_eventbridge_sqs_record_context(record):
|
|
500
|
+
"""Classify a single SQS record as an EventBridge delivery and return its
|
|
501
|
+
DSM carrier.
|
|
502
|
+
|
|
503
|
+
Returns a tuple ``(dd_context, is_eventbridge)``. A record is only treated
|
|
504
|
+
as EventBridge when its ``body`` is a JSON object carrying the EventBridge
|
|
505
|
+
envelope fields (``detail`` object plus ``detail-type`` and ``source``). A
|
|
506
|
+
non-JSON or non-envelope body is not an error here: it simply means the
|
|
507
|
+
record is a regular SQS message, so the caller can fall back to the SQS
|
|
508
|
+
message attribute carrier instead.
|
|
509
|
+
"""
|
|
510
|
+
body_str = record.get("body")
|
|
511
|
+
try:
|
|
512
|
+
body = json.loads(body_str)
|
|
513
|
+
except (ValueError, TypeError):
|
|
514
|
+
return None, False
|
|
515
|
+
|
|
516
|
+
if not isinstance(body, dict):
|
|
517
|
+
return None, False
|
|
518
|
+
|
|
519
|
+
detail = body.get("detail")
|
|
520
|
+
if not (
|
|
521
|
+
isinstance(detail, dict) and body.get("detail-type") and body.get("source")
|
|
522
|
+
):
|
|
523
|
+
return None, False
|
|
524
|
+
|
|
525
|
+
return detail.get("_datadog"), True
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
def _extract_sqs_record_message_attribute_context(record):
|
|
529
|
+
"""Return the ``_datadog`` carrier for a non-EventBridge SQS record.
|
|
530
|
+
|
|
531
|
+
A record in an SQS batch may itself be an SNS notification (an SNS => SQS
|
|
532
|
+
subscription without raw message delivery), in which case the attributes
|
|
533
|
+
live in the SNS envelope inside ``body`` rather than in the record's own
|
|
534
|
+
``messageAttributes``. Both shapes are handled here so that a mixed batch
|
|
535
|
+
(EventBridge deliveries alongside SNS deliveries on the same queue) does
|
|
536
|
+
not lose the SNS DSM context.
|
|
537
|
+
"""
|
|
538
|
+
msg_attributes = record.get("messageAttributes")
|
|
539
|
+
|
|
540
|
+
if not msg_attributes:
|
|
541
|
+
sns_record = _parse_sns_notification_from_sqs_body(record) or {}
|
|
542
|
+
msg_attributes = sns_record.get("MessageAttributes") or {}
|
|
543
|
+
|
|
544
|
+
return _decode_dd_message_attribute(msg_attributes.get("_datadog"))
|
|
545
|
+
|
|
546
|
+
|
|
547
|
+
def _parse_sns_notification_from_sqs_body(record):
|
|
548
|
+
"""Return the SNS envelope carried in an SQS record's ``body``, if any.
|
|
549
|
+
|
|
550
|
+
Returns ``None`` when the body is not an SNS notification, which simply
|
|
551
|
+
means the record is a direct SQS send.
|
|
552
|
+
"""
|
|
553
|
+
try:
|
|
554
|
+
body = json.loads(record.get("body"))
|
|
555
|
+
except (ValueError, TypeError):
|
|
556
|
+
return None
|
|
557
|
+
|
|
558
|
+
if (
|
|
559
|
+
isinstance(body, dict)
|
|
560
|
+
and body.get("Type", "") == "Notification"
|
|
561
|
+
and "TopicArn" in body
|
|
562
|
+
):
|
|
563
|
+
return body
|
|
564
|
+
|
|
565
|
+
return None
|
|
566
|
+
|
|
567
|
+
|
|
568
|
+
def _decode_dd_message_attribute(dd_payload):
|
|
569
|
+
"""Decode a ``_datadog`` SQS/SNS message attribute into a carrier dict.
|
|
570
|
+
|
|
571
|
+
SQS uses ``dataType`` with ``stringValue``/``binaryValue`` while SNS uses
|
|
572
|
+
``Type`` with ``Value``; both are supported.
|
|
573
|
+
"""
|
|
574
|
+
if not dd_payload:
|
|
575
|
+
return None
|
|
576
|
+
|
|
577
|
+
dd_json_data = None
|
|
578
|
+
dd_json_data_type = dd_payload.get("Type") or dd_payload.get("dataType")
|
|
579
|
+
if dd_json_data_type == "Binary":
|
|
580
|
+
import base64
|
|
581
|
+
|
|
582
|
+
dd_json_data = dd_payload.get("binaryValue") or dd_payload.get("Value")
|
|
583
|
+
if dd_json_data:
|
|
584
|
+
dd_json_data = base64.b64decode(dd_json_data)
|
|
585
|
+
elif dd_json_data_type == "String":
|
|
586
|
+
dd_json_data = dd_payload.get("stringValue") or dd_payload.get("Value")
|
|
587
|
+
else:
|
|
588
|
+
logger.debug(
|
|
589
|
+
"Datadog Lambda Python only supports extracting trace"
|
|
590
|
+
"context from String or Binary SQS/SNS message attributes"
|
|
591
|
+
)
|
|
592
|
+
|
|
593
|
+
return json.loads(dd_json_data) if dd_json_data else None
|
|
372
594
|
|
|
373
595
|
|
|
374
596
|
def extract_context_from_eventbridge_event(event, lambda_context):
|
|
@@ -380,8 +602,11 @@ def extract_context_from_eventbridge_event(event, lambda_context):
|
|
|
380
602
|
that header.
|
|
381
603
|
"""
|
|
382
604
|
try:
|
|
383
|
-
detail = event.get("detail")
|
|
605
|
+
detail = event.get("detail") or {}
|
|
384
606
|
dd_context = detail.get("_datadog")
|
|
607
|
+
|
|
608
|
+
_dsm_set_eventbridge_checkpoint(dd_context, event.get("detail-type"))
|
|
609
|
+
|
|
385
610
|
if not dd_context:
|
|
386
611
|
return extract_context_from_lambda_context(lambda_context)
|
|
387
612
|
|
|
@@ -906,11 +1131,14 @@ def determine_service_name(
|
|
|
906
1131
|
if mapped_service:
|
|
907
1132
|
return mapped_service
|
|
908
1133
|
|
|
1134
|
+
# When integration service names are removed, inferred (synthetic) spans use
|
|
1135
|
+
# the base service name (DD_SERVICE) instead of the AWS resource/instance
|
|
1136
|
+
# representation.
|
|
1137
|
+
if config.remove_integration_service_names_enabled and config.service:
|
|
1138
|
+
return config.service
|
|
1139
|
+
|
|
909
1140
|
# Check if AWS service representation is disabled
|
|
910
|
-
|
|
911
|
-
"DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED", ""
|
|
912
|
-
).lower()
|
|
913
|
-
if aws_service_representation in ("false", "0"):
|
|
1141
|
+
if not config.aws_service_representation_enabled:
|
|
914
1142
|
return fallback
|
|
915
1143
|
|
|
916
1144
|
# Use extracted_key if it exists and is not empty, otherwise use fallback
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "8.129.0"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "datadog_lambda"
|
|
3
|
-
version = "8.
|
|
3
|
+
version = "8.129.0"
|
|
4
4
|
description = "The Datadog AWS Lambda Library"
|
|
5
5
|
authors = ["Datadog, Inc. <dev@datadoghq.com>"]
|
|
6
6
|
license = "Apache-2.0"
|
|
@@ -28,7 +28,7 @@ classifiers = [
|
|
|
28
28
|
[tool.poetry.dependencies]
|
|
29
29
|
python = ">=3.8.0,<3.15"
|
|
30
30
|
datadog = ">=0.51.0,<1.0.0"
|
|
31
|
-
wrapt = "
|
|
31
|
+
wrapt = ">=1.11.2,!=2.2.0,<3"
|
|
32
32
|
ddtrace = [
|
|
33
33
|
{version = ">=3.19.1,<4", python = ">=3.8,<3.10"},
|
|
34
34
|
{version = ">=4.1.1,<5,!=4.6.*", python = ">=3.10"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "8.127.0"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|