datadog_lambda 7.112.0__py3-none-any.whl → 8.113.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.
datadog_lambda/asm.py CHANGED
@@ -1,9 +1,12 @@
1
- from copy import deepcopy
2
1
  import logging
2
+ import urllib.parse
3
+ from copy import deepcopy
3
4
  from typing import Any, Dict, List, Optional, Union
4
5
 
5
6
  from ddtrace.contrib.internal.trace_utils import _get_request_header_client_ip
6
7
  from ddtrace.internal import core
8
+ from ddtrace.internal.utils import get_blocked
9
+ from ddtrace.internal.utils import http as http_utils
7
10
  from ddtrace.trace import Span
8
11
 
9
12
  from datadog_lambda.trigger import (
@@ -50,6 +53,7 @@ def asm_set_context(event_source: _EventSource):
50
53
  This allows the AppSecSpanProcessor to know information about the event
51
54
  at the moment the span is created and skip it when not relevant.
52
55
  """
56
+
53
57
  if event_source.event_type not in _http_event_types:
54
58
  core.set_item("appsec_skip_next_lambda_event", True)
55
59
 
@@ -126,6 +130,14 @@ def asm_start_request(
126
130
  span.set_tag_str("http.client_ip", request_ip)
127
131
  span.set_tag_str("network.client.ip", request_ip)
128
132
 
133
+ # Encode the parsed query and append it to reconstruct the original raw URI expected by AppSec.
134
+ if parsed_query:
135
+ try:
136
+ encoded_query = urllib.parse.urlencode(parsed_query, doseq=True)
137
+ raw_uri += "?" + encoded_query # type: ignore
138
+ except Exception:
139
+ pass
140
+
129
141
  core.dispatch(
130
142
  # The matching listener is registered in ddtrace.appsec._handlers
131
143
  "aws_lambda.start_request",
@@ -182,3 +194,47 @@ def asm_start_response(
182
194
  response_headers,
183
195
  ),
184
196
  )
197
+
198
+ if isinstance(response, dict) and "statusCode" in response:
199
+ body = response.get("body")
200
+ else:
201
+ body = response
202
+
203
+ core.dispatch(
204
+ # The matching listener is registered in ddtrace.appsec._handlers
205
+ "aws_lambda.parse_body",
206
+ (body,),
207
+ )
208
+
209
+
210
+ def get_asm_blocked_response(
211
+ event_source: _EventSource,
212
+ ) -> Optional[Dict[str, Any]]:
213
+ """Get the blocked response for the given event source."""
214
+ if event_source.event_type not in _http_event_types:
215
+ return None
216
+
217
+ blocked = get_blocked()
218
+ if not blocked:
219
+ return None
220
+
221
+ desired_type = blocked.get("type", "auto")
222
+ if desired_type == "none":
223
+ content_type = "text/plain; charset=utf-8"
224
+ content = ""
225
+ else:
226
+ content_type = blocked.get("content-type", "application/json")
227
+ content = http_utils._get_blocked_template(content_type)
228
+
229
+ response_headers = {
230
+ "content-type": content_type,
231
+ }
232
+ if "location" in blocked:
233
+ response_headers["location"] = blocked["location"]
234
+
235
+ return {
236
+ "statusCode": blocked.get("status_code", 403),
237
+ "headers": response_headers,
238
+ "body": content,
239
+ "isBase64Encoded": False,
240
+ }
datadog_lambda/tracing.py CHANGED
@@ -4,6 +4,7 @@
4
4
  # Copyright 2019 Datadog, Inc.
5
5
  import logging
6
6
  import os
7
+ import re
7
8
  import traceback
8
9
  import ujson as json
9
10
  from datetime import datetime, timezone
@@ -856,15 +857,31 @@ def create_service_mapping(val):
856
857
  return new_service_mapping
857
858
 
858
859
 
859
- def determine_service_name(service_mapping, specific_key, generic_key, default_value):
860
- service_name = service_mapping.get(specific_key)
861
- if service_name is None:
862
- service_name = service_mapping.get(generic_key, default_value)
863
- return service_name
860
+ def determine_service_name(
861
+ service_mapping, specific_key, generic_key, extracted_key, fallback=None
862
+ ):
863
+ # Check for mapped service (specific key first, then generic key)
864
+ mapped_service = service_mapping.get(specific_key) or service_mapping.get(
865
+ generic_key
866
+ )
867
+ if mapped_service:
868
+ return mapped_service
869
+
870
+ # Check if AWS service representation is disabled
871
+ aws_service_representation = os.environ.get(
872
+ "DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED", ""
873
+ ).lower()
874
+ if aws_service_representation in ("false", "0"):
875
+ return fallback
876
+
877
+ # Use extracted_key if it exists and is not empty, otherwise use fallback
878
+ return (
879
+ extracted_key.strip() if extracted_key and extracted_key.strip() else fallback
880
+ )
864
881
 
865
882
 
866
883
  # Initialization code
867
- service_mapping_str = os.getenv("DD_SERVICE_MAPPING", "")
884
+ service_mapping_str = os.environ.get("DD_SERVICE_MAPPING", "")
868
885
  service_mapping = create_service_mapping(service_mapping_str)
869
886
 
870
887
  _dd_origin = {"_dd.origin": "lambda"}
@@ -988,6 +1005,7 @@ def create_inferred_span_from_api_gateway_websocket_event(
988
1005
  "http.url": http_url,
989
1006
  "endpoint": endpoint,
990
1007
  "resource_names": endpoint,
1008
+ "span.kind": "server",
991
1009
  "apiid": api_id,
992
1010
  "apiname": api_id,
993
1011
  "stage": request_context.get("stage"),
@@ -1046,6 +1064,7 @@ def create_inferred_span_from_api_gateway_event(
1046
1064
  "endpoint": path,
1047
1065
  "http.method": method,
1048
1066
  "resource_names": resource,
1067
+ "span.kind": "server",
1049
1068
  "apiid": api_id,
1050
1069
  "apiname": api_id,
1051
1070
  "stage": request_context.get("stage"),
@@ -1150,12 +1169,13 @@ def create_inferred_span_from_sqs_event(event, context):
1150
1169
  event_source_arn = event_record.get("eventSourceARN")
1151
1170
  queue_name = event_source_arn.split(":")[-1]
1152
1171
  service_name = determine_service_name(
1153
- service_mapping, queue_name, "lambda_sqs", "sqs"
1172
+ service_mapping, queue_name, "lambda_sqs", queue_name, "sqs"
1154
1173
  )
1155
1174
  attrs = event_record.get("attributes") or {}
1156
1175
  tags = {
1157
1176
  "operation_name": "aws.sqs",
1158
1177
  "resource_names": queue_name,
1178
+ "span.kind": "server",
1159
1179
  "queuename": queue_name,
1160
1180
  "event_source_arn": event_source_arn,
1161
1181
  "receipt_handle": event_record.get("receiptHandle"),
@@ -1217,11 +1237,12 @@ def create_inferred_span_from_sns_event(event, context):
1217
1237
  topic_arn = sns_message.get("TopicArn")
1218
1238
  topic_name = topic_arn.split(":")[-1]
1219
1239
  service_name = determine_service_name(
1220
- service_mapping, topic_name, "lambda_sns", "sns"
1240
+ service_mapping, topic_name, "lambda_sns", topic_name, "sns"
1221
1241
  )
1222
1242
  tags = {
1223
1243
  "operation_name": "aws.sns",
1224
1244
  "resource_names": topic_name,
1245
+ "span.kind": "server",
1225
1246
  "topicname": topic_name,
1226
1247
  "topic_arn": topic_arn,
1227
1248
  "message_id": sns_message.get("MessageId"),
@@ -1252,15 +1273,16 @@ def create_inferred_span_from_kinesis_event(event, context):
1252
1273
  event_record = get_first_record(event)
1253
1274
  event_source_arn = event_record.get("eventSourceARN")
1254
1275
  event_id = event_record.get("eventID")
1255
- stream_name = event_source_arn.split(":")[-1]
1276
+ stream_name = re.sub(r"^stream/", "", (event_source_arn or "").split(":")[-1])
1256
1277
  shard_id = event_id.split(":")[0]
1257
1278
  service_name = determine_service_name(
1258
- service_mapping, stream_name, "lambda_kinesis", "kinesis"
1279
+ service_mapping, stream_name, "lambda_kinesis", stream_name, "kinesis"
1259
1280
  )
1260
1281
  kinesis = event_record.get("kinesis") or {}
1261
1282
  tags = {
1262
1283
  "operation_name": "aws.kinesis",
1263
1284
  "resource_names": stream_name,
1285
+ "span.kind": "server",
1264
1286
  "streamname": stream_name,
1265
1287
  "shardid": shard_id,
1266
1288
  "event_source_arn": event_source_arn,
@@ -1287,12 +1309,13 @@ def create_inferred_span_from_dynamodb_event(event, context):
1287
1309
  event_source_arn = event_record.get("eventSourceARN")
1288
1310
  table_name = event_source_arn.split("/")[1]
1289
1311
  service_name = determine_service_name(
1290
- service_mapping, table_name, "lambda_dynamodb", "dynamodb"
1312
+ service_mapping, table_name, "lambda_dynamodb", table_name, "dynamodb"
1291
1313
  )
1292
1314
  dynamodb_message = event_record.get("dynamodb") or {}
1293
1315
  tags = {
1294
1316
  "operation_name": "aws.dynamodb",
1295
1317
  "resource_names": table_name,
1318
+ "span.kind": "server",
1296
1319
  "tablename": table_name,
1297
1320
  "event_source_arn": event_source_arn,
1298
1321
  "event_id": event_record.get("eventID"),
@@ -1321,11 +1344,12 @@ def create_inferred_span_from_s3_event(event, context):
1321
1344
  obj = s3.get("object") or {}
1322
1345
  bucket_name = bucket.get("name")
1323
1346
  service_name = determine_service_name(
1324
- service_mapping, bucket_name, "lambda_s3", "s3"
1347
+ service_mapping, bucket_name, "lambda_s3", bucket_name, "s3"
1325
1348
  )
1326
1349
  tags = {
1327
1350
  "operation_name": "aws.s3",
1328
1351
  "resource_names": bucket_name,
1352
+ "span.kind": "server",
1329
1353
  "event_name": event_record.get("eventName"),
1330
1354
  "bucketname": bucket_name,
1331
1355
  "bucket_arn": bucket.get("arn"),
@@ -1351,11 +1375,12 @@ def create_inferred_span_from_s3_event(event, context):
1351
1375
  def create_inferred_span_from_eventbridge_event(event, context):
1352
1376
  source = event.get("source")
1353
1377
  service_name = determine_service_name(
1354
- service_mapping, source, "lambda_eventbridge", "eventbridge"
1378
+ service_mapping, source, "lambda_eventbridge", source, "eventbridge"
1355
1379
  )
1356
1380
  tags = {
1357
1381
  "operation_name": "aws.eventbridge",
1358
1382
  "resource_names": source,
1383
+ "span.kind": "server",
1359
1384
  "detail_type": event.get("detail-type"),
1360
1385
  }
1361
1386
  InferredSpanInfo.set_tags(
@@ -1429,9 +1454,21 @@ def create_function_execution_span(
1429
1454
  tags["_dd.parent_source"] = trace_context_source
1430
1455
  tags.update(trigger_tags)
1431
1456
  tracer.set_tags(_dd_origin)
1457
+ # Determine service name based on config and env var
1458
+ if config.service:
1459
+ service_name = config.service
1460
+ else:
1461
+ aws_service_representation = os.environ.get(
1462
+ "DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED", ""
1463
+ ).lower()
1464
+ if aws_service_representation in ("false", "0"):
1465
+ service_name = "aws.lambda"
1466
+ else:
1467
+ service_name = function_name if function_name else "aws.lambda"
1468
+
1432
1469
  span = tracer.trace(
1433
1470
  "aws.lambda",
1434
- service="aws.lambda",
1471
+ service=service_name,
1435
1472
  resource=function_name,
1436
1473
  span_type="serverless",
1437
1474
  )
datadog_lambda/version.py CHANGED
@@ -1 +1 @@
1
- __version__ = "7.112.0"
1
+ __version__ = "8.113.0"
datadog_lambda/wrapper.py CHANGED
@@ -9,7 +9,7 @@ import ujson as json
9
9
  from importlib import import_module
10
10
  from time import time_ns
11
11
 
12
- from datadog_lambda.asm import asm_set_context, asm_start_response, asm_start_request
12
+ from ddtrace.internal._exceptions import BlockingException
13
13
  from datadog_lambda.extension import should_use_extension, flush_extension
14
14
  from datadog_lambda.cold_start import (
15
15
  set_cold_start,
@@ -46,6 +46,14 @@ from datadog_lambda.trigger import (
46
46
  extract_http_status_code_tag,
47
47
  )
48
48
 
49
+ if config.appsec_enabled:
50
+ from datadog_lambda.asm import (
51
+ asm_set_context,
52
+ asm_start_response,
53
+ asm_start_request,
54
+ get_asm_blocked_response,
55
+ )
56
+
49
57
  if config.profiling_enabled:
50
58
  from ddtrace.profiling import profiler
51
59
 
@@ -120,6 +128,7 @@ class _LambdaDecorator(object):
120
128
  self.span = None
121
129
  self.inferred_span = None
122
130
  self.response = None
131
+ self.blocking_response = None
123
132
 
124
133
  if config.profiling_enabled:
125
134
  self.prof = profiler.Profiler(env=config.env, service=config.service)
@@ -159,8 +168,12 @@ class _LambdaDecorator(object):
159
168
  """Executes when the wrapped function gets called"""
160
169
  self._before(event, context)
161
170
  try:
171
+ if self.blocking_response:
172
+ return self.blocking_response
162
173
  self.response = self.func(event, context, **kwargs)
163
174
  return self.response
175
+ except BlockingException:
176
+ self.blocking_response = get_asm_blocked_response(self.event_source)
164
177
  except Exception:
165
178
  from datadog_lambda.metric import submit_errors_metric
166
179
 
@@ -171,6 +184,8 @@ class _LambdaDecorator(object):
171
184
  raise
172
185
  finally:
173
186
  self._after(event, context)
187
+ if self.blocking_response:
188
+ return self.blocking_response
174
189
 
175
190
  def _inject_authorizer_span_headers(self, request_id):
176
191
  reference_span = self.inferred_span if self.inferred_span else self.span
@@ -203,6 +218,7 @@ class _LambdaDecorator(object):
203
218
  def _before(self, event, context):
204
219
  try:
205
220
  self.response = None
221
+ self.blocking_response = None
206
222
  set_cold_start(init_timestamp_ns)
207
223
 
208
224
  if not should_use_extension:
@@ -253,6 +269,7 @@ class _LambdaDecorator(object):
253
269
  )
254
270
  if config.appsec_enabled:
255
271
  asm_start_request(self.span, event, event_source, self.trigger_tags)
272
+ self.blocking_response = get_asm_blocked_response(self.event_source)
256
273
  else:
257
274
  set_correlation_ids()
258
275
  if config.profiling_enabled and is_new_sandbox():
@@ -286,13 +303,14 @@ class _LambdaDecorator(object):
286
303
  if status_code:
287
304
  self.span.set_tag("http.status_code", status_code)
288
305
 
289
- if config.appsec_enabled:
306
+ if config.appsec_enabled and not self.blocking_response:
290
307
  asm_start_response(
291
308
  self.span,
292
309
  status_code,
293
310
  self.event_source,
294
311
  response=self.response,
295
312
  )
313
+ self.blocking_response = get_asm_blocked_response(self.event_source)
296
314
 
297
315
  self.span.finish()
298
316
 
@@ -300,6 +318,9 @@ class _LambdaDecorator(object):
300
318
  if status_code:
301
319
  self.inferred_span.set_tag("http.status_code", status_code)
302
320
 
321
+ if self.trigger_tags and (route := self.trigger_tags.get("http.route")):
322
+ self.inferred_span.set_tag("http.route", route)
323
+
303
324
  if config.service:
304
325
  self.inferred_span.set_tag("peer.service", config.service)
305
326
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: datadog_lambda
3
- Version: 7.112.0
3
+ Version: 8.113.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.10.2,<4)
22
+ Requires-Dist: ddtrace (>=3.11.0,<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
1
  datadog_lambda/__init__.py,sha256=uAqki08Fd26eUXZtoptJX68kTFhK-ZkCyLVCtE0MS4k,802
2
2
  datadog_lambda/api.py,sha256=FqQ9vjNSxD5BxjeHDDKbim_NIN08dshW-52wNzqw5yo,5249
3
- datadog_lambda/asm.py,sha256=gX4ySlmLCGnOWRf0LpMJxDElpQ2su5LnphJJy4Lrc-Y,6115
3
+ datadog_lambda/asm.py,sha256=pYgtSKtOj8KTd701iPiXvOXQ32D1nH_k1BzOmMU7L1k,7767
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
@@ -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=aalT3t_ISPvPZIZzkrSK6OnW1ixY9ijsefjUQQEKQk0,56071
20
+ datadog_lambda/tracing.py,sha256=A7uUXlWBwfxOa2PFVLg2Tgft8Ft14xz-kSOjvOaA8JQ,57396
21
21
  datadog_lambda/trigger.py,sha256=7EhNoCtJbnuYaqCvv1i2tAqZxfXx7ccsnMkwnWvmd7w,14212
22
- datadog_lambda/version.py,sha256=HDtiDcfxQBWPKo0xjxP1Hvuj0QipaSpVwhvqFrmieB0,24
23
- datadog_lambda/wrapper.py,sha256=siHNfNSLb3iIwN0vRnW67CtnOirqogbFG54zjWPVLAU,13377
22
+ datadog_lambda/version.py,sha256=VIVUGpBGnx4PNj35gsO1Xf4OfZ_Naws0GXp54dMVxj0,24
23
+ datadog_lambda/wrapper.py,sha256=ZCszRfSAbBplOcPJ_RI5XTMSkNBfvgEJYbYEdhlhbwQ,14270
24
24
  datadog_lambda/xray.py,sha256=jvA4Fk76PLMgsjUoUZ7gp2otv53hFt39Nvso1ZNaivg,3749
25
- datadog_lambda-7.112.0.dist-info/LICENSE,sha256=4yQmjpKp1MKL7DdRDPVHkKYc2W0aezm5SIDske8oAdM,11379
26
- datadog_lambda-7.112.0.dist-info/LICENSE-3rdparty.csv,sha256=9CDAR1GKawwTbZkqt1RP0uwEcaRM3RhOeTB5tWXr8Ts,1381
27
- datadog_lambda-7.112.0.dist-info/METADATA,sha256=YzLOAslZUkBtDAPKfgPyYnpbXKUwd92D1zZbctuFBR4,7727
28
- datadog_lambda-7.112.0.dist-info/NOTICE,sha256=Jue-d8mQ1ENIHDZdYc2-X8mVYtScXb8pzF1pTLN-kRc,141
29
- datadog_lambda-7.112.0.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
30
- datadog_lambda-7.112.0.dist-info/RECORD,,
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,,