Skip to content

Commit 6c1a992

Browse files
committed
Normalize logged HTTP requests
1 parent 165f78a commit 6c1a992

17 files changed

+139
-36
lines changed

datadog_lambda/patch.py

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
# This product includes software developed at Datadog (https://www.datadoghq.com/).
44
# Copyright 2019 Datadog, Inc.
55

6+
import json
67
import os
78
import sys
89
import logging
@@ -74,8 +75,7 @@ def _wrap_requests_request(func, instance, args, kwargs):
7475

7576
# If we're in an integration test, log the HTTP requests made
7677
if os.environ.get("DD_INTEGRATION_TEST", "false").lower() == "true":
77-
request_string = "HTTP {} Kwargs: {}".format(" ".join(args), kwargs)
78-
print(request_string)
78+
_print_request_string(args, kwargs)
7979

8080
return func(*args, **kwargs)
8181

@@ -93,8 +93,43 @@ def _wrap_httplib_request(func, instance, args, kwargs):
9393
else:
9494
kwargs["headers"] = context
9595

96-
if os.environ.get("DD_INTEGRATION_TEST", "false").lower() == "true":
97-
request_string = "HTTP {} Kwargs: {}".format(" ".join(args), kwargs)
98-
print(request_string)
99-
10096
return func(*args, **kwargs)
97+
98+
99+
def _print_request_string(args, kwargs):
100+
"""Print the request so that it can be checked in integration tests
101+
102+
Only used by integration tests.
103+
"""
104+
# Normalizes the different ways args can be passed to a request
105+
# to prevent test flakiness
106+
method = None
107+
if len(args) > 0:
108+
method = args[0]
109+
else:
110+
method = kwargs.get("method", "").upper()
111+
112+
url = None
113+
if len(args) > 1:
114+
url = args[1]
115+
else:
116+
url = kwargs.get("url")
117+
118+
# Sort the datapoints POSTed by their name so that snapshots always align
119+
data = kwargs.get("data", "{}")
120+
data_dict = json.loads(data)
121+
data_dict.get("series", []).sort(key=lambda series: series.get("metric"))
122+
sorted_data = json.dumps(data_dict)
123+
124+
# Sort headers to prevent any differences in ordering
125+
headers = kwargs.get("headers", {})
126+
sorted_headers = sorted(
127+
"{}:{}".format(key, value) for key, value in headers.items()
128+
)
129+
sorted_header_str = json.dumps(sorted_headers)
130+
print(
131+
"HTTP {} {} Headers: {} Data: {}".format(
132+
method, url, sorted_header_str, sorted_data
133+
)
134+
)
135+

scripts/run_integration_tests.sh

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ set -e
55

66
# These values need to be in sync with serverless.yml, where there needs to be a function
77
# defined for every handler-runtime combination
8-
LAMBDA_HANDLERS=("async-metrics" "sync-metrics")
8+
LAMBDA_HANDLERS=("async-metrics" "sync-metrics" "http-requests")
99
RUNTIMES=("python27" "python36" "python37" "python38")
1010

1111
LOGS_WAIT_SECONDS=20
@@ -28,12 +28,14 @@ fi
2828
if [ -z "$DD_API_KEY" ]; then
2929
echo "No DD_API_KEY env var set, exiting"
3030
exit 1
31-
else
32-
echo "The API key is $DD_API_KEY"
3331
fi
3432

35-
echo "Building layers that will be deployed with our test functions"
36-
# source $scripts_dir/build_layers.sh
33+
if [ -n "$BUILD_LAYERS" ]; then
34+
echo "Building layers that will be deployed with our test functions"
35+
source $scripts_dir/build_layers.sh
36+
else
37+
echo "Not building layers, ensure they've already been built or re-run with 'REBUILD_LAYERS=true ./scripts/run_integration_tests.sh'"
38+
fi
3739

3840
echo "Deploying functions"
3941
cd $integration_tests_dir
@@ -48,9 +50,9 @@ for handler_name in "${LAMBDA_HANDLERS[@]}"; do
4850

4951
return_value=$(serverless invoke -f $function_name)
5052

