datadog_lambda 5.92.0__py3-none-any.whl → 5.94.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/tracing.py CHANGED
@@ -5,8 +5,9 @@
5
5
  import hashlib
6
6
  import logging
7
7
  import os
8
- import json
9
8
  import base64
9
+ import traceback
10
+ import ujson as json
10
11
  from datetime import datetime, timezone
11
12
  from typing import Optional, Dict
12
13
 
@@ -66,6 +67,8 @@ if dd_tracing_enabled:
66
67
 
67
68
  telemetry_writer.enable()
68
69
 
70
+ is_lambda_context = os.environ.get(XrayDaemon.FUNCTION_NAME_HEADER_NAME) != ""
71
+
69
72
  propagator = HTTPPropagator()
70
73
 
71
74
  DD_TRACE_JAVA_TRACE_ID_PADDING = "00000000"
@@ -93,7 +96,7 @@ def _convert_xray_sampling(xray_sampled):
93
96
 
94
97
 
95
98
  def _get_xray_trace_context():
96
- if not is_lambda_context():
99
+ if not is_lambda_context:
97
100
  return None
98
101
 
99
102
  xray_trace_entity = parse_xray_header(
@@ -109,11 +112,7 @@ def _get_xray_trace_context():
109
112
  logger.debug(
110
113
  "Converted trace context %s from X-Ray segment %s",
111
114
  trace_context,
112
- (
113
- xray_trace_entity["trace_id"],
114
- xray_trace_entity["parent_id"],
115
- xray_trace_entity["sampled"],
116
- ),
115
+ xray_trace_entity,
117
116
  )
118
117
  return trace_context
119
118
 
@@ -124,7 +123,9 @@ def _get_dd_trace_py_context():
124
123
  return None
125
124
 
126
125
  logger.debug(
127
- "found dd trace context: %s", (span.context.trace_id, span.context.span_id)
126
+ "found dd trace context: trace_id=%s span_id=%s",
127
+ span.context.trace_id,
128
+ span.context.span_id,
128
129
  )
129
130
  return span.context
130
131
 
@@ -235,37 +236,31 @@ def extract_context_from_sqs_or_sns_event_or_context(event, lambda_context):
235
236
 
236
237
  # logic to deal with SNS => SQS event
237
238
  if "body" in first_record:
238
- body_str = first_record.get("body", {})
239
+ body_str = first_record.get("body")
239
240
  try:
240
241
  body = json.loads(body_str)
241
242
  if body.get("Type", "") == "Notification" and "TopicArn" in body:
242
243
  logger.debug("Found SNS message inside SQS event")
243
244
  first_record = get_first_record(create_sns_event(body))
244
245
  except Exception:
245
- first_record = event.get("Records")[0]
246
246
  pass
247
247
 
248
- msg_attributes = first_record.get(
249
- "messageAttributes",
250
- first_record.get("Sns", {}).get("MessageAttributes", {}),
251
- )
252
- dd_payload = msg_attributes.get("_datadog", {})
248
+ msg_attributes = first_record.get("messageAttributes")
249
+ if msg_attributes is None:
250
+ sns_record = first_record.get("Sns") or {}
251
+ msg_attributes = sns_record.get("MessageAttributes") or {}
252
+ dd_payload = msg_attributes.get("_datadog")
253
253
  if dd_payload:
254
254
  # SQS uses dataType and binaryValue/stringValue
255
255
  # SNS uses Type and Value
256
256
  dd_json_data = None
257
- dd_json_data_type = dd_payload.get("Type", dd_payload.get("dataType", ""))
257
+ dd_json_data_type = dd_payload.get("Type") or dd_payload.get("dataType")
258
258
  if dd_json_data_type == "Binary":
259
- dd_json_data = dd_payload.get(
260
- "binaryValue",
261
- dd_payload.get("Value", r"{}"),
262
- )
263
- dd_json_data = base64.b64decode(dd_json_data)
259
+ dd_json_data = dd_payload.get("binaryValue") or dd_payload.get("Value")
260
+ if dd_json_data:
261
+ dd_json_data = base64.b64decode(dd_json_data)
264
262
  elif dd_json_data_type == "String":
265
- dd_json_data = dd_payload.get(
266
- "stringValue",
267
- dd_payload.get("Value", r"{}"),
268
- )
263
+ dd_json_data = dd_payload.get("stringValue") or dd_payload.get("Value")
269
264
  else:
270
265
  logger.debug(
271
266
  "Datadog Lambda Python only supports extracting trace"
@@ -278,23 +273,25 @@ def extract_context_from_sqs_or_sns_event_or_context(event, lambda_context):
278
273
  else:
279
274
  # Handle case where trace context is injected into attributes.AWSTraceHeader
280
275
  # example: Root=1-654321ab-000000001234567890abcdef;Parent=0123456789abcdef;Sampled=1
281
- x_ray_header = first_record.get("attributes", {}).get("AWSTraceHeader")
282
- if x_ray_header:
283
- x_ray_context = parse_xray_header(x_ray_header)
284
- trace_id_parts = x_ray_context.get("trace_id", "").split("-")
285
- if len(trace_id_parts) > 2 and trace_id_parts[2].startswith(
286
- DD_TRACE_JAVA_TRACE_ID_PADDING
287
- ):
288
- # If it starts with eight 0's padding,
289
- # then this AWSTraceHeader contains Datadog injected trace context
290
- logger.debug(
291
- "Found dd-trace injected trace context from AWSTraceHeader"
292
- )
293
- return Context(
294
- trace_id=int(trace_id_parts[2][8:], 16),
295
- span_id=int(int(x_ray_context["parent_id"], 16)),
296
- sampling_priority=float(x_ray_context["sampled"]),
297
- )
276
+ attrs = first_record.get("attributes")
277
+ if attrs:
278
+ x_ray_header = attrs.get("AWSTraceHeader")
279
+ if x_ray_header:
280
+ x_ray_context = parse_xray_header(x_ray_header)
281
+ trace_id_parts = x_ray_context.get("trace_id", "").split("-")
282
+ if len(trace_id_parts) > 2 and trace_id_parts[2].startswith(
283
+ DD_TRACE_JAVA_TRACE_ID_PADDING
284
+ ):
285
+ # If it starts with eight 0's padding,
286
+ # then this AWSTraceHeader contains Datadog injected trace context
287
+ logger.debug(
288
+ "Found dd-trace injected trace context from AWSTraceHeader"
289
+ )
290
+ return Context(
291
+ trace_id=int(trace_id_parts[2][8:], 16),
292
+ span_id=int(x_ray_context["parent_id"], 16),
293
+ sampling_priority=float(x_ray_context["sampled"]),
294
+ )
298
295
  return extract_context_from_lambda_context(lambda_context)
299
296
  except Exception as e:
300
297
  logger.debug("The trace extractor returned with error %s", e)
@@ -339,21 +336,22 @@ def extract_context_from_kinesis_event(event, lambda_context):
339
336
  """
340
337
  try:
341
338
  record = get_first_record(event)
342
- data = record.get("kinesis", {}).get("data", None)
339
+ kinesis = record.get("kinesis")
340
+ if not kinesis:
341
+ return extract_context_from_lambda_context(lambda_context)
342
+ data = kinesis.get("data")
343
343
  if data:
344
344
  b64_bytes = data.encode("ascii")
345
345
  str_bytes = base64.b64decode(b64_bytes)
346
346
  data_str = str_bytes.decode("ascii")
347
347
  data_obj = json.loads(data_str)
348
348
  dd_ctx = data_obj.get("_datadog")
349
-
350
- if not dd_ctx:
351
- return extract_context_from_lambda_context(lambda_context)
352
-
353
- return propagator.extract(dd_ctx)
349
+ if dd_ctx:
350
+ return propagator.extract(dd_ctx)
354
351
  except Exception as e:
355
352
  logger.debug("The trace extractor returned with error %s", e)
356
- return extract_context_from_lambda_context(lambda_context)
353
+
354
+ return extract_context_from_lambda_context(lambda_context)
357
355
 
358
356
 
359
357
  def _deterministic_md5_hash(s: str) -> int:
@@ -380,7 +378,7 @@ def extract_context_from_step_functions(event, lambda_context):
380
378
  state_entered_time = event.get("State").get("EnteredTime")
381
379
  trace_id = _deterministic_md5_hash(execution_id)
382
380
  parent_id = _deterministic_md5_hash(
383
- execution_id + "#" + state_name + "#" + state_entered_time
381
+ f"{execution_id}#{state_name}#{state_entered_time}"
384
382
  )
385
383
  sampling_priority = SamplingPriority.AUTO_KEEP
386
384
  return Context(
@@ -396,11 +394,7 @@ def extract_context_custom_extractor(extractor, event, lambda_context):
396
394
  Extract Datadog trace context using a custom trace extractor function
397
395
  """
398
396
  try:
399
- (
400
- trace_id,
401
- parent_id,
402
- sampling_priority,
403
- ) = extractor(event, lambda_context)
397
+ trace_id, parent_id, sampling_priority = extractor(event, lambda_context)
404
398
  return Context(
405
399
  trace_id=int(trace_id),
406
400
  span_id=int(parent_id),
@@ -426,15 +420,20 @@ def is_authorizer_response(response) -> bool:
426
420
 
427
421
  def get_injected_authorizer_data(event, is_http_api) -> dict:
428
422
  try:
429
- authorizer_headers = event.get("requestContext", {}).get("authorizer")
423
+ req_ctx = event.get("requestContext")
424
+ if not req_ctx:
425
+ return None
426
+ authorizer_headers = req_ctx.get("authorizer")
430
427
  if not authorizer_headers:
431
428
  return None
432
429
 
433
- dd_data_raw = (
434
- authorizer_headers.get("lambda", {}).get("_datadog")
435
- if is_http_api
436
- else authorizer_headers.get("_datadog")
437
- )
430
+ if is_http_api:
431
+ lambda_hdr = authorizer_headers.get("lambda")
432
+ if not lambda_hdr:
433
+ return None
434
+ dd_data_raw = lambda_hdr.get("_datadog")
435
+ else:
436
+ dd_data_raw = authorizer_headers.get("_datadog")
438
437
 
439
438
  if not dd_data_raw:
440
439
  return None
@@ -448,16 +447,19 @@ def get_injected_authorizer_data(event, is_http_api) -> dict:
448
447
  # that case, we use the injected Authorizing_Request_Id to tell if it's cached. But token
449
448
  # authorizers don't pass on the requestId. The Authorizing_Request_Id can't work for all
450
449
  # cases neither. As a result, we combine both methods as shown below.
451
- if authorizer_headers.get("integrationLatency", 0) > 0 or event.get(
452
- "requestContext", {}
453
- ).get("requestId") == injected_data.get(Headers.Authorizing_Request_Id):
450
+ if authorizer_headers.get("integrationLatency", 0) > 0:
454
451
  return injected_data
455
- else:
452
+ req_ctx = event.get("requestContext")
453
+ if not req_ctx:
456
454
  return None
455
+ if req_ctx.get("requestId") == injected_data.get(
456
+ Headers.Authorizing_Request_Id
457
+ ):
458
+ return injected_data
459
+ return None
457
460
 
458
461
  except Exception as e:
459
462
  logger.debug("Failed to check if invocated by an authorizer. error %s", e)
460
- return None
461
463
 
462
464
 
463
465
  def extract_dd_trace_context(
@@ -529,8 +531,8 @@ def get_dd_trace_context_obj():
529
531
  xray_context = _get_xray_trace_context() # xray (sub)segment
530
532
  except Exception as e:
531
533
  logger.debug(
532
- "get_dd_trace_context couldn't read from segment from x-ray, with error %s"
533
- % e
534
+ "get_dd_trace_context couldn't read from segment from x-ray, with error %s",
535
+ e,
534
536
  )
535
537
  if not xray_context:
536
538
  return None
@@ -569,7 +571,7 @@ def set_correlation_ids():
569
571
 
570
572
  TODO: Remove me when Datadog tracer is natively supported in Lambda.
571
573
  """
572
- if not is_lambda_context():
574
+ if not is_lambda_context:
573
575
  logger.debug("set_correlation_ids is only supported in LambdaContext")
574
576
  return
575
577
  if dd_tracing_enabled:
@@ -613,14 +615,6 @@ def inject_correlation_ids():
613
615
  logger.debug("logs injection configured")
614
616
 
615
617
 
616
- def is_lambda_context():
617
- """
618
- Return True if the X-Ray context is `LambdaContext`, rather than the
619
- regular `Context` (e.g., when testing lambda functions locally).
620
- """
621
- return os.environ.get(XrayDaemon.FUNCTION_NAME_HEADER_NAME, "") != ""
622
-
623
-
624
618
  def set_dd_trace_py_root(trace_context_source, merge_xray_traces):
625
619
  if trace_context_source == TraceContextSource.EVENT or merge_xray_traces:
626
620
  context = Context(
@@ -635,8 +629,9 @@ def set_dd_trace_py_root(trace_context_source, merge_xray_traces):
635
629
 
636
630
  tracer.context_provider.activate(context)
637
631
  logger.debug(
638
- "Set dd trace root context to: %s",
639
- (context.trace_id, context.span_id),
632
+ "Set dd trace root context to: trace_id=%s span_id=%s",
633
+ context.trace_id,
634
+ context.span_id,
640
635
  )
641
636
 
642
637
 
@@ -697,9 +692,7 @@ def create_inferred_span(
697
692
  event_source.to_string(),
698
693
  e,
699
694
  )
700
- return None
701
695
  logger.debug("Unable to infer a span: unknown event type")
702
- return None
703
696
 
704
697
 
705
698
  def create_service_mapping(val):
@@ -721,20 +714,22 @@ def determine_service_name(service_mapping, specific_key, generic_key, default_v
721
714
  return service_name
722
715
 
723
716
 
724
- service_mapping = {}
725
717
  # Initialization code
726
718
  service_mapping_str = os.getenv("DD_SERVICE_MAPPING", "")
727
719
  service_mapping = create_service_mapping(service_mapping_str)
728
720
 
721
+ _dd_origin = {"_dd.origin": "lambda"}
722
+
729
723
 
730
724
  def create_inferred_span_from_lambda_function_url_event(event, context):
731
725
  request_context = event.get("requestContext")
732
726
  api_id = request_context.get("apiId")
733
727
  domain = request_context.get("domainName")
734
728
  service_name = determine_service_name(service_mapping, api_id, "lambda_url", domain)
735
- method = request_context.get("http", {}).get("method")
736
- path = request_context.get("http", {}).get("path")
737
- resource = "{0} {1}".format(method, path)
729
+ http = request_context.get("http")
730
+ method = http.get("method") if http else None
731
+ path = http.get("path") if http else None
732
+ resource = f"{method} {path}"
738
733
  tags = {
739
734
  "operation_name": "aws.lambda.url",
740
735
  "http.url": domain + path,
@@ -744,25 +739,23 @@ def create_inferred_span_from_lambda_function_url_event(event, context):
744
739
  "request_id": context.aws_request_id,
745
740
  }
746
741
  request_time_epoch = request_context.get("timeEpoch")
747
- args = {
748
- "service": service_name,
749
- "resource": resource,
750
- "span_type": "http",
751
- }
752
- tracer.set_tags(
753
- {"_dd.origin": "lambda"}
754
- ) # function urls don't count as lambda_inferred,
742
+ tracer.set_tags(_dd_origin) # function urls don't count as lambda_inferred,
755
743
  # because they're in the same service as the inferring lambda function
756
- span = tracer.trace("aws.lambda.url", **args)
744
+ span = tracer.trace(
745
+ "aws.lambda.url", service=service_name, resource=resource, span_type="http"
746
+ )
757
747
  InferredSpanInfo.set_tags(tags, tag_source="self", synchronicity="sync")
758
748
  if span:
759
749
  span.set_tags(tags)
760
- span.start = request_time_epoch / 1000
750
+ span.start_ns = int(request_time_epoch) * 1e6
761
751
  return span
762
752
 
763
753
 
764
754
  def is_api_gateway_invocation_async(event):
765
- return event.get("headers", {}).get("X-Amz-Invocation-Type") == "Event"
755
+ hdrs = event.get("headers")
756
+ if not hdrs:
757
+ return False
758
+ return hdrs.get("X-Amz-Invocation-Type") == "Event"
766
759
 
767
760
 
768
761
  def insert_upstream_authorizer_span(
@@ -862,7 +855,7 @@ def create_inferred_span_from_api_gateway_websocket_event(
862
855
  "resource": endpoint,
863
856
  "span_type": "web",
864
857
  }
865
- tracer.set_tags({"_dd.origin": "lambda"})
858
+ tracer.set_tags(_dd_origin)
866
859
  upstream_authorizer_span = None
867
860
  finish_time_ns = None
868
861
  if decode_authorizer_context:
@@ -893,7 +886,8 @@ def create_inferred_span_from_api_gateway_event(
893
886
  )
894
887
  method = event.get("httpMethod")
895
888
  path = event.get("path")
896
- resource = "{0} {1}".format(method, path)
889
+ resource_path = _get_resource_path(event, request_context)
890
+ resource = f"{method} {resource_path}"
897
891
  tags = {
898
892
  "operation_name": "aws.apigateway.rest",
899
893
  "http.url": domain + path,
@@ -915,7 +909,7 @@ def create_inferred_span_from_api_gateway_event(
915
909
  "resource": resource,
916
910
  "span_type": "http",
917
911
  }
918
- tracer.set_tags({"_dd.origin": "lambda"})
912
+ tracer.set_tags(_dd_origin)
919
913
  upstream_authorizer_span = None
920
914
  finish_time_ns = None
921
915
  if decode_authorizer_context:
@@ -936,6 +930,16 @@ def create_inferred_span_from_api_gateway_event(
936
930
  return span
937
931
 
938
932
 
933
+ def _get_resource_path(event, request_context):
934
+ route_key = request_context.get("routeKey") or ""
935
+ if "{" in route_key:
936
+ try:
937
+ return route_key.split(" ")[1]
938
+ except Exception as e:
939
+ logger.debug("Error parsing routeKey: %s", e)
940
+ return event.get("rawPath") or request_context.get("resourcePath") or route_key
941
+
942
+
939
943
  def create_inferred_span_from_http_api_event(
940
944
  event, context, decode_authorizer_context: bool = True
941
945
  ):
@@ -945,17 +949,19 @@ def create_inferred_span_from_http_api_event(
945
949
  service_name = determine_service_name(
946
950
  service_mapping, api_id, "lambda_api_gateway", domain
947
951
  )
948
- method = request_context.get("http", {}).get("method")
952
+ http = request_context.get("http") or {}
953
+ method = http.get("method")
949
954
  path = event.get("rawPath")
950
- resource = "{0} {1}".format(method, path)
955
+ resource_path = _get_resource_path(event, request_context)
956
+ resource = f"{method} {resource_path}"
951
957
  tags = {
952
958
  "operation_name": "aws.httpapi",
953
959
  "endpoint": path,
954
960
  "http.url": domain + path,
955
- "http.method": request_context.get("http", {}).get("method"),
956
- "http.protocol": request_context.get("http", {}).get("protocol"),
957
- "http.source_ip": request_context.get("http", {}).get("sourceIp"),
958
- "http.user_agent": request_context.get("http", {}).get("userAgent"),
961
+ "http.method": http.get("method"),
962
+ "http.protocol": http.get("protocol"),
963
+ "http.source_ip": http.get("sourceIp"),
964
+ "http.user_agent": http.get("userAgent"),
959
965
  "resource_names": resource,
960
966
  "request_id": context.aws_request_id,
961
967
  "apiid": api_id,
@@ -967,12 +973,7 @@ def create_inferred_span_from_http_api_event(
967
973
  InferredSpanInfo.set_tags(tags, tag_source="self", synchronicity="async")
968
974
  else:
969
975
  InferredSpanInfo.set_tags(tags, tag_source="self", synchronicity="sync")
970
- args = {
971
- "service": service_name,
972
- "resource": resource,
973
- "span_type": "http",
974
- }
975
- tracer.set_tags({"_dd.origin": "lambda"})
976
+ tracer.set_tags(_dd_origin)
976
977
  inferred_span_start_ns = request_time_epoch_ms * 1e6
977
978
  if decode_authorizer_context:
978
979
  injected_authorizer_data = get_injected_authorizer_data(event, True)
@@ -980,7 +981,9 @@ def create_inferred_span_from_http_api_event(
980
981
  inferred_span_start_ns = injected_authorizer_data.get(
981
982
  Headers.Parent_Span_Finish_Time
982
983
  )
983
- span = tracer.trace("aws.httpapi", **args)
984
+ span = tracer.trace(
985
+ "aws.httpapi", service=service_name, resource=resource, span_type="http"
986
+ )
984
987
  if span:
985
988
  span.set_tags(tags)
986
989
  span.start_ns = int(inferred_span_start_ns)
@@ -996,21 +999,17 @@ def create_inferred_span_from_sqs_event(event, context):
996
999
  service_name = determine_service_name(
997
1000
  service_mapping, queue_name, "lambda_sqs", "sqs"
998
1001
  )
1002
+ attrs = event_record.get("attributes") or {}
999
1003
  tags = {
1000
1004
  "operation_name": "aws.sqs",
1001
1005
  "resource_names": queue_name,
1002
1006
  "queuename": queue_name,
1003
1007
  "event_source_arn": event_source_arn,
1004
1008
  "receipt_handle": event_record.get("receiptHandle"),
1005
- "sender_id": event_record.get("attributes", {}).get("SenderId"),
1009
+ "sender_id": attrs.get("SenderId"),
1006
1010
  }
1007
1011
  InferredSpanInfo.set_tags(tags, tag_source="self", synchronicity="async")
1008
- request_time_epoch = event_record.get("attributes", {}).get("SentTimestamp")
1009
- args = {
1010
- "service": service_name,
1011
- "resource": queue_name,
1012
- "span_type": "web",
1013
- }
1012
+ request_time_epoch = attrs.get("SentTimestamp")
1014
1013
  start_time = int(request_time_epoch) / 1000
1015
1014
 
1016
1015
  upstream_span = None
@@ -1039,15 +1038,17 @@ def create_inferred_span_from_sqs_event(event, context):
1039
1038
 
1040
1039
  except Exception as e:
1041
1040
  logger.debug(
1042
- "Unable to create upstream span from SQS message, with error %s" % e
1041
+ "Unable to create upstream span from SQS message, with error %s", e
1043
1042
  )
1044
1043
  pass
1045
1044
 
1046
1045
  # trace context needs to be set again as it is reset
1047
1046
  # when sns_span.finish executes
1048
1047
  tracer.context_provider.activate(trace_ctx)
1049
- tracer.set_tags({"_dd.origin": "lambda"})
1050
- span = tracer.trace("aws.sqs", **args)
1048
+ tracer.set_tags(_dd_origin)
1049
+ span = tracer.trace(
1050
+ "aws.sqs", service=service_name, resource=queue_name, span_type="web"
1051
+ )
1051
1052
  if span:
1052
1053
  span.set_tags(tags)
1053
1054
  span.start = start_time
@@ -1059,8 +1060,8 @@ def create_inferred_span_from_sqs_event(event, context):
1059
1060
 
1060
1061
  def create_inferred_span_from_sns_event(event, context):
1061
1062
  event_record = get_first_record(event)
1062
- sns_message = event_record.get("Sns")
1063
- topic_arn = event_record.get("Sns", {}).get("TopicArn")
1063
+ sns_message = event_record.get("Sns") or {}
1064
+ topic_arn = sns_message.get("TopicArn")
1064
1065
  topic_name = topic_arn.split(":")[-1]
1065
1066
  service_name = determine_service_name(
1066
1067
  service_mapping, topic_name, "lambda_sns", "sns"
@@ -1075,21 +1076,19 @@ def create_inferred_span_from_sns_event(event, context):
1075
1076
  }
1076
1077
 
1077
1078
  # Subject not available in SNS => SQS scenario
1078
- if "Subject" in sns_message and sns_message["Subject"]:
1079
- tags["subject"] = sns_message.get("Subject")
1079
+ subject = sns_message.get("Subject")
1080
+ if subject:
1081
+ tags["subject"] = subject
1080
1082
 
1081
1083
  InferredSpanInfo.set_tags(tags, tag_source="self", synchronicity="async")
1082
1084
  sns_dt_format = "%Y-%m-%dT%H:%M:%S.%fZ"
1083
- timestamp = event_record.get("Sns", {}).get("Timestamp")
1085
+ timestamp = sns_message.get("Timestamp")
1084
1086
  dt = datetime.strptime(timestamp, sns_dt_format)
1085
1087
 
1086
- args = {
1087
- "service": service_name,
1088
- "resource": topic_name,
1089
- "span_type": "web",
1090
- }
1091
- tracer.set_tags({"_dd.origin": "lambda"})
1092
- span = tracer.trace("aws.sns", **args)
1088
+ tracer.set_tags(_dd_origin)
1089
+ span = tracer.trace(
1090
+ "aws.sns", service=service_name, resource=topic_name, span_type="web"
1091
+ )
1093
1092
  if span:
1094
1093
  span.set_tags(tags)
1095
1094
  span.start = dt.replace(tzinfo=timezone.utc).timestamp()
@@ -1105,6 +1104,7 @@ def create_inferred_span_from_kinesis_event(event, context):
1105
1104
  service_name = determine_service_name(
1106
1105
  service_mapping, stream_name, "lambda_kinesis", "kinesis"
1107
1106
  )
1107
+ kinesis = event_record.get("kinesis") or {}
1108
1108
  tags = {
1109
1109
  "operation_name": "aws.kinesis",
1110
1110
  "resource_names": stream_name,
@@ -1114,20 +1114,15 @@ def create_inferred_span_from_kinesis_event(event, context):
1114
1114
  "event_id": event_id,
1115
1115
  "event_name": event_record.get("eventName"),
1116
1116
  "event_version": event_record.get("eventVersion"),
1117
- "partition_key": event_record.get("kinesis", {}).get("partitionKey"),
1117
+ "partition_key": kinesis.get("partitionKey"),
1118
1118
  }
1119
1119
  InferredSpanInfo.set_tags(tags, tag_source="self", synchronicity="async")
1120
- request_time_epoch = event_record.get("kinesis", {}).get(
1121
- "approximateArrivalTimestamp"
1122
- )
1120
+ request_time_epoch = kinesis.get("approximateArrivalTimestamp")
1123
1121
 
1124
- args = {
1125
- "service": service_name,
1126
- "resource": stream_name,
1127
- "span_type": "web",
1128
- }
1129
- tracer.set_tags({"_dd.origin": "lambda"})
1130
- span = tracer.trace("aws.kinesis", **args)
1122
+ tracer.set_tags(_dd_origin)
1123
+ span = tracer.trace(
1124
+ "aws.kinesis", service=service_name, resource=stream_name, span_type="web"
1125
+ )
1131
1126
  if span:
1132
1127
  span.set_tags(tags)
1133
1128
  span.start = request_time_epoch
@@ -1141,7 +1136,7 @@ def create_inferred_span_from_dynamodb_event(event, context):
1141
1136
  service_name = determine_service_name(
1142
1137
  service_mapping, table_name, "lambda_dynamodb", "dynamodb"
1143
1138
  )
1144
- dynamodb_message = event_record.get("dynamodb")
1139
+ dynamodb_message = event_record.get("dynamodb") or {}
1145
1140
  tags = {
1146
1141
  "operation_name": "aws.dynamodb",
1147
1142
  "resource_names": table_name,
@@ -1154,16 +1149,11 @@ def create_inferred_span_from_dynamodb_event(event, context):
1154
1149
  "size_bytes": str(dynamodb_message.get("SizeBytes")),
1155
1150
  }
1156
1151
  InferredSpanInfo.set_tags(tags, synchronicity="async", tag_source="self")
1157
- request_time_epoch = event_record.get("dynamodb", {}).get(
1158
- "ApproximateCreationDateTime"
1152
+ request_time_epoch = dynamodb_message.get("ApproximateCreationDateTime")
1153
+ tracer.set_tags(_dd_origin)
1154
+ span = tracer.trace(
1155
+ "aws.dynamodb", service=service_name, resource=table_name, span_type="web"
1159
1156
  )
1160
- args = {
1161
- "service": service_name,
1162
- "resource": table_name,
1163
- "span_type": "web",
1164
- }
1165
- tracer.set_tags({"_dd.origin": "lambda"})
1166
- span = tracer.trace("aws.dynamodb", **args)
1167
1157
  if span:
1168
1158
  span.set_tags(tags)
1169
1159
 
@@ -1173,7 +1163,10 @@ def create_inferred_span_from_dynamodb_event(event, context):
1173
1163
 
1174
1164
  def create_inferred_span_from_s3_event(event, context):
1175
1165
  event_record = get_first_record(event)
1176
- bucket_name = event_record.get("s3", {}).get("bucket", {}).get("name")
1166
+ s3 = event_record.get("s3") or {}
1167
+ bucket = s3.get("bucket") or {}
1168
+ obj = s3.get("object") or {}
1169
+ bucket_name = bucket.get("name")
1177
1170
  service_name = determine_service_name(
1178
1171
  service_mapping, bucket_name, "lambda_s3", "s3"
1179
1172
  )
@@ -1182,23 +1175,20 @@ def create_inferred_span_from_s3_event(event, context):
1182
1175
  "resource_names": bucket_name,
1183
1176
  "event_name": event_record.get("eventName"),
1184
1177
  "bucketname": bucket_name,
1185
- "bucket_arn": event_record.get("s3", {}).get("bucket", {}).get("arn"),
1186
- "object_key": event_record.get("s3", {}).get("object", {}).get("key"),
1187
- "object_size": str(event_record.get("s3", {}).get("object", {}).get("size")),
1188
- "object_etag": event_record.get("s3", {}).get("object", {}).get("eTag"),
1178
+ "bucket_arn": bucket.get("arn"),
1179
+ "object_key": obj.get("key"),
1180
+ "object_size": str(obj.get("size")),
1181
+ "object_etag": obj.get("eTag"),
1189
1182
  }
1190
1183
  InferredSpanInfo.set_tags(tags, synchronicity="async", tag_source="self")
1191
1184
  dt_format = "%Y-%m-%dT%H:%M:%S.%fZ"
1192
1185
  timestamp = event_record.get("eventTime")
1193
1186
  dt = datetime.strptime(timestamp, dt_format)
1194
1187
 
1195
- args = {
1196
- "service": service_name,
1197
- "resource": bucket_name,
1198
- "span_type": "web",
1199
- }
1200
- tracer.set_tags({"_dd.origin": "lambda"})
1201
- span = tracer.trace("aws.s3", **args)
1188
+ tracer.set_tags(_dd_origin)
1189
+ span = tracer.trace(
1190
+ "aws.s3", service=service_name, resource=bucket_name, span_type="web"
1191
+ )
1202
1192
  if span:
1203
1193
  span.set_tags(tags)
1204
1194
  span.start = dt.replace(tzinfo=timezone.utc).timestamp()
@@ -1224,13 +1214,10 @@ def create_inferred_span_from_eventbridge_event(event, context):
1224
1214
  timestamp = event.get("time")
1225
1215
  dt = datetime.strptime(timestamp, dt_format)
1226
1216
 
1227
- args = {
1228
- "service": service_name,
1229
- "resource": source,
1230
- "span_type": "web",
1231
- }
1232
- tracer.set_tags({"_dd.origin": "lambda"})
1233
- span = tracer.trace("aws.eventbridge", **args)
1217
+ tracer.set_tags(_dd_origin)
1218
+ span = tracer.trace(
1219
+ "aws.eventbridge", service=service_name, resource=source, span_type="web"
1220
+ )
1234
1221
  if span:
1235
1222
  span.set_tags(tags)
1236
1223
  span.start = dt.replace(tzinfo=timezone.utc).timestamp()
@@ -1247,7 +1234,7 @@ def create_function_execution_span(
1247
1234
  trigger_tags,
1248
1235
  parent_span=None,
1249
1236
  ):
1250
- tags = {}
1237
+ tags = None
1251
1238
  if context:
1252
1239
  function_arn = (context.invoked_function_arn or "").lower()
1253
1240
  tk = function_arn.split(":")
@@ -1266,18 +1253,19 @@ def create_function_execution_span(
1266
1253
  "dd_trace": ddtrace_version,
1267
1254
  "span.name": "aws.lambda",
1268
1255
  }
1256
+ tags = tags or {}
1269
1257
  if is_proactive_init:
1270
1258
  tags["proactive_initialization"] = str(is_proactive_init).lower()
1271
1259
  if trace_context_source == TraceContextSource.XRAY and merge_xray_traces:
1272
1260
  tags["_dd.parent_source"] = trace_context_source
1273
1261
  tags.update(trigger_tags)
1274
- args = {
1275
- "service": "aws.lambda",
1276
- "resource": function_name,
1277
- "span_type": "serverless",
1278
- }
1279
- tracer.set_tags({"_dd.origin": "lambda"})
1280
- span = tracer.trace("aws.lambda", **args)
1262
+ tracer.set_tags(_dd_origin)
1263
+ span = tracer.trace(
1264
+ "aws.lambda",
1265
+ service="aws.lambda",
1266
+ resource=function_name,
1267
+ span_type="serverless",
1268
+ )
1281
1269
  if span:
1282
1270
  span.set_tags(tags)
1283
1271
  if parent_span:
@@ -1333,3 +1321,34 @@ class InferredSpanInfo(object):
1333
1321
  e,
1334
1322
  )
1335
1323
  return False
1324
+
1325
+
1326
+ def emit_telemetry_on_exception_outside_of_handler(
1327
+ exception, resource_name, handler_load_start_time_ns
1328
+ ):
1329
+ """
1330
+ Emit an enhanced error metric and create a span for exceptions occurring outside the handler
1331
+ """
1332
+ submit_errors_metric(None)
1333
+ if dd_tracing_enabled:
1334
+ span = tracer.trace(
1335
+ "aws.lambda",
1336
+ service="aws.lambda",
1337
+ resource=resource_name,
1338
+ span_type="serverless",
1339
+ )
1340
+ span.start_ns = handler_load_start_time_ns
1341
+
1342
+ tags = {
1343
+ "error.status": 500,
1344
+ "error.type": type(exception).__name__,
1345
+ "error.message": exception,
1346
+ "error.stack": traceback.format_exc(),
1347
+ "resource_names": resource_name,
1348
+ "resource.name": resource_name,
1349
+ "operation_name": "aws.lambda",
1350
+ "status": "error",
1351
+ }
1352
+ span.set_tags(tags)
1353
+ span.error = 1
1354
+ span.finish()