datadog_lambda 8.113.0__py3-none-any.whl → 8.116.0__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.
@@ -3,10 +3,11 @@ import os
3
3
 
4
4
 
5
5
  if os.environ.get("DD_INSTRUMENTATION_TELEMETRY_ENABLED") is None:
6
- os.environ["DD_INSTRUMENTATION_TELEMETRY_ENABLED"] = "false"
6
+ # Telemetry is required for Appsec Software Composition Analysis
7
+ os.environ["DD_INSTRUMENTATION_TELEMETRY_ENABLED"] = os.environ.get(
8
+ "DD_APPSEC_ENABLED", "false"
9
+ )
7
10
 
8
- if os.environ.get("DD_API_SECURITY_ENABLED") is None:
9
- os.environ["DD_API_SECURITY_ENABLED"] = "False"
10
11
 
11
12
  initialize_cold_start_tracing()
12
13
 
datadog_lambda/asm.py CHANGED
@@ -73,17 +73,16 @@ def asm_start_request(
73
73
  route: Optional[str] = None
74
74
 
75
75
  if event_source.event_type == EventTypes.ALB:
76
- headers = event.get("headers")
77
- multi_value_request_headers = event.get("multiValueHeaders")
78
- if multi_value_request_headers:
79
- request_headers = _to_single_value_headers(multi_value_request_headers)
80
- else:
81
- request_headers = headers or {}
82
-
83
76
  raw_uri = event.get("path")
84
- parsed_query = event.get("multiValueQueryStringParameters") or event.get(
85
- "queryStringParameters"
86
- )
77
+
78
+ if event_source.subtype == EventSubtypes.ALB:
79
+ request_headers = event.get("headers", {})
80
+ parsed_query = event.get("queryStringParameters")
81
+ if event_source.subtype == EventSubtypes.ALB_MULTI_VALUE_HEADERS:
82
+ request_headers = _to_single_value_headers(
83
+ event.get("multiValueHeaders", {})
84
+ )
85
+ parsed_query = event.get("multiValueQueryStringParameters")
87
86
 
88
87
  elif event_source.event_type == EventTypes.LAMBDA_FUNCTION_URL:
89
88
  request_headers = event.get("headers", {})
@@ -127,8 +126,8 @@ def asm_start_request(
127
126
 
128
127
  request_ip = _get_request_header_client_ip(request_headers, peer_ip, True)
129
128
  if request_ip is not None:
130
- span.set_tag_str("http.client_ip", request_ip)
131
- span.set_tag_str("network.client.ip", request_ip)
129
+ span.set_tag("http.client_ip", request_ip)
130
+ span.set_tag("network.client.ip", request_ip)
132
131
 
133
132
  # Encode the parsed query and append it to reconstruct the original raw URI expected by AppSec.
134
133
  if parsed_query:
@@ -226,15 +225,27 @@ def get_asm_blocked_response(
226
225
  content_type = blocked.get("content-type", "application/json")
227
226
  content = http_utils._get_blocked_template(content_type)
228
227
 
229
- response_headers = {
230
- "content-type": content_type,
231
- }
232
- if "location" in blocked:
233
- response_headers["location"] = blocked["location"]
234
-
235
- return {
228
+ response = {
236
229
  "statusCode": blocked.get("status_code", 403),
237
- "headers": response_headers,
238
230
  "body": content,
239
231
  "isBase64Encoded": False,
240
232
  }
233
+
234
+ needs_multi_value_headers = event_source.equals(
235
+ EventTypes.ALB, EventSubtypes.ALB_MULTI_VALUE_HEADERS
236
+ )
237
+
238
+ if needs_multi_value_headers:
239
+ response["multiValueHeaders"] = {
240
+ "content-type": [content_type],
241
+ }
242
+ if "location" in blocked:
243
+ response["multiValueHeaders"]["location"] = [blocked["location"]]
244
+ else:
245
+ response["headers"] = {
246
+ "content-type": content_type,
247
+ }
248
+ if "location" in blocked:
249
+ response["headers"]["location"] = blocked["location"]
250
+
251
+ return response
datadog_lambda/metric.py CHANGED
@@ -214,6 +214,33 @@ def submit_errors_metric(lambda_context):
214
214
  submit_enhanced_metric("errors", lambda_context)
215
215
 
216
216
 
217
+ def submit_batch_item_failures_metric(response, lambda_context):
218
+ """Submit aws.lambda.enhanced.batch_item_failures metric with the count of batch item failures
219
+
220
+ Args:
221
+ response (dict): Lambda function response object
222
+ lambda_context (object): Lambda context dict passed to the function by AWS
223
+ """
224
+ if not config.enhanced_metrics_enabled:
225
+ logger.debug(
226
+ "Not submitting batch_item_failures metric because enhanced metrics are disabled"
227
+ )
228
+ return
229
+
230
+ if not isinstance(response, dict):
231
+ return
232
+
233
+ batch_item_failures = response.get("batchItemFailures")
234
+ if batch_item_failures is not None and isinstance(batch_item_failures, list):
235
+ lambda_metric(
236
+ "aws.lambda.enhanced.batch_item_failures",
237
+ len(batch_item_failures),
238
+ timestamp=None,
239
+ tags=get_enhanced_metrics_tags(lambda_context),
240
+ force_async=True,
241
+ )
242
+
243
+
217
244
  def submit_dynamodb_stream_type_metric(event):
218
245
  stream_view_type = (
219
246
  event.get("Records", [{}])[0].get("dynamodb", {}).get("StreamViewType")
datadog_lambda/tracing.py CHANGED
@@ -209,6 +209,20 @@ def extract_context_from_http_event_or_context(
209
209
  return context
210
210
 
211
211
 
212
+ def extract_context_from_request_header_or_context(event, lambda_context, event_source):
213
+ request = event.get("request")
214
+ if isinstance(request, (set, dict)) and "headers" in request:
215
+ context = extract_context_from_http_event_or_context(
216
+ request,
217
+ lambda_context,
218
+ event_source,
219
+ decode_authorizer_context=False,
220
+ )
221
+ else:
222
+ context = extract_context_from_lambda_context(lambda_context)
223
+ return context
224
+
225
+
212
226
  def create_sns_event(message):
213
227
  return {
214
228
  "Records": [
@@ -627,6 +641,10 @@ def extract_dd_trace_context(
627
641
 
628
642
  if extractor is not None:
629
643
  context = extract_context_custom_extractor(extractor, event, lambda_context)
644
+ elif isinstance(event, (set, dict)) and "request" in event:
645
+ context = extract_context_from_request_header_or_context(
646
+ event, lambda_context, event_source
647
+ )
630
648
  elif isinstance(event, (set, dict)) and "headers" in event:
631
649
  context = extract_context_from_http_event_or_context(
632
650
  event, lambda_context, event_source, decode_authorizer_context
datadog_lambda/trigger.py CHANGED
@@ -54,6 +54,10 @@ class EventSubtypes(_stringTypedEnum):
54
54
  WEBSOCKET = "websocket"
55
55
  HTTP_API = "http-api"
56
56
 
57
+ ALB = "alb" # regular alb
58
+ # ALB with the multi-value headers option checked on the target group
59
+ ALB_MULTI_VALUE_HEADERS = "alb-multi-value-headers"
60
+
57
61
 
58
62
  class _EventSource:
59
63
  """
@@ -133,7 +137,12 @@ def parse_event_source(event: dict) -> _EventSource:
133
137
  event_source.subtype = EventSubtypes.WEBSOCKET
134
138
 
135
139
  if request_context and request_context.get("elb"):
136
- event_source = _EventSource(EventTypes.ALB)
140
+ if "multiValueHeaders" in event:
141
+ event_source = _EventSource(
142
+ EventTypes.ALB, EventSubtypes.ALB_MULTI_VALUE_HEADERS
143
+ )
144
+ else:
145
+ event_source = _EventSource(EventTypes.ALB, EventSubtypes.ALB)
137
146
 
138
147
  if event.get("awslogs"):
139
148
  event_source = _EventSource(EventTypes.CLOUDWATCH_LOGS)
@@ -288,7 +297,7 @@ def extract_http_tags(event):
288
297
  """
289
298
  Extracts HTTP facet tags from the triggering event
290
299
  """
291
- http_tags = {}
300
+ http_tags = {"span.kind": "server"}
292
301
 
293
302
  # Safely get request_context and ensure it's a dictionary
294
303
  request_context = event.get("requestContext")
@@ -316,7 +325,8 @@ def extract_http_tags(event):
316
325
  method = apigateway_v2_http.get("method")
317
326
 
318
327
  if path:
319
- http_tags["http.url_details.path"] = path
328
+ if http_tags.get("http.url"):
329
+ http_tags["http.url"] += path
320
330
  if method:
321
331
  http_tags["http.method"] = method
322
332
 
datadog_lambda/version.py CHANGED
@@ -1 +1 @@
1
- __version__ = "8.113.0"
1
+ __version__ = "8.116.0"
datadog_lambda/wrapper.py CHANGED
@@ -46,6 +46,10 @@ from datadog_lambda.trigger import (
46
46
  extract_http_status_code_tag,
47
47
  )
48
48
 
49
+ # ddtrace imports are also tested in
50
+ # dd-trace-py/tests/internal/test_serverless.py please update those tests when
51
+ # making changes to any ddtrace import.
52
+
49
53
  if config.appsec_enabled:
50
54
  from datadog_lambda.asm import (
51
55
  asm_set_context,
@@ -53,6 +57,9 @@ if config.appsec_enabled:
53
57
  asm_start_request,
54
58
  get_asm_blocked_response,
55
59
  )
60
+ from ddtrace.internal.appsec.product import start
61
+
62
+ start()
56
63
 
57
64
  if config.profiling_enabled:
58
65
  from ddtrace.profiling import profiler
@@ -62,7 +69,11 @@ if config.llmobs_enabled:
62
69
 
63
70
  if config.exception_replay_enabled:
64
71
  from ddtrace.debugging._exception.replay import SpanExceptionHandler
65
- from ddtrace.debugging._uploader import LogsIntakeUploaderV1
72
+
73
+ try:
74
+ from ddtrace.debugging._uploader import SignalUploader
75
+ except ImportError:
76
+ from ddtrace.debugging._uploader import LogsIntakeUploaderV1 as SignalUploader
66
77
 
67
78
  logger = logging.getLogger(__name__)
68
79
 
@@ -280,7 +291,39 @@ class _LambdaDecorator(object):
280
291
 
281
292
  def _after(self, event, context):
282
293
  try:
294
+ from datadog_lambda.metric import submit_batch_item_failures_metric
295
+
296
+ submit_batch_item_failures_metric(self.response, context)
297
+
283
298
  status_code = extract_http_status_code_tag(self.trigger_tags, self.response)
299
+
300
+ if self.span:
301
+ if config.appsec_enabled and not self.blocking_response:
302
+ asm_start_response(
303
+ self.span,
304
+ status_code,
305
+ self.event_source,
306
+ response=self.response,
307
+ )
308
+ self.blocking_response = get_asm_blocked_response(self.event_source)
309
+
310
+ if self.blocking_response:
311
+ status_code = str(self.blocking_response.get("statusCode"))
312
+ if config.capture_payload_enabled and self.response:
313
+ tag_object(
314
+ self.span, "function.blocked_response", self.response
315
+ )
316
+ self.response = self.blocking_response
317
+
318
+ if config.capture_payload_enabled:
319
+ tag_object(self.span, "function.request", event)
320
+ tag_object(self.span, "function.response", self.response)
321
+
322
+ if status_code:
323
+ self.span.set_tag("http.status_code", status_code)
324
+
325
+ self.span.finish()
326
+
284
327
  if status_code:
285
328
  self.trigger_tags["http.status_code"] = status_code
286
329
  mark_trace_as_error_for_5xx_responses(context, status_code, self.span)
@@ -295,25 +338,6 @@ class _LambdaDecorator(object):
295
338
  if should_trace_cold_start:
296
339
  trace_ctx = tracer.current_trace_context()
297
340
 
298
- if self.span:
299
- if config.capture_payload_enabled:
300
- tag_object(self.span, "function.request", event)
301
- tag_object(self.span, "function.response", self.response)
302
-
303
- if status_code:
304
- self.span.set_tag("http.status_code", status_code)
305
-
306
- if config.appsec_enabled and not self.blocking_response:
307
- asm_start_response(
308
- self.span,
309
- status_code,
310
- self.event_source,
311
- response=self.response,
312
- )
313
- self.blocking_response = get_asm_blocked_response(self.event_source)
314
-
315
- self.span.finish()
316
-
317
341
  if self.inferred_span:
318
342
  if status_code:
319
343
  self.inferred_span.set_tag("http.status_code", status_code)
@@ -358,7 +382,7 @@ class _LambdaDecorator(object):
358
382
 
359
383
  # Flush exception replay
360
384
  if config.exception_replay_enabled:
361
- LogsIntakeUploaderV1._instance.periodic()
385
+ SignalUploader._instance.periodic()
362
386
 
363
387
  if config.encode_authorizer_context and is_authorizer_response(
364
388
  self.response
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: datadog_lambda
3
- Version: 8.113.0
3
+ Version: 8.116.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
@@ -19,7 +19,7 @@ Classifier: Programming Language :: Python :: 3.13
19
19
  Provides-Extra: dev
20
20
  Requires-Dist: botocore (>=1.34.0,<2.0.0) ; extra == "dev"
21
21
  Requires-Dist: datadog (>=0.51.0,<1.0.0)
22
- Requires-Dist: ddtrace (>=3.11.0,<4)
22
+ Requires-Dist: ddtrace (>=3.16.2,<4)
23
23
  Requires-Dist: flake8 (>=5.0.4,<6.0.0) ; extra == "dev"
24
24
  Requires-Dist: pytest (>=8.0.0,<9.0.0) ; extra == "dev"
25
25
  Requires-Dist: pytest-benchmark (>=4.0,<5.0) ; extra == "dev"
@@ -1,6 +1,6 @@
1
- datadog_lambda/__init__.py,sha256=uAqki08Fd26eUXZtoptJX68kTFhK-ZkCyLVCtE0MS4k,802
1
+ datadog_lambda/__init__.py,sha256=rAQyhhjO-TzPu3_EtL111hr03gdeeQRmm1Zsz2dKKTw,816
2
2
  datadog_lambda/api.py,sha256=FqQ9vjNSxD5BxjeHDDKbim_NIN08dshW-52wNzqw5yo,5249
3
- datadog_lambda/asm.py,sha256=pYgtSKtOj8KTd701iPiXvOXQ32D1nH_k1BzOmMU7L1k,7767
3
+ datadog_lambda/asm.py,sha256=ZOX_rElpGF5uX-r-SoPnjFAv0vSd1y7YYfwz5m38xpc,8167
4
4
  datadog_lambda/cold_start.py,sha256=NfE4EtRUHuc2ub26U8sAwMVEDNINbw1OTh2MAklQGyk,8032
5
5
  datadog_lambda/config.py,sha256=QoEZrSL_zSHY5lV9RznAwPONEZe0i3mCWp2jPoO8aEw,5260
6
6
  datadog_lambda/constants.py,sha256=0y6O9s_8RLflYR507SDMQjKmYY16tr1yi2KQuuF1GaY,1696
@@ -8,7 +8,7 @@ datadog_lambda/dogstatsd.py,sha256=-2HiU3xvV_beXh8rI9oIXxSUmVcamI0KqAl7gc9LkYA,5
8
8
  datadog_lambda/extension.py,sha256=ZU64QpA2K9K9C0jfqusBgpiWQe0QA2dcJCNk7UgjVfw,621
9
9
  datadog_lambda/handler.py,sha256=VhdNUb_lpPRJ73h-fdDxxqSm0Ic_lkt4x_9Rt0amVV4,1312
10
10
  datadog_lambda/logger.py,sha256=nGxNMouF7wcjmoPsgivzzjNLvSy3WbGtKElxOvITZDg,766
11
- datadog_lambda/metric.py,sha256=aQI2N96nXB9NHTNsMgerCcA6SP2R_bip0dmNeOULxB0,7684
11
+ datadog_lambda/metric.py,sha256=vRg0E9-nn4aXhzgu08NC58AnTBKm2anvdvHoDzyllOQ,8636
12
12
  datadog_lambda/module_name.py,sha256=5FmOCjjgjq78b6a83QePZZFmqahAoy9XHdUNWdq2D1Q,139
13
13
  datadog_lambda/patch.py,sha256=veoZyepRCY2OlH2m_TJ9mRC-weDuzMZD6BkipHC3n3U,4639
14
14
  datadog_lambda/span_pointers.py,sha256=le8dcZqyrWHf6I1ELZ4Ym3Xd4QNUuE5PoBctuYw5Ij0,6245
@@ -17,14 +17,14 @@ datadog_lambda/statsd_writer.py,sha256=wUNG8phIreNOGCc5VjYZV-FNogvKfiz9kN33QldqZ
17
17
  datadog_lambda/tag_object.py,sha256=NNvMVi07kNYRBMPpB7Lk7_-z5ccmxo2UmTWnvWVPhiM,2089
18
18
  datadog_lambda/tags.py,sha256=wy6uH8eAGMn7cfZEdHpL9uEGoM85bVyyXhYwSQtfHHc,2532
19
19
  datadog_lambda/thread_stats_writer.py,sha256=VfD6G65zNHDA0nABhGOO0it7yqvn-p6ereQ329DA7r8,2894
20
- datadog_lambda/tracing.py,sha256=A7uUXlWBwfxOa2PFVLg2Tgft8Ft14xz-kSOjvOaA8JQ,57396
21
- datadog_lambda/trigger.py,sha256=7EhNoCtJbnuYaqCvv1i2tAqZxfXx7ccsnMkwnWvmd7w,14212
22
- datadog_lambda/version.py,sha256=VIVUGpBGnx4PNj35gsO1Xf4OfZ_Naws0GXp54dMVxj0,24
23
- datadog_lambda/wrapper.py,sha256=ZCszRfSAbBplOcPJ_RI5XTMSkNBfvgEJYbYEdhlhbwQ,14270
20
+ datadog_lambda/tracing.py,sha256=CbfEE3zf5wh7csIJCurYAT-UVTB4rzlm6iF9vVN-RTE,58067
21
+ datadog_lambda/trigger.py,sha256=bkNe77eOVuYc8kyH7KKYlsvHZfC-NXyl9GLqJ8Qaky8,14628
22
+ datadog_lambda/version.py,sha256=0MQjkZh_IgQU9fynjjB791-Iay3ACO64V30roqZmgyw,24
23
+ datadog_lambda/wrapper.py,sha256=6Q9A2xPlv1UWdsS3bvL8fo7NwBPETyusthiOvhGlM5c,15160
24
24
  datadog_lambda/xray.py,sha256=jvA4Fk76PLMgsjUoUZ7gp2otv53hFt39Nvso1ZNaivg,3749
25
- datadog_lambda-8.113.0.dist-info/LICENSE,sha256=4yQmjpKp1MKL7DdRDPVHkKYc2W0aezm5SIDske8oAdM,11379
26
- datadog_lambda-8.113.0.dist-info/LICENSE-3rdparty.csv,sha256=9CDAR1GKawwTbZkqt1RP0uwEcaRM3RhOeTB5tWXr8Ts,1381
27
- datadog_lambda-8.113.0.dist-info/METADATA,sha256=eOT372rwVMUz4fdAIrkcEfT3Bx1Kuro2vWGymFpQY38,7727
28
- datadog_lambda-8.113.0.dist-info/NOTICE,sha256=Jue-d8mQ1ENIHDZdYc2-X8mVYtScXb8pzF1pTLN-kRc,141
29
- datadog_lambda-8.113.0.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
30
- datadog_lambda-8.113.0.dist-info/RECORD,,
25
+ datadog_lambda-8.116.0.dist-info/LICENSE,sha256=4yQmjpKp1MKL7DdRDPVHkKYc2W0aezm5SIDske8oAdM,11379
26
+ datadog_lambda-8.116.0.dist-info/LICENSE-3rdparty.csv,sha256=9CDAR1GKawwTbZkqt1RP0uwEcaRM3RhOeTB5tWXr8Ts,1381
27
+ datadog_lambda-8.116.0.dist-info/METADATA,sha256=KuESyqTSoJavVi7iG4EIhSC6gFy9qSox2UBxSwvUSzI,7727
28
+ datadog_lambda-8.116.0.dist-info/NOTICE,sha256=Jue-d8mQ1ENIHDZdYc2-X8mVYtScXb8pzF1pTLN-kRc,141
29
+ datadog_lambda-8.116.0.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
30
+ datadog_lambda-8.116.0.dist-info/RECORD,,