Skip to content

Commit 64e15f5

Browse files
authored
Merge pull request #833 from DataDog/0.21.1-dev
Release 0.21.1
2 parents e26c219 + 70b4045 commit 64e15f5

File tree

10 files changed

+105
-43
lines changed

10 files changed

+105
-43
lines changed

ddtrace/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from .tracer import Tracer
55
from .settings import config
66

7-
__version__ = '0.21.0'
7+
__version__ = '0.21.1'
88

99
# a global tracer instance with integration settings
1010
tracer = Tracer()

ddtrace/bootstrap/sitecustomize.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@
1515
'[dd.trace_id=%(dd.trace_id)s dd.span_id=%(dd.span_id)s] ' if logs_injection else ''
1616
)
1717

18+
if logs_injection:
19+
# immediately patch logging if trace id injected
20+
from ddtrace import patch; patch(logging=True) # noqa
21+
1822
debug = os.environ.get("DATADOG_TRACE_DEBUG")
1923

2024
# Set here a default logging format for basicConfig

ddtrace/contrib/dbapi/__init__.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,18 +65,23 @@ def _trace_method(self, method, name, resource, extra_tags, *args, **kwargs):
6565
def executemany(self, query, *args, **kwargs):
6666
""" Wraps the cursor.executemany method"""
6767
self._self_last_execute_operation = query
68+
# Always return the result as-is
69+
# DEV: Some libraries return `None`, others `int`, and others the cursor objects
70+
# These differences should be overriden at the integration specific layer (e.g. in `sqlite3/patch.py`)
6871
# FIXME[matt] properly handle kwargs here. arg names can be different
6972
# with different libs.
70-
self._trace_method(
73+
return self._trace_method(
7174
self.__wrapped__.executemany, self._self_datadog_name, query, {'sql.executemany': 'true'},
7275
query, *args, **kwargs)
73-
return self
7476

7577
def execute(self, query, *args, **kwargs):
7678
""" Wraps the cursor.execute method"""
7779
self._self_last_execute_operation = query
78-
self._trace_method(self.__wrapped__.execute, self._self_datadog_name, query, {}, query, *args, **kwargs)
79-
return self
80+
81+
# Always return the result as-is
82+
# DEV: Some libraries return `None`, others `int`, and others the cursor objects
83+
# These differences should be overriden at the integration specific layer (e.g. in `sqlite3/patch.py`)
84+
return self._trace_method(self.__wrapped__.execute, self._self_datadog_name, query, {}, query, *args, **kwargs)
8085

8186
def callproc(self, proc, args):
8287
""" Wraps the cursor.callproc method"""

ddtrace/contrib/sqlite3/patch.py

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44
import wrapt
55

66
# project
7-
from ddtrace import Pin
8-
from ddtrace.contrib.dbapi import TracedConnection
9-
7+
from ...contrib.dbapi import TracedConnection, TracedCursor, FetchTracedCursor
108
from ...ext import AppTypes
9+
from ...pin import Pin
10+
from ...settings import config
1111

1212
# Original connect method
1313
_connect = sqlite3.connect
@@ -36,7 +36,31 @@ def patch_conn(conn):
3636
return wrapped
3737

3838

39+
class TracedSQLiteCursor(TracedCursor):
40+
def executemany(self, *args, **kwargs):
41+
# DEV: SQLite3 Cursor.execute always returns back the cursor instance
42+
super(TracedSQLiteCursor, self).executemany(*args, **kwargs)
43+
return self
44+
45+
def execute(self, *args, **kwargs):
46+
# DEV: SQLite3 Cursor.execute always returns back the cursor instance
47+
super(TracedSQLiteCursor, self).execute(*args, **kwargs)
48+
return self
49+
50+
51+
class TracedSQLiteFetchCursor(TracedSQLiteCursor, FetchTracedCursor):
52+
pass
53+
54+
3955
class TracedSQLite(TracedConnection):
56+
def __init__(self, conn, pin=None, cursor_cls=None):
57+
if not cursor_cls:
58+
# Do not trace `fetch*` methods by default
59+
cursor_cls = TracedSQLiteCursor
60+
if config.dbapi2.trace_fetch_methods:
61+
cursor_cls = TracedSQLiteFetchCursor
62+
63+
super(TracedSQLite, self).__init__(conn, pin=pin, cursor_cls=cursor_cls)
4064

4165
def execute(self, *args, **kwargs):
4266
# sqlite has a few extra sugar functions

ddtrace/propagation/http.py

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -56,34 +56,35 @@ def parent_call():
5656
headers[HTTP_HEADER_SAMPLING_PRIORITY] = str(span_context.sampling_priority)
5757

5858
@staticmethod
59-
def extract_trace_id(headers):
60-
trace_id = 0
59+
def extract_header_value(possible_header_names, headers, default=None):
60+
for header, value in headers.items():
61+
for header_name in possible_header_names:
62+
if header.lower() == header_name.lower():
63+
return value
6164

62-
for key in POSSIBLE_HTTP_HEADER_TRACE_IDS:
63-
if key in headers:
64-
trace_id = headers.get(key)
65+
return default
6566

66-
return int(trace_id)
67+
@staticmethod
68+
def extract_trace_id(headers):
69+
return int(
70+
HTTPPropagator.extract_header_value(
71+
POSSIBLE_HTTP_HEADER_TRACE_IDS, headers, default=0,
72+
)
73+
)
6774

6875
@staticmethod
6976
def extract_parent_span_id(headers):
70-
parent_span_id = 0
71-
72-
for key in POSSIBLE_HTTP_HEADER_PARENT_IDS:
73-
if key in headers:
74-
parent_span_id = headers.get(key)
75-
76-
return int(parent_span_id)
77+
return int(
78+
HTTPPropagator.extract_header_value(
79+
POSSIBLE_HTTP_HEADER_PARENT_IDS, headers, default=0,
80+
)
81+
)
7782

7883
@staticmethod
7984
def extract_sampling_priority(headers):
80-
sampling_priority = None
81-
82-
for key in POSSIBLE_HTTP_HEADER_SAMPLING_PRIORITIES:
83-
if key in headers:
84-
sampling_priority = headers.get(key)
85-
86-
return sampling_priority
85+
return HTTPPropagator.extract_header_value(
86+
POSSIBLE_HTTP_HEADER_SAMPLING_PRIORITIES, headers,
87+
)
8788

8889
def extract(self, headers):
8990
"""Extract a Context from HTTP headers into a new Context.

tests/contrib/dbapi/test_unit.py

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,23 @@ def setUp(self):
1414
def test_execute_wrapped_is_called_and_returned(self):
1515
cursor = self.cursor
1616
cursor.rowcount = 0
17+
cursor.execute.return_value = '__result__'
18+
1719
pin = Pin('pin_name', tracer=self.tracer)
1820
traced_cursor = TracedCursor(cursor, pin)
19-
assert traced_cursor is traced_cursor.execute('__query__', 'arg_1', kwarg1='kwarg1')
21+
# DEV: We always pass through the result
22+
assert '__result__' == traced_cursor.execute('__query__', 'arg_1', kwarg1='kwarg1')
2023
cursor.execute.assert_called_once_with('__query__', 'arg_1', kwarg1='kwarg1')
2124

2225
def test_executemany_wrapped_is_called_and_returned(self):
2326
cursor = self.cursor
2427
cursor.rowcount = 0
28+
cursor.executemany.return_value = '__result__'
29+
2530
pin = Pin('pin_name', tracer=self.tracer)
2631
traced_cursor = TracedCursor(cursor, pin)
27-
assert traced_cursor is traced_cursor.executemany('__query__', 'arg_1', kwarg1='kwarg1')
32+
# DEV: We always pass through the result
33+
assert '__result__' == traced_cursor.executemany('__query__', 'arg_1', kwarg1='kwarg1')
2834
cursor.executemany.assert_called_once_with('__query__', 'arg_1', kwarg1='kwarg1')
2935

3036
def test_fetchone_wrapped_is_called_and_returned(self):
@@ -114,14 +120,17 @@ def test_when_pin_disabled_then_no_tracing(self):
114120
cursor = self.cursor
115121
tracer = self.tracer
116122
cursor.rowcount = 0
123+
cursor.execute.return_value = '__result__'
124+
cursor.executemany.return_value = '__result__'
125+
117126
tracer.enabled = False
118127
pin = Pin('pin_name', tracer=tracer)
119128
traced_cursor = TracedCursor(cursor, pin)
120129

121-
assert traced_cursor is traced_cursor.execute('arg_1', kwarg1='kwarg1')
130+
assert '__result__' == traced_cursor.execute('arg_1', kwarg1='kwarg1')
122131
assert len(tracer.writer.pop()) == 0
123132

124-
assert traced_cursor is traced_cursor.executemany('arg_1', kwarg1='kwarg1')
133+
assert '__result__' == traced_cursor.executemany('arg_1', kwarg1='kwarg1')
125134
assert len(tracer.writer.pop()) == 0
126135

127136
cursor.callproc.return_value = 'callproc'
@@ -191,17 +200,21 @@ def setUp(self):
191200
def test_execute_wrapped_is_called_and_returned(self):
192201
cursor = self.cursor
193202
cursor.rowcount = 0
203+
cursor.execute.return_value = '__result__'
204+
194205
pin = Pin('pin_name', tracer=self.tracer)
195206
traced_cursor = FetchTracedCursor(cursor, pin)
196-
assert traced_cursor is traced_cursor.execute('__query__', 'arg_1', kwarg1='kwarg1')
207+
assert '__result__' == traced_cursor.execute('__query__', 'arg_1', kwarg1='kwarg1')
197208
cursor.execute.assert_called_once_with('__query__', 'arg_1', kwarg1='kwarg1')
198209

199210
def test_executemany_wrapped_is_called_and_returned(self):
200211
cursor = self.cursor
201212
cursor.rowcount = 0
213+
cursor.executemany.return_value = '__result__'
214+
202215
pin = Pin('pin_name', tracer=self.tracer)
203216
traced_cursor = FetchTracedCursor(cursor, pin)
204-
assert traced_cursor is traced_cursor.executemany('__query__', 'arg_1', kwarg1='kwarg1')
217+
assert '__result__' == traced_cursor.executemany('__query__', 'arg_1', kwarg1='kwarg1')
205218
cursor.executemany.assert_called_once_with('__query__', 'arg_1', kwarg1='kwarg1')
206219

207220
def test_fetchone_wrapped_is_called_and_returned(self):
@@ -297,14 +310,17 @@ def test_when_pin_disabled_then_no_tracing(self):
297310
cursor = self.cursor
298311
tracer = self.tracer
299312
cursor.rowcount = 0
313+
cursor.execute.return_value = '__result__'
314+
cursor.executemany.return_value = '__result__'
315+
300316
tracer.enabled = False
301317
pin = Pin('pin_name', tracer=tracer)
302318
traced_cursor = FetchTracedCursor(cursor, pin)
303319

304-
assert traced_cursor is traced_cursor.execute('arg_1', kwarg1='kwarg1')
320+
assert '__result__' == traced_cursor.execute('arg_1', kwarg1='kwarg1')
305321
assert len(tracer.writer.pop()) == 0
306322

307-
assert traced_cursor is traced_cursor.executemany('arg_1', kwarg1='kwarg1')
323+
assert '__result__' == traced_cursor.executemany('arg_1', kwarg1='kwarg1')
308324
assert len(tracer.writer.pop()) == 0
309325

310326
cursor.callproc.return_value = 'callproc'

tests/contrib/mysqldb/test_mysql.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ def test_simple_query(self):
4242
conn, tracer = self._get_conn_tracer()
4343
writer = tracer.writer
4444
cursor = conn.cursor()
45-
cursor.execute("SELECT 1")
45+
rowcount = cursor.execute("SELECT 1")
46+
eq_(rowcount, 1)
4647
rows = cursor.fetchall()
4748
eq_(len(rows), 1)
4849
spans = writer.pop()

tests/contrib/psycopg/test_psycopg.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,8 @@ def assert_conn_is_traced(self, db, service):
8989

9090
start = time.time()
9191
cursor = db.cursor()
92-
cursor.execute(q)
92+
res = cursor.execute(q)
93+
self.assertIsNone(res)
9394
rows = cursor.fetchall()
9495
end = time.time()
9596

tests/contrib/pymysql/test_pymysql.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,11 @@ def test_simple_query(self):
5454
conn, tracer = self._get_conn_tracer()
5555
writer = tracer.writer
5656
cursor = conn.cursor()
57-
cursor.execute('SELECT 1')
57+
58+
# PyMySQL returns back the rowcount instead of a cursor
59+
rowcount = cursor.execute('SELECT 1')
60+
eq_(rowcount, 1)
61+
5862
rows = cursor.fetchall()
5963
eq_(len(rows), 1)
6064
spans = writer.pop()
@@ -135,7 +139,11 @@ def test_query_many(self):
135139
stmt = "INSERT INTO dummy (dummy_key, dummy_value) VALUES (%s, %s)"
136140
data = [("foo", "this is foo"),
137141
("bar", "this is bar")]
138-
cursor.executemany(stmt, data)
142+
143+
# PyMySQL `executemany()` returns the rowcount
144+
rowcount = cursor.executemany(stmt, data)
145+
eq_(rowcount, 2)
146+
139147
query = "SELECT dummy_key, dummy_value FROM dummy ORDER BY dummy_key"
140148
cursor.execute(query)
141149
rows = cursor.fetchall()

tests/contrib/sqlite3/test_sqlite3.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import ddtrace
77
from ddtrace import Pin
88
from ddtrace.contrib.sqlite3 import connection_factory
9-
from ddtrace.contrib.sqlite3.patch import patch, unpatch
9+
from ddtrace.contrib.sqlite3.patch import patch, unpatch, TracedSQLiteCursor
1010
from ddtrace.ext import errors
1111

1212
# testing
@@ -29,8 +29,9 @@ def test_backwards_compat(self):
2929
factory = connection_factory(self.tracer, service='my_db_service')
3030
conn = sqlite3.connect(':memory:', factory=factory)
3131
q = 'select * from sqlite_master'
32-
rows = conn.execute(q)
33-
assert not rows.fetchall()
32+
cursor = conn.execute(q)
33+
self.assertIsInstance(cursor, TracedSQLiteCursor)
34+
assert not cursor.fetchall()
3435
assert not self.spans
3536

3637
def test_service_info(self):
@@ -60,6 +61,7 @@ def test_sqlite(self):
6061
q = 'select * from sqlite_master'
6162
start = time.time()
6263
cursor = db.execute(q)
64+
self.assertIsInstance(cursor, TracedSQLiteCursor)
6365
rows = cursor.fetchall()
6466
end = time.time()
6567
assert not rows

0 commit comments

Comments
 (0)