Skip to content

Commit f3660b7

Browse files
authored
Merge pull request #56 from DataDog/tian.chu/fix-httplib-patch
Patching http clients erase existing headers
2 parents ed20079 + 1fac0fa commit f3660b7

11 files changed

+152
-103
lines changed

datadog_lambda/patch.py

Lines changed: 42 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -10,27 +10,49 @@
1010

1111
from wrapt import wrap_function_wrapper as wrap
1212
from wrapt.importer import when_imported
13+
from ddtrace import patch_all as patch_all_dd
1314

14-
from datadog_lambda.tracing import get_dd_trace_context
15+
from datadog_lambda.tracing import (
16+
get_dd_trace_context,
17+
dd_tracing_enabled,
18+
)
1519

1620
logger = logging.getLogger(__name__)
1721

1822
if sys.version_info >= (3, 0, 0):
1923
httplib_module = "http.client"
24+
from collections.abc import MutableMapping
2025
else:
2126
httplib_module = "httplib"
27+
from collections import MutableMapping
2228

2329
_httplib_patched = False
2430
_requests_patched = False
31+
_integration_tests_patched = False
2532

2633

2734
def patch_all():
2835
"""
29-
Patch the widely-used HTTP clients to automatically inject
30-
Datadog trace context.
36+
Patch third-party libraries for tracing.
3137
"""
32-
_patch_httplib()
33-
_ensure_patch_requests()
38+
_patch_for_integration_tests()
39+
40+
if dd_tracing_enabled:
41+
patch_all_dd()
42+
else:
43+
_patch_httplib()
44+
_ensure_patch_requests()
45+
46+
47+
def _patch_for_integration_tests():
48+
"""
49+
Patch `requests` to log the outgoing requests for integration tests.
50+
"""
51+
global _integration_tests_patched
52+
is_in_tests = os.environ.get("DD_INTEGRATION_TEST", "false").lower() == "true"
53+
if not _integration_tests_patched and is_in_tests:
54+
wrap("requests", "Session.send", _log_request)
55+
_integration_tests_patched = True
3456

3557

3658
def _patch_httplib():
@@ -80,17 +102,13 @@ def _wrap_requests_request(func, instance, args, kwargs):
80102
into the outgoing requests.
81103
"""
82104
context = get_dd_trace_context()
83-
if "headers" in kwargs and isinstance(kwargs["headers"], dict):
105+
if "headers" in kwargs and isinstance(kwargs["headers"], MutableMapping):
84106
kwargs["headers"].update(context)
85-
elif len(args) >= 5 and isinstance(args[4], dict):
107+
elif len(args) >= 5 and isinstance(args[4], MutableMapping):
86108
args[4].update(context)
87109
else:
88110
kwargs["headers"] = context
89111

90-
# If we're in an integration test, log the HTTP requests made
91-
if os.environ.get("DD_INTEGRATION_TEST", "false").lower() == "true":
92-
_print_request_string(args, kwargs)
93-
94112
return func(*args, **kwargs)
95113

96114

@@ -100,43 +118,38 @@ def _wrap_httplib_request(func, instance, args, kwargs):
100118
the Datadog trace headers into the outgoing requests.
101119
"""
102120
context = get_dd_trace_context()
103-
if "headers" in kwargs and isinstance(kwargs["headers"], dict):
121+
if "headers" in kwargs and isinstance(kwargs["headers"], MutableMapping):
104122
kwargs["headers"].update(context)
105-
elif len(args) >= 4 and isinstance(args[3], dict):
123+
elif len(args) >= 4 and isinstance(args[3], MutableMapping):
106124
args[3].update(context)
107125
else:
108126
kwargs["headers"] = context
109127

110128
return func(*args, **kwargs)
111129

112130

113-
def _print_request_string(args, kwargs):
131+
def _log_request(func, instance, args, kwargs):
132+
request = kwargs.get("request") or args[0]
133+
_print_request_string(request)
134+
return func(*args, **kwargs)
135+
136+
137+
def _print_request_string(request):
114138
"""Print the request so that it can be checked in integration tests
115139
116140
Only used by integration tests.
117141
"""
118-
# Normalizes the different ways args can be passed to a request
119-
# to prevent test flakiness
120-
method = None
121-
if len(args) > 0:
122-
method = args[0]
123-
else:
124-
method = kwargs.get("method", "").upper()
125-
126-
url = None
127-
if len(args) > 1:
128-
url = args[1]
129-
else:
130-
url = kwargs.get("url")
142+
method = request.method
143+
url = request.url
131144

132145
# Sort the datapoints POSTed by their name so that snapshots always align
133-
data = kwargs.get("data", "{}")
146+
data = request.body or "{}"
134147
data_dict = json.loads(data)
135148
data_dict.get("series", []).sort(key=lambda series: series.get("metric"))
136149
sorted_data = json.dumps(data_dict)
137150

138151
# Sort headers to prevent any differences in ordering
139-
headers = kwargs.get("headers", {})
152+
headers = request.headers or {}
140153
sorted_headers = sorted(
141154
"{}:{}".format(key, value) for key, value in headers.items()
142155
)

datadog_lambda/wrapper.py

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
set_dd_trace_py_root,
2323
create_function_execution_span,
2424
)
25-
from ddtrace import patch_all as patch_all_dd
2625

2726
logger = logging.getLogger(__name__)
2827

@@ -93,12 +92,9 @@ def __init__(self, func):
9392
if self.logs_injection:
9493
inject_correlation_ids()
9594

96-
if not dd_tracing_enabled:
97-
# When using dd_trace_py it will patch all the http clients for us,
98-
# Patch HTTP clients to propagate Datadog trace context
99-
patch_all()
100-
else:
101-
patch_all_dd()
95+
# Patch third-party libraries for tracing
96+
patch_all()
97+
10298
logger.debug("datadog_lambda_wrapper initialized")
10399
except Exception:
104100
traceback.print_exc()
@@ -137,10 +133,10 @@ def _before(self, event, context):
137133

138134
def _after(self, event, context):
139135
try:
140-
if self.span:
141-
self.span.finish()
142136
if not self.flush_to_log:
143137
lambda_stats.flush(float("inf"))
138+
if self.span:
139+
self.span.finish()
144140
logger.debug("datadog_lambda_wrapper _after() done")
145141
except Exception:
146142
traceback.print_exc()

0 commit comments

Comments
 (0)