Skip to content

Commit df70de2

Browse files
expose DD_CAPTURE_LAMBDA_PAYLOAD_MAX_DEPTH [SVLS-3853] (#387)
* expose DD_CAPTURE_LAMBDA_PAYLOAD_MAX_DEPTH * Update README.md Co-authored-by: Brett Blue <[email protected]> * improve readme.md --------- Co-authored-by: Brett Blue <[email protected]>
1 parent 1dc0502 commit df70de2

File tree

6 files changed

+86
-27
lines changed

6 files changed

+86
-27
lines changed

README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,21 @@ Follow the [installation instructions](https://docs.datadoghq.com/serverless/ins
1616

1717
Follow the [configuration instructions](https://docs.datadoghq.com/serverless/configuration) to tag your telemetry, capture request/response payloads, filter or scrub sensitive information from logs or traces, and more.
1818

19+
For additional tracing configuration options, check out the [official documentation for Datadog trace client](https://ddtrace.readthedocs.io/en/stable/configuration.html).
20+
21+
Besides the environment variables supported by dd-trace-py, the datadog-lambda-python library added following environment variables.
22+
23+
| Environment Variables | Description | Default Value |
24+
| -------------------- | ------------ | ------------- |
25+
| DD_ENCODE_AUTHORIZER_CONTEXT | When set to `true` for Lambda authorizers, the tracing context will be encoded into the response for propagation. Supported for NodeJS and Python. | `true` |
26+
| DD_DECODE_AUTHORIZER_CONTEXT | When set to `true` for Lambdas that are authorized via Lambda authorizers, it will parse and use the encoded tracing context (if found). Supported for NodeJS and Python. | `true` |
27+
| DD_COLD_START_TRACING | Set to `false` to disable Cold Start Tracing. Used in NodeJS and Python. | `true` |
28+
| DD_MIN_COLD_START_DURATION | Sets the minimum duration (in milliseconds) for a module load event to be traced via Cold Start Tracing. Number. | `3` |
29+
| DD_COLD_START_TRACE_SKIP_LIB | optionally skip creating Cold Start Spans for a comma-separated list of libraries. Useful to limit depth or skip known libraries. | `ddtrace.internal.compat,ddtrace.filters` |
30+
| DD_CAPTURE_LAMBDA_PAYLOAD | [Captures incoming and outgoing AWS Lambda payloads][1] in the Datadog APM spans for Lambda invocations. | `false` |
31+
| DD_CAPTURE_LAMBDA_PAYLOAD_MAX_DEPTH | Determines the level of detail captured from AWS Lambda payloads, which are then assigned as tags for the `aws.lambda` span. It specifies the nesting depth of the JSON payload structure to process. Once the specified maximum depth is reached, the tag's value is set to the stringified value of any nested elements beyond this level. <br> For example, given the input payload: <pre>{<br> "lv1" : {<br> "lv2": {<br> "lv3": "val"<br> }<br> }<br>}</pre> If the depth is set to `2`, the resulting tag's key is set to `function.request.lv1.lv2` and the value is `{\"lv3\": \"val\"}`. <br> If the depth is set to `0`, the resulting tag's key is set to `function.request` and value is `{\"lv1\":{\"lv2\":{\"lv3\": \"val\"}}}` | `10` |
32+
33+
1934
## Opening Issues
2035

2136
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.
@@ -51,3 +66,5 @@ For product feedback and questions, join the `#serverless` channel in the [Datad
5166
Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
5267

5368
This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2019 Datadog, Inc.
69+
70+
[1]: https://www.datadoghq.com/blog/troubleshoot-lambda-function-request-response-payloads/

datadog_lambda/tag_object.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,11 @@
1313

1414

1515
def tag_object(span, key, obj, depth=0):
16-
if depth >= max_depth:
17-
return
18-
else:
19-
depth += 1
2016
if obj is None:
2117
return span.set_tag(key, obj)
18+
if depth >= max_depth:
19+
return tag_object(span, key, _redact_val(key, str(obj)[0:5000]))
20+
depth += 1
2221
if _should_try_string(obj):
2322
parsed = None
2423
try:

datadog_lambda/wrapper.py

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -49,18 +49,13 @@
4949
extract_trigger_tags,
5050
extract_http_status_code_tag,
5151
)
52-
from datadog_lambda.tag_object import tag_object
5352

5453
profiling_env_var = os.environ.get("DD_PROFILING_ENABLED", "false").lower() == "true"
5554
if profiling_env_var:
5655
from ddtrace.profiling import profiler
5756

5857
logger = logging.getLogger(__name__)
5958

60-
dd_capture_lambda_payload_enabled = (
61-
os.environ.get("DD_CAPTURE_LAMBDA_PAYLOAD", "false").lower() == "true"
62-
)
63-
6459
DD_FLUSH_TO_LOG = "DD_FLUSH_TO_LOG"
6560
DD_LOGS_INJECTION = "DD_LOGS_INJECTION"
6661
DD_MERGE_XRAY_TRACES = "DD_MERGE_XRAY_TRACES"
@@ -72,10 +67,34 @@
7267
DD_COLD_START_TRACING = "DD_COLD_START_TRACING"
7368
DD_MIN_COLD_START_DURATION = "DD_MIN_COLD_START_DURATION"
7469
DD_COLD_START_TRACE_SKIP_LIB = "DD_COLD_START_TRACE_SKIP_LIB"
70+
DD_CAPTURE_LAMBDA_PAYLOAD = "DD_CAPTURE_LAMBDA_PAYLOAD"
71+
DD_CAPTURE_LAMBDA_PAYLOAD_MAX_DEPTH = "DD_CAPTURE_LAMBDA_PAYLOAD_MAX_DEPTH"
7572
DD_REQUESTS_SERVICE_NAME = "DD_REQUESTS_SERVICE_NAME"
7673
DD_SERVICE = "DD_SERVICE"
7774
DD_ENV = "DD_ENV"
7875

76+
77+
def get_env_as_int(env_key, default_value: int) -> int:
78+
try:
79+
return int(os.environ.get(env_key, default_value))
80+
except Exception as e:
81+
logger.warn(
82+
f"Failed to parse {env_key} as int. Using default value: {default_value}. Error: {e}"
83+
)
84+
return default_value
85+
86+
87+
dd_capture_lambda_payload_enabled = (
88+
os.environ.get(DD_CAPTURE_LAMBDA_PAYLOAD, "false").lower() == "true"
89+
)
90+
91+
if dd_capture_lambda_payload_enabled:
92+
import datadog_lambda.tag_object as tag_object
93+
94+
tag_object.max_depth = get_env_as_int(
95+
DD_CAPTURE_LAMBDA_PAYLOAD_MAX_DEPTH, tag_object.max_depth
96+
)
97+
7998
env_env_var = os.environ.get(DD_ENV, None)
8099

81100
init_timestamp_ns = time_ns()
@@ -161,14 +180,9 @@ def __init__(self, func):
161180
self.cold_start_tracing = depends_on_dd_tracing_enabled(
162181
os.environ.get(DD_COLD_START_TRACING, "true").lower() == "true"
163182
)
164-
self.min_cold_start_trace_duration = 3
165-
if DD_MIN_COLD_START_DURATION in os.environ:
166-
try:
167-
self.min_cold_start_trace_duration = int(
168-
os.environ[DD_MIN_COLD_START_DURATION]
169-
)
170-
except Exception:
171-
logger.debug(f"Malformatted env {DD_MIN_COLD_START_DURATION}")
183+
self.min_cold_start_trace_duration = get_env_as_int(
184+
DD_MIN_COLD_START_DURATION, 3
185+
)
172186
self.cold_start_trace_skip_lib = [
173187
"ddtrace.internal.compat",
174188
"ddtrace.filters",
@@ -307,16 +321,14 @@ def _after(self, event, context):
307321
create_dd_dummy_metadata_subsegment(
308322
self.trigger_tags, XraySubsegment.LAMBDA_FUNCTION_TAGS_KEY
309323
)
310-
should_trace_cold_start = (
311-
dd_tracing_enabled and self.cold_start_tracing and is_new_sandbox()
312-
)
324+
should_trace_cold_start = self.cold_start_tracing and is_new_sandbox()
313325
if should_trace_cold_start:
314326
trace_ctx = tracer.current_trace_context()
315327

316328
if self.span:
317329
if dd_capture_lambda_payload_enabled:
318-
tag_object(self.span, "function.request", event)
319-
tag_object(self.span, "function.response", self.response)
330+
tag_object.tag_object(self.span, "function.request", event)
331+
tag_object.tag_object(self.span, "function.response", self.response)
320332

321333
if status_code:
322334
self.span.set_tag("http.status_code", status_code)

datadog_lambda/xray.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,6 @@ def generate_random_id():
7575

7676

7777
def build_segment(context, key, metadata):
78-
7978
segment = json.dumps(
8079
{
8180
"id": generate_random_id(),

scripts/run_integration_tests.sh

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -200,8 +200,8 @@ for handler_name in "${LAMBDA_HANDLERS[@]}"; do
200200
sed -E "s/(api_key=|'api_key': '|DD-API-KEY:)[a-z0-9\.\-]+/\1XXXX/g" |
201201
# Normalize package version so that these snapshots aren't broken on version bumps
202202
sed -E "s/(dd_lambda_layer:datadog-python[0-9]+_)[0-9]+\.[0-9]+\.[0-9]+/\1X\.X\.X/g" |
203-
sed -E "s/(datadog_lambda:v)([0-9]+\.[0-9]+\.[0-9])/\1XX/g" |
204-
sed -E "s/(datadogpy\/)([0-9]+\.[0-9]+\.[0-9])/\1XX/g" |
203+
sed -E "s/(datadog_lambda:v)([0-9]+\.[0-9]+\.[0-9]+)/\1XX/g" |
204+
sed -E "s/(datadogpy\/)([0-9]+\.[0-9]+\.[0-9]+)/\1XX/g" |
205205
sed -E "s/(python )([0-9]\.[0-9]+\.[0-9]+)/\1XX/g" |
206206
# Strip out run ID (from function name, resource, etc.)
207207
sed -E "s/${!run_id}/XXXX/g" |
@@ -231,7 +231,7 @@ for handler_name in "${LAMBDA_HANDLERS[@]}"; do
231231
sed -E "s/(\"connection_id\"\:\ \")[a-zA-Z0-9\-]+/\1XXXX/g" |
232232
sed -E "s/(\"shardId\-)([0-9]+)\:([a-zA-Z0-9]+)[a-zA-Z0-9]/\1XXXX:XXXX/g" |
233233
sed -E "s/(\"shardId\-)[0-9a-zA-Z]+/\1XXXX/g" |
234-
sed -E "s/(\"datadog_lambda\"\: \")([0-9]+\.[0-9]+\.[0-9])/\1X.X.X/g" |
234+
sed -E "s/(\"datadog_lambda\"\: \")([0-9]+\.[0-9]+\.[0-9]+)/\1X.X.X/g" |
235235
sed -E "s/(\"partition_key\"\:\ \")[a-zA-Z0-9\-]+/\1XXXX/g" |
236236
sed -E "s/(\"object_etag\"\:\ \")[a-zA-Z0-9\-]+/\1XXXX/g" |
237237
sed -E "s/(\"dd_trace\"\: \")([0-9]+\.[0-9]+\.[0-9]+)/\1X.X.X/g" |

tests/test_tag_object.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,39 @@ def test_tag_object(self):
2828
],
2929
True,
3030
)
31-
self.assertEqual(1, 1)
31+
32+
def test_tag_object_max_depth(self):
33+
payload = {
34+
"hello": "world",
35+
"level1": {
36+
"level2_dict": {"level3": 3},
37+
"level2_list": [None, True, "nice", {"l3": "v3"}],
38+
"level2_bool": True,
39+
"level2_int": 2,
40+
},
41+
"vals": [{"thingOne": 1}, {"thingTwo": 2}],
42+
}
43+
spanMock = MagicMock()
44+
import datadog_lambda.tag_object as lib_ref
45+
46+
lib_ref.max_depth = 2 # setting up the test
47+
tag_object(spanMock, "function.request", payload)
48+
lib_ref.max_depth = 10 # revert the setup
49+
spanMock.set_tag.assert_has_calls(
50+
[
51+
call("function.request.vals.0", "{'thingOne': 1}"),
52+
call("function.request.vals.1", "{'thingTwo': 2}"),
53+
call("function.request.hello", "world"),
54+
call("function.request.level1.level2_dict", "{'level3': 3}"),
55+
call(
56+
"function.request.level1.level2_list",
57+
"[None, True, 'nice', {'l3': 'v3'}]",
58+
),
59+
call("function.request.level1.level2_bool", "True"),
60+
call("function.request.level1.level2_int", "2"),
61+
],
62+
True,
63+
)
3264

3365
def test_redacted_tag_object(self):
3466
payload = {

0 commit comments

Comments
 (0)