Skip to content

Commit 02cc8c6

Browse files
feat: [SVLS-5677] DynamoDB Stream event span pointers (#522)
1 parent 2b85536 commit 02cc8c6

15 files changed

+799
-211
lines changed

datadog_lambda/span_pointers.py

+71-4
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22
import logging
33
from typing import List
44

5-
from ddtrace._trace.utils_botocore.span_pointers import (
5+
from ddtrace._trace.utils_botocore.span_pointers.dynamodb import (
6+
_aws_dynamodb_item_span_pointer_description,
7+
)
8+
from ddtrace._trace.utils_botocore.span_pointers.s3 import (
69
_aws_s3_object_span_pointer_description,
710
)
811
from ddtrace._trace._span_pointer import _SpanPointerDirection
@@ -21,10 +24,13 @@ def calculate_span_pointers(
2124
if event_source.equals(EventTypes.S3):
2225
return _calculate_s3_span_pointers_for_event(event)
2326

27+
elif event_source.equals(EventTypes.DYNAMODB):
28+
return _calculate_dynamodb_span_pointers_for_event(event)
29+
2430
except Exception as e:
2531
logger.warning(
2632
"failed to calculate span pointers for event: %s",
27-
str(e),
33+
e,
2834
)
2935

3036
return []
@@ -69,7 +75,7 @@ def _calculate_s3_span_pointers_for_object_created_s3_information(
6975
except KeyError as e:
7076
logger.warning(
7177
"missing s3 information required to make a span pointer: %s",
72-
str(e),
78+
e,
7379
)
7480
return []
7581

@@ -86,6 +92,67 @@ def _calculate_s3_span_pointers_for_object_created_s3_information(
8692
except Exception as e:
8793
logger.warning(
8894
"failed to generate S3 span pointer: %s",
89-
str(e),
95+
e,
96+
)
97+
return []
98+
99+
100+
def _calculate_dynamodb_span_pointers_for_event(event) -> List[_SpanPointerDescription]:
101+
# Example event:
102+
# https://docs.aws.amazon.com/lambda/latest/dg/with-ddb.html
103+
104+
return list(
105+
chain.from_iterable(
106+
_calculate_dynamodb_span_pointers_for_event_record(record)
107+
for record in event.get("Records", [])
108+
)
109+
)
110+
111+
112+
def _calculate_dynamodb_span_pointers_for_event_record(
113+
record,
114+
) -> List[_SpanPointerDescription]:
115+
try:
116+
table_name = _extract_table_name_from_dynamodb_stream_record(record)
117+
primary_key = record["dynamodb"]["Keys"]
118+
119+
except Exception as e:
120+
logger.warning(
121+
"missing DynamoDB information required to make a span pointer: %s",
122+
e,
90123
)
91124
return []
125+
126+
try:
127+
return [
128+
_aws_dynamodb_item_span_pointer_description(
129+
pointer_direction=_SpanPointerDirection.UPSTREAM,
130+
table_name=table_name,
131+
primary_key=primary_key,
132+
)
133+
]
134+
135+
except Exception as e:
136+
logger.warning(
137+
"failed to generate DynamoDB span pointer: %s",
138+
e,
139+
)
140+
return []
141+
142+
143+
def _extract_table_name_from_dynamodb_stream_record(record) -> str:
144+
# Example eventSourceARN:
145+
# arn:aws:dynamodb:us-east-2:123456789012:table/my-table/stream/2024-06-10T19:26:16.525
146+
event_source_arn = record["eventSourceARN"]
147+
148+
[_arn, _aws, _dynamodb, _region, _account, dynamodb_info] = event_source_arn.split(
149+
":", maxsplit=5
150+
)
151+
if _arn != "arn" or _aws != "aws" or _dynamodb != "dynamodb":
152+
raise ValueError(f"unexpected eventSourceARN format: {event_source_arn}")
153+
154+
[_table, table_name, _stream, _timestamp] = dynamodb_info.split("/")
155+
if _table != "table" or _stream != "stream":
156+
raise ValueError(f"unexpected eventSourceARN format: {event_source_arn}")
157+
158+
return table_name

poetry.lock

+201-186
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ classifiers = [
2727
python = ">=3.8.0,<4"
2828
datadog = ">=0.41.0,<1.0.0"
2929
wrapt = "^1.11.2"
30-
ddtrace = ">=2.14.1"
30+
ddtrace = ">=2.15.0"
3131
ujson = ">=5.9.0"
3232
boto3 = { version = "^1.34.0", optional = true }
3333
requests = { version ="^2.22.0", optional = true }

tests/Dockerfile

+5
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ FROM python:$python_version
33

44
ENV PYTHONDONTWRITEBYTECODE True
55

6+
# Add Rust compiler which is needed to build dd-trace-py from source
7+
RUN curl https://sh.rustup.rs -sSf | \
8+
sh -s -- --default-toolchain stable -y
9+
ENV PATH=/root/.cargo/bin:$PATH
10+
611
RUN mkdir -p /test/datadog_lambda
712
WORKDIR /test
813

tests/integration/snapshots/logs/async-metrics_python310.log

+46-2
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,39 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A
279279
"metrics": {
280280
"_dd.top_level": 1
281281
},
282-
"type": "serverless"
282+
"type": "serverless",
283+
"span_links": [
284+
{
285+
"trace_id": "XXXX",
286+
"span_id": "XXXX",
287+
"attributes": {
288+
"ptr.kind": "aws.dynamodb.item",
289+
"ptr.dir": "u",
290+
"ptr.hash": "e2af34d333891f765c7f02d2da80895e",
291+
"link.kind": "span-pointer"
292+
}
293+
},
294+
{
295+
"trace_id": "XXXX",
296+
"span_id": "XXXX",
297+
"attributes": {
298+
"ptr.kind": "aws.dynamodb.item",
299+
"ptr.dir": "u",
300+
"ptr.hash": "e2af34d333891f765c7f02d2da80895e",
301+
"link.kind": "span-pointer"
302+
}
303+
},
304+
{
305+
"trace_id": "XXXX",
306+
"span_id": "XXXX",
307+
"attributes": {
308+
"ptr.kind": "aws.dynamodb.item",
309+
"ptr.dir": "u",
310+
"ptr.hash": "e2af34d333891f765c7f02d2da80895e",
311+
"link.kind": "span-pointer"
312+
}
313+
}
314+
]
283315
},
284316
{
285317
"trace_id": "XXXX",
@@ -942,7 +974,19 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A
942974
"metrics": {
943975
"_dd.top_level": 1
944976
},
945-
"type": "serverless"
977+
"type": "serverless",
978+
"span_links": [
979+
{
980+
"trace_id": "XXXX",
981+
"span_id": "XXXX",
982+
"attributes": {
983+
"ptr.kind": "aws.s3.object",
984+
"ptr.dir": "u",
985+
"ptr.hash": "1dc3e5d00dae48c1f07d95371a747788",
986+
"link.kind": "span-pointer"
987+
}
988+
}
989+
]
946990
},
947991
{
948992
"trace_id": "XXXX",

tests/integration/snapshots/logs/async-metrics_python311.log

+46-2
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,39 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A
279279
"metrics": {
280280
"_dd.top_level": 1
281281
},
282-
"type": "serverless"
282+
"type": "serverless",
283+
"span_links": [
284+
{
285+
"trace_id": "XXXX",
286+
"span_id": "XXXX",
287+
"attributes": {
288+
"ptr.kind": "aws.dynamodb.item",
289+
"ptr.dir": "u",
290+
"ptr.hash": "e2af34d333891f765c7f02d2da80895e",
291+
"link.kind": "span-pointer"
292+
}
293+
},
294+
{
295+
"trace_id": "XXXX",
296+
"span_id": "XXXX",
297+
"attributes": {
298+
"ptr.kind": "aws.dynamodb.item",
299+
"ptr.dir": "u",
300+
"ptr.hash": "e2af34d333891f765c7f02d2da80895e",
301+
"link.kind": "span-pointer"
302+
}
303+
},
304+
{
305+
"trace_id": "XXXX",
306+
"span_id": "XXXX",
307+
"attributes": {
308+
"ptr.kind": "aws.dynamodb.item",
309+
"ptr.dir": "u",
310+
"ptr.hash": "e2af34d333891f765c7f02d2da80895e",
311+
"link.kind": "span-pointer"
312+
}
313+
}
314+
]
283315
},
284316
{
285317
"trace_id": "XXXX",
@@ -942,7 +974,19 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A
942974
"metrics": {
943975
"_dd.top_level": 1
944976
},
945-
"type": "serverless"
977+
"type": "serverless",
978+
"span_links": [
979+
{
980+
"trace_id": "XXXX",
981+
"span_id": "XXXX",
982+
"attributes": {
983+
"ptr.kind": "aws.s3.object",
984+
"ptr.dir": "u",
985+
"ptr.hash": "1dc3e5d00dae48c1f07d95371a747788",
986+
"link.kind": "span-pointer"
987+
}
988+
}
989+
]
946990
},
947991
{
948992
"trace_id": "XXXX",

tests/integration/snapshots/logs/async-metrics_python312.log

+46-2
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,39 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A
279279
"metrics": {
280280
"_dd.top_level": 1
281281
},
282-
"type": "serverless"
282+
"type": "serverless",
283+
"span_links": [
284+
{
285+
"trace_id": "XXXX",
286+
"span_id": "XXXX",
287+
"attributes": {
288+
"ptr.kind": "aws.dynamodb.item",
289+
"ptr.dir": "u",
290+
"ptr.hash": "e2af34d333891f765c7f02d2da80895e",
291+
"link.kind": "span-pointer"
292+
}
293+
},
294+
{
295+
"trace_id": "XXXX",
296+
"span_id": "XXXX",
297+
"attributes": {
298+
"ptr.kind": "aws.dynamodb.item",
299+
"ptr.dir": "u",
300+
"ptr.hash": "e2af34d333891f765c7f02d2da80895e",
301+
"link.kind": "span-pointer"
302+
}
303+
},
304+
{
305+
"trace_id": "XXXX",
306+
"span_id": "XXXX",
307+
"attributes": {
308+
"ptr.kind": "aws.dynamodb.item",
309+
"ptr.dir": "u",
310+
"ptr.hash": "e2af34d333891f765c7f02d2da80895e",
311+
"link.kind": "span-pointer"
312+
}
313+
}
314+
]
283315
},
284316
{
285317
"trace_id": "XXXX",
@@ -942,7 +974,19 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A
942974
"metrics": {
943975
"_dd.top_level": 1
944976
},
945-
"type": "serverless"
977+
"type": "serverless",
978+
"span_links": [
979+
{
980+
"trace_id": "XXXX",
981+
"span_id": "XXXX",
982+
"attributes": {
983+
"ptr.kind": "aws.s3.object",
984+
"ptr.dir": "u",
985+
"ptr.hash": "1dc3e5d00dae48c1f07d95371a747788",
986+
"link.kind": "span-pointer"
987+
}
988+
}
989+
]
946990
},
947991
{
948992
"trace_id": "XXXX",

tests/integration/snapshots/logs/async-metrics_python38.log

+46-2
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,39 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A
279279
"metrics": {
280280
"_dd.top_level": 1
281281
},
282-
"type": "serverless"
282+
"type": "serverless",
283+
"span_links": [
284+
{
285+
"trace_id": "XXXX",
286+
"span_id": "XXXX",
287+
"attributes": {
288+
"ptr.kind": "aws.dynamodb.item",
289+
"ptr.dir": "u",
290+
"ptr.hash": "e2af34d333891f765c7f02d2da80895e",
291+
"link.kind": "span-pointer"
292+
}
293+
},
294+
{
295+
"trace_id": "XXXX",
296+
"span_id": "XXXX",
297+
"attributes": {
298+
"ptr.kind": "aws.dynamodb.item",
299+
"ptr.dir": "u",
300+
"ptr.hash": "e2af34d333891f765c7f02d2da80895e",
301+
"link.kind": "span-pointer"
302+
}
303+
},
304+
{
305+
"trace_id": "XXXX",
306+
"span_id": "XXXX",
307+
"attributes": {
308+
"ptr.kind": "aws.dynamodb.item",
309+
"ptr.dir": "u",
310+
"ptr.hash": "e2af34d333891f765c7f02d2da80895e",
311+
"link.kind": "span-pointer"
312+
}
313+
}
314+
]
283315
},
284316
{
285317
"trace_id": "XXXX",
@@ -942,7 +974,19 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A
942974
"metrics": {
943975
"_dd.top_level": 1
944976
},
945-
"type": "serverless"
977+
"type": "serverless",
978+
"span_links": [
979+
{
980+
"trace_id": "XXXX",
981+
"span_id": "XXXX",
982+
"attributes": {
983+
"ptr.kind": "aws.s3.object",
984+
"ptr.dir": "u",
985+
"ptr.hash": "1dc3e5d00dae48c1f07d95371a747788",
986+
"link.kind": "span-pointer"
987+
}
988+
}
989+
]
946990
},
947991
{
948992
"trace_id": "XXXX",

0 commit comments

Comments
 (0)