51-
if [ -n "$UPDATE_SNAPSHOTS" ]; then
53+
if [ -n "$UPDATE_SNAPSHOTS" ] || [ ! -f $function_snapshot_path ]; then
5254
# If $UPDATE_SNAPSHOTS is set to true, write the new logs over the current snapshot
53-
echo "Overwriting return value snapshot for $function_name"
55+
echo "Writing return value snapshot for $function_name"
5456
echo "$return_value" >$function_snapshot_path
5557
else
5658
# Compare new return value to snapshot
@@ -87,24 +89,25 @@ for handler_name in "${LAMBDA_HANDLERS[@]}"; do
8789
# Normalize Lambda runtime report logs
8890
logs=$(echo "$logs" | sed -E 's/(RequestId|TraceId|SegmentId|Duration|Memory Used|"e"): [a-z0-9\.\-]+/\1: XXXX/g')
8991
# Normalize DD APM headers
90-
logs=$(echo "$logs" | sed -E "s/('x-datadog-parent-id': '|'x-datadog-trace-id': ')[0-9]+/\1XXXX/g")
91-
# Normalize timestamps logged requests
92+
logs=$(echo "$logs" | sed -E "s/(x-datadog-parent-id:|x-datadog-trace-id:)[0-9]+/\1XXXX/g")
93+
# Normalize timestamps in datapoints POSTed to DD
9294
logs=$(echo "$logs" | sed -E 's/"points": \[\[[0-9\.]+,/"points": \[\[XXXX,/g')
93-
# Normalize the invocation IDs used in requests to the Lambda runtime
94-
logs=$(echo "$logs" | sed -E 's/\/2018-06-01\/runtime\/invocation\/[a-z0-9-]+/\/2018-06-01\/runtime\/invocation\/XXXX/g')
95+
# # Normalize invocation IDs used in requests to the Lambda runtime
96+
# logs=$(echo "$logs" | sed -E 's/\/2018-06-01\/runtime\/invocation\/[a-z0-9-]+/\/2018-06-01\/runtime\/invocation\/XXXX/g')
9597
# Strip API key from logged requests
9698
logs=$(echo "$logs" | sed -E "s/(api_key=|'api_key': ')[a-z0-9\.\-]+/\1XXXX/g")
9799

98-
if [ -n "$UPDATE_SNAPSHOTS" ]; then
99-
# If $UPDATE_SNAPSHOTS is set to true, write the new logs over the current snapshot
100-
echo "Overwriting log snapshot for $function_name"
100+
if [ -n "$UPDATE_SNAPSHOTS" ] || [ ! -f $function_snapshot_path ]; then
101+
# If $UPDATE_SNAPSHOTS is set to true write the new logs over the current snapshot
102+
# If no file exists yet, we create one
103+
echo "Writing log snapshot for $function_name"
101104
echo "$logs" >$function_snapshot_path
102105
else
103106
# Compare new logs to snapshots
104107
set +e # Don't exit this script if there is a diff
105108
diff_output=$(echo "$logs" | diff - $function_snapshot_path)
106109
if [ $? -eq 1 ]; then
107-
echo "Failed: Mismatch found between new $function_name logs and snapshot:"
110+
echo "Failed: Mismatch found between new $function_name logs (first) and snapshot (second):"
108111
echo "$diff_output"
109112
mismatch_found=true
110113
else

tests/integration/http_requests.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ def handle(event, context):
1111
lambda_metric(
1212
"tests.integration.count", 21, tags=["test:integration", "role:hello"]
1313
)
14-
response = requests.get("https://datadoghq.com")
14+
15+
us_response = requests.get("https://ip-ranges.datadoghq.com/")
16+
eu_response = requests.get("https://ip-ranges.datadoghq.eu/")
1517

1618
return {"statusCode": 200, "body": "hello, dog!"}

