Skip to content

Commit c7440a5

Browse files
authored
Infer API Gateway spans (#172)
* Infer spans from API Gateway events * Adding some prints. remove later * Change some info on the API Gateway span * Rename something * black * >:( * black * fix time * Support various API Gateway, HTTPAPI, and Websocket events * black * Add DD_INFERRED_SPANS env var to turn inferred spans on and off * infer spans in integration tests * specify which env var to set true in order to enable inferred spans * try setting inferred span name to inferred span URL * s/beta/experimental/ * Correctly create spans in separate services, assuming the extension is running and the tag is not set on the function * Remove function_name * Flush after closing spans * black * black * update snapshots * Make the snapshots valid json * black * Remove the inferredSpansFilter * Refactor inferred-span event type detection to use the trigger event type code * remove unused import * lines too long >=( * Finish refactor using _EventSource object * lol, remove println debugging * Update snapshots
1 parent 0671dc8 commit c7440a5

21 files changed

+587
-77
lines changed

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,17 @@ Initialize the Datadog tracer when set to `true`. Defaults to `false`.
8787

8888
Set to `true` to merge the X-Ray trace and the Datadog trace, when using both the X-Ray and Datadog tracing. Defaults to `false`.
8989

90+
### DD_INFERRED_SPANS (experimental)
91+
92+
Inferred Spans are spans that Datadog can create based on incoming event metadata.
93+
Set `DD_INFERRED_SPANS` to `true` to infer spans based on Lambda events.
94+
Inferring upstream spans is only supported if you are using the [Datadog Lambda Extension](https://docs.datadoghq.com/serverless/libraries_integrations/extension/).
95+
Defaults to `false`.
96+
Infers spans for:
97+
- API Gateway REST events
98+
- API Gateway websocket events
99+
- HTTP API events
100+
90101
## Opening Issues
91102

92103
If you encounter a bug with this package, we want to hear about it. Before opening a new issue, search the existing issues to avoid duplicates.

datadog_lambda/tracing.py

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,14 @@
2121
from ddtrace import __version__ as ddtrace_version
2222
from ddtrace.propagation.http import HTTPPropagator
2323
from datadog_lambda import __version__ as datadog_lambda_version
24+
from datadog_lambda.trigger import parse_event_source, EventTypes, EventSubtypes
2425

2526
logger = logging.getLogger(__name__)
2627

28+
29+
SPAN_TYPE_TAG = "_dd.span_type"
30+
SPAN_TYPE_INFERRED = "inferred"
31+
2732
dd_trace_context = {}
2833
dd_tracing_enabled = os.environ.get("DD_TRACE_ENABLED", "false").lower() == "true"
2934

@@ -377,13 +382,121 @@ def set_dd_trace_py_root(trace_context_source, merge_xray_traces):
377382
)
378383

379384

385+
def create_inferred_span(event, context, function_name):
386+
event_source = parse_event_source(event)
387+
try:
388+
if event_source.equals(
389+
EventTypes.API_GATEWAY, subtype=EventSubtypes.API_GATEWAY
390+
):
391+
logger.debug("API Gateway event detected. Inferring a span")
392+
return create_inferred_span_from_api_gateway_event(event, context)
393+
elif event_source.equals(
394+
EventTypes.API_GATEWAY, subtype=EventSubtypes.HTTP_API
395+
):
396+
logger.debug("HTTP API event detected. Inferring a span")
397+
return create_inferred_span_from_http_api_event(event, context)
398+
elif event_source.equals(
399+
EventTypes.API_GATEWAY, subtype=EventSubtypes.WEBSOCKET
400+
):
401+
logger.debug("API Gateway Websocket event detected. Inferring a span")
402+
return create_inferred_span_from_api_gateway_websocket_event(event, context)
403+
except Exception as e:
404+
logger.debug(
405+
"Unable to infer span. Detected type: {}. Reason: {}",
406+
event_source.to_string(),
407+
e,
408+
)
409+
return None
410+
logger.debug("Unable to infer a span: unknown event type")
411+
return None
412+
413+
414+
def create_inferred_span_from_api_gateway_websocket_event(event, context):
415+
domain = event["requestContext"]["domainName"]
416+
endpoint = event["requestContext"]["routeKey"]
417+
tags = {
418+
"operation_name": "aws.apigateway.websocket",
419+
"service.name": domain,
420+
"http.url": domain + endpoint,
421+
"endpoint": endpoint,
422+
"resource_name": domain + endpoint,
423+
"request_id": context.aws_request_id,
424+
"connection_id": event["requestContext"]["connectionId"],
425+
SPAN_TYPE_TAG: SPAN_TYPE_INFERRED,
426+
}
427+
request_time_epoch = event["requestContext"]["requestTimeEpoch"]
428+
args = {
429+
"resource": domain + endpoint,
430+
"span_type": "web",
431+
}
432+
tracer.set_tags({"_dd.origin": "lambda"})
433+
span = tracer.trace("aws.apigateway.websocket", **args)
434+
if span:
435+
span.set_tags(tags)
436+
span.start = request_time_epoch / 1000
437+
return span
438+
439+
440+
def create_inferred_span_from_api_gateway_event(event, context):
441+
domain = event["requestContext"]["domainName"]
442+
path = event["path"]
443+
tags = {
444+
"operation_name": "aws.apigateway.rest",
445+
"service.name": domain,
446+
"http.url": domain + path,
447+
"endpoint": path,
448+
"http.method": event["httpMethod"],
449+
"resource_name": domain + path,
450+
"request_id": context.aws_request_id,
451+
SPAN_TYPE_TAG: SPAN_TYPE_INFERRED,
452+
}
453+
request_time_epoch = event["requestContext"]["requestTimeEpoch"]
454+
args = {
455+
"resource": domain + path,
456+
"span_type": "http",
457+
}
458+
tracer.set_tags({"_dd.origin": "lambda"})
459+
span = tracer.trace("aws.apigateway", **args)
460+
if span:
461+
span.set_tags(tags)
462+
span.start = request_time_epoch / 1000
463+
return span
464+
465+
466+
def create_inferred_span_from_http_api_event(event, context):
467+
domain = event["requestContext"]["domainName"]
468+
path = event["rawPath"]
469+
tags = {
470+
"operation_name": "aws.httpapi",
471+
"service.name": domain,
472+
"http.url": domain + path,
473+
"endpoint": path,
474+
"http.method": event["requestContext"]["http"]["method"],
475+
"resource_name": domain + path,
476+
"request_id": context.aws_request_id,
477+
SPAN_TYPE_TAG: SPAN_TYPE_INFERRED,
478+
}
479+
request_time_epoch = event["requestContext"]["timeEpoch"]
480+
args = {
481+
"resource": domain + path,
482+
"span_type": "http",
483+
}
484+
tracer.set_tags({"_dd.origin": "lambda"})
485+
span = tracer.trace("aws.httpapi", **args)
486+
if span:
487+
span.set_tags(tags)
488+
span.start = request_time_epoch / 1000
489+
return span
490+
491+
380492
def create_function_execution_span(
381493
context,
382494
function_name,
383495
is_cold_start,
384496
trace_context_source,
385497
merge_xray_traces,
386498
trigger_tags,
499+
upstream=None,
387500
):
388501
tags = {}
389502
if context:
@@ -402,6 +515,7 @@ def create_function_execution_span(
402515
else None,
403516
"datadog_lambda": datadog_lambda_version,
404517
"dd_trace": ddtrace_version,
518+
"span.name": "aws.lambda",
405519
}
406520
if trace_context_source == TraceContextSource.XRAY and merge_xray_traces:
407521
tags["_dd.parent_source"] = trace_context_source
@@ -415,4 +529,6 @@ def create_function_execution_span(
415529
span = tracer.trace("aws.lambda", **args)
416530
if span:
417531
span.set_tags(tags)
532+
if upstream:
533+
span.parent_id = upstream.span_id
418534
return span

0 commit comments

Comments
 (0)