tests/integration/serverless.yml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,3 +81,28 @@ functions:
8181
runtime: python3.8
8282
layers:
8383
- { Ref: Python38LambdaLayer }
84+
85+
# http-requests
86+
http-requests-python27:
87+
handler: http_requests.handle
88+
runtime: python2.7
89+
layers:
90+
- { Ref: Python27LambdaLayer }
91+
92+
http-requests-python36:
93+
handler: http_requests.handle
94+
runtime: python3.6
95+
layers:
96+
- { Ref: Python36LambdaLayer }
97+
98+
http-requests-python37:
99+
handler: http_requests.handle
100+
runtime: python3.7
101+
layers:
102+
- { Ref: Python37LambdaLayer }
103+
104+
http-requests-python38:
105+
handler: http_requests.handle
106+
runtime: python3.8
107+
layers:
108+
- { Ref: Python38LambdaLayer }
Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
1-
HTTP GET /2018-06-01/runtime/invocation/XXXX Kwargs: {'headers': {'x-datadog-trace-id': 'XXXX', 'x-datadog-parent-id': 'XXXX', 'x-datadog-sampling-priority': '-1'}}
21
START RequestId: XXXX Version: $LATEST
32
{"m": "aws.lambda.enhanced.invocations", "v": 1, "e": XXXX, "t": ["region:us-east-1", "account_id:601427279990", "functionname:integration-tester-dev-async-metrics-python37", "cold_start:true", "memorysize:1024", "runtime:python3.7", "dd_lambda_layer:datadog-python37_2.13.0"]}
43
{"m": "hello.dog", "v": 1, "e": XXXX, "t": ["team:serverless", "role:hello", "dd_lambda_layer:datadog-python37_2.13.0"]}
54
{"m": "tests.integration.count", "v": 21, "e": XXXX, "t": ["test:integration", "role:hello", "dd_lambda_layer:datadog-python37_2.13.0"]}
6-
HTTP POST /2018-06-01/runtime/invocation/XXXX/response {"statusCode": 200, "body": "hello, dog!"} Kwargs: {'headers': {'Content-Type': 'application/json', 'x-datadog-trace-id': 'XXXX', 'x-datadog-parent-id': 'XXXX', 'x-datadog-sampling-priority': '2'}}
7-
HTTP GET /2018-06-01/runtime/invocation/XXXX Kwargs: {'headers': {'x-datadog-trace-id': 'XXXX', 'x-datadog-parent-id': 'XXXX', 'x-datadog-sampling-priority': '2'}}
85
END RequestId: XXXX
96
REPORT RequestId: XXXX Duration: XXXX ms Billed Duration: XXXX ms Memory Size: 1024 MB Max Memory Used: XXXX MB Init Duration: XXXX ms
107
XRAY TraceId: XXXX SegmentId: XXXX Sampled: true
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
START RequestId: XXXX Version: $LATEST
2+
{"e": XXXX, "m": "aws.lambda.enhanced.invocations", "t": ["region:us-east-1", "account_id:601427279990", "functionname:integration-tester-dev-http-requests-python27", "cold_start:true", "memorysize:1024", "runtime:python2.7", "dd_lambda_layer:datadog-python27_2.13.0"], "v": 1}
3+
HTTP GET https://ip-ranges.datadoghq.com/ Headers: ["x-datadog-parent-id:XXXX", "x-datadog-sampling-priority:2", "x-datadog-trace-id:XXXX"] Data: {}
4+
HTTP GET https://ip-ranges.datadoghq.eu/ Headers: ["x-datadog-parent-id:XXXX", "x-datadog-sampling-priority:2", "x-datadog-trace-id:XXXX"] Data: {}
5+
HTTP POST https://api.datadoghq.com/api/v1/distribution_points Headers: ["Content-Type:application/json", "x-datadog-parent-id:XXXX", "x-datadog-sampling-priority:2", "x-datadog-trace-id:XXXX"] Data: {"series": [{"tags": ["team:serverless", "role:hello", "dd_lambda_layer:datadog-python27_2.13.0"], "metric": "hello.dog", "interval": 10, "host": null, "points": [[XXXX, [1.0]]], "device": null, "type": "distribution"}, {"tags": ["test:integration", "role:hello", "dd_lambda_layer:datadog-python27_2.13.0"], "metric": "tests.integration.count", "interval": 10, "host": null, "points": [[XXXX, [21.0]]], "device": null, "type": "distribution"}]}
6+
END RequestId: XXXX
7+
REPORT RequestId: XXXX Duration: XXXX ms Billed Duration: XXXX ms Memory Size: 1024 MB Max Memory Used: XXXX MB Init Duration: XXXX ms
8+
XRAY TraceId: XXXX SegmentId: XXXX Sampled: true
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"body": "hello, dog!",
3+
"statusCode": 200
4+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
START RequestId: XXXX Version: $LATEST
2+
{"m": "aws.lambda.enhanced.invocations", "v": 1, "e": XXXX, "t": ["region:us-east-1", "account_id:601427279990", "functionname:integration-tester-dev-http-requests-python36", "cold_start:true", "memorysize:1024", "runtime:python3.6", "dd_lambda_layer:datadog-python36_2.13.0"]}
3+
HTTP GET https://ip-ranges.datadoghq.com/ Headers: ["x-datadog-parent-id:XXXX", "x-datadog-sampling-priority:2", "x-datadog-trace-id:XXXX"] Data: {}
4+
HTTP GET https://ip-ranges.datadoghq.eu/ Headers: ["x-datadog-parent-id:XXXX", "x-datadog-sampling-priority:2", "x-datadog-trace-id:XXXX"] Data: {}
5+
HTTP POST https://api.datadoghq.com/api/v1/distribution_points Headers: ["Content-Type:application/json", "x-datadog-parent-id:XXXX", "x-datadog-sampling-priority:2", "x-datadog-trace-id:XXXX"] Data: {"series": [{"metric": "hello.dog", "points": [[XXXX, [1.0]]], "type": "distribution", "host": null, "device": null, "tags": ["team:serverless", "role:hello", "dd_lambda_layer:datadog-python36_2.13.0"], "interval": 10}, {"metric": "tests.integration.count", "points": [[XXXX, [21.0]]], "type": "distribution", "host": null, "device": null, "tags": ["test:integration", "role:hello", "dd_lambda_layer:datadog-python36_2.13.0"], "interval": 10}]}
6+
END RequestId: XXXX
7+
REPORT RequestId: XXXX Duration: XXXX ms Billed Duration: XXXX ms Memory Size: 1024 MB Max Memory Used: XXXX MB Init Duration: XXXX ms
8+
XRAY TraceId: XXXX SegmentId: XXXX Sampled: true
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"statusCode": 200,
3+
"body": "hello, dog!"
4+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
START RequestId: XXXX Version: $LATEST
2+
{"m": "aws.lambda.enhanced.invocations", "v": 1, "e": XXXX, "t": ["region:us-east-1", "account_id:601427279990", "functionname:integration-tester-dev-http-requests-python37", "cold_start:true", "memorysize:1024", "runtime:python3.7", "dd_lambda_layer:datadog-python37_2.13.0"]}
3+
HTTP GET https://ip-ranges.datadoghq.com/ Headers: ["x-datadog-parent-id:XXXX", "x-datadog-sampling-priority:2", "x-datadog-trace-id:XXXX"] Data: {}
4+
HTTP GET https://ip-ranges.datadoghq.eu/ Headers: ["x-datadog-parent-id:XXXX", "x-datadog-sampling-priority:2", "x-datadog-trace-id:XXXX"] Data: {}
5+
HTTP POST https://api.datadoghq.com/api/v1/distribution_points Headers: ["Content-Type:application/json", "x-datadog-parent-id:XXXX", "x-datadog-sampling-priority:2", "x-datadog-trace-id:XXXX"] Data: {"series": [{"metric": "hello.dog", "points": [[XXXX, [1.0]]], "type": "distribution", "host": null, "device": null, "tags": ["team:serverless", "role:hello", "dd_lambda_layer:datadog-python37_2.13.0"], "interval": 10}, {"metric": "tests.integration.count", "points": [[XXXX, [21.0]]], "type": "distribution", "host": null, "device": null, "tags": ["test:integration", "role:hello", "dd_lambda_layer:datadog-python37_2.13.0"], "interval": 10}]}
6+
END RequestId: XXXX
7+
REPORT RequestId: XXXX Duration: XXXX ms Billed Duration: XXXX ms Memory Size: 1024 MB Max Memory Used: XXXX MB Init Duration: XXXX ms
8+
XRAY TraceId: XXXX SegmentId: XXXX Sampled: true

0 commit comments

Comments
 (0)