Skip to content

Commit 6656236

Browse files
authored
Merge pull request #740 from DataDog/0.17-dev
v0.17.0
2 parents 4e5acbd + be1364f commit 6656236

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

67 files changed

+2878
-938
lines changed

.circleci/config.yml

Lines changed: 254 additions & 76 deletions
Large diffs are not rendered by default.

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.16.0'
7+
__version__ = '0.17.0'
88

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

ddtrace/bootstrap/sitecustomize.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,18 @@ def update_patched_modules():
4343
EXTRA_PATCHED_MODULES.update({module: should_patch.lower() == 'true'})
4444

4545

46+
def add_global_tags(tracer):
47+
tags = {}
48+
for tag in os.environ.get('DD_TRACE_GLOBAL_TAGS', '').split(','):
49+
tag_name, _, tag_value = tag.partition(':')
50+
if not tag_name or not tag_value:
51+
log.debug('skipping malformed tracer tag')
52+
continue
53+
54+
tags[tag_name] = tag_value
55+
tracer.set_tags(tags)
56+
57+
4658
try:
4759
from ddtrace import tracer
4860
patch = True
@@ -51,7 +63,7 @@ def update_patched_modules():
5163
# TODO: these variables are deprecated; use utils method and update our documentation
5264
# correct prefix should be DD_*
5365
enabled = os.environ.get("DATADOG_TRACE_ENABLED")
54-
hostname = os.environ.get("DATADOG_TRACE_AGENT_HOSTNAME")
66+
hostname = os.environ.get('DD_AGENT_HOST', os.environ.get('DATADOG_TRACE_AGENT_HOSTNAME'))
5567
port = os.environ.get("DATADOG_TRACE_AGENT_PORT")
5668
priority_sampling = os.environ.get("DATADOG_PRIORITY_SAMPLING")
5769

@@ -81,6 +93,9 @@ def update_patched_modules():
8193
if 'DATADOG_ENV' in os.environ:
8294
tracer.set_tags({"env": os.environ["DATADOG_ENV"]})
8395

96+
if 'DD_TRACE_GLOBAL_TAGS' in os.environ:
97+
add_global_tags(tracer)
98+
8499
# Ensure sitecustomize.py is properly called if available in application directories:
85100
# * exclude `bootstrap_dir` from the search
86101
# * find a user `sitecustomize.py` module

ddtrace/contrib/dbapi/__init__.py

Lines changed: 87 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,12 @@
22
Generic dbapi tracing code.
33
"""
44

5-
# stdlib
65
import logging
76

8-
# 3p
97
import wrapt
108

11-
# project
129
from ddtrace import Pin
13-
from ddtrace.ext import sql
14-
15-
from ...ext import AppTypes
10+
from ddtrace.ext import AppTypes, sql
1611

1712
log = logging.getLogger(__name__)
1813

@@ -24,38 +19,92 @@ def __init__(self, cursor, pin):
2419
super(TracedCursor, self).__init__(cursor)
2520
pin.onto(self)
2621
name = pin.app or 'sql'
27-
self._self_datadog_name = '%s.query' % name
28-
29-
def _trace_method(self, method, resource, extra_tags, *args, **kwargs):
22+
self._self_datadog_name = '{}.query'.format(name)
23+
self._self_last_execute_operation = None
24+
25+
def _trace_method(self, method, name, resource, extra_tags, *args, **kwargs):
26+
"""
27+
Internal function to trace the call to the underlying cursor method
28+
:param method: The callable to be wrapped
29+
:param name: The name of the resulting span.
30+
:param resource: The sql query. Sql queries are obfuscated on the agent side.
31+
:param extra_tags: A dict of tags to store into the span's meta
32+
:param args: The args that will be passed as positional args to the wrapped method
33+
:param kwargs: The args that will be passed as kwargs to the wrapped method
34+
:return: The result of the wrapped method invocation
35+
"""
3036
pin = Pin.get_from(self)
3137
if not pin or not pin.enabled():
3238
return method(*args, **kwargs)
3339
service = pin.service
34-
35-
with pin.tracer.trace(self._self_datadog_name, service=service, resource=resource) as s:
40+
with pin.tracer.trace(name, service=service, resource=resource) as s:
3641
s.span_type = sql.TYPE
42+
# No reason to tag the query since it is set as the resource by the agent. See:
43+
# https://github.com/DataDog/datadog-trace-agent/blob/bda1ebbf170dd8c5879be993bdd4dbae70d10fda/obfuscate/sql.go#L232
3744
s.set_tags(pin.tags)
3845
s.set_tags(extra_tags)
3946

4047
try:
4148
return method(*args, **kwargs)
4249
finally:
43-
s.set_metric("db.rowcount", self.rowcount)
50+
row_count = self.__wrapped__.rowcount
51+
s.set_metric('db.rowcount', row_count)
52+
# Necessary for django integration backward compatibility. Django integration used to provide its own
53+
# implementation of the TracedCursor, which used to store the row count into a tag instead of
54+
# as a metric. Such custom implementation has been replaced by this generic dbapi implementation and
55+
# this tag has been added since.
56+
if row_count and row_count >= 0:
57+
s.set_tag(sql.ROWS, row_count)
4458

4559
def executemany(self, query, *args, **kwargs):
60+
""" Wraps the cursor.executemany method"""
61+
self._self_last_execute_operation = query
4662
# FIXME[matt] properly handle kwargs here. arg names can be different
4763
# with different libs.
48-
return self._trace_method(
49-
self.__wrapped__.executemany, query, {'sql.executemany': 'true'},
64+
self._trace_method(
65+
self.__wrapped__.executemany, self._self_datadog_name, query, {'sql.executemany': 'true'},
5066
query, *args, **kwargs)
67+
return self
5168

5269
def execute(self, query, *args, **kwargs):
53-
return self._trace_method(
54-
self.__wrapped__.execute, query, {}, query, *args, **kwargs)
70+
""" Wraps the cursor.execute method"""
71+
self._self_last_execute_operation = query
72+
self._trace_method(self.__wrapped__.execute, self._self_datadog_name, query, {}, query, *args, **kwargs)
73+
return self
74+
75+
def fetchone(self, *args, **kwargs):
76+
""" Wraps the cursor.fetchone method"""
77+
span_name = '{}.{}'.format(self._self_datadog_name, 'fetchone')
78+
return self._trace_method(self.__wrapped__.fetchone, span_name, self._self_last_execute_operation, {},
79+
*args, **kwargs)
80+
81+
def fetchall(self, *args, **kwargs):
82+
""" Wraps the cursor.fetchall method"""
83+
span_name = '{}.{}'.format(self._self_datadog_name, 'fetchall')
84+
return self._trace_method(self.__wrapped__.fetchall, span_name, self._self_last_execute_operation, {},
85+
*args, **kwargs)
86+
87+
def fetchmany(self, *args, **kwargs):
88+
""" Wraps the cursor.fetchmany method"""
89+
span_name = '{}.{}'.format(self._self_datadog_name, 'fetchmany')
90+
# We want to trace the information about how many rows were requested. Note that this number may be larger
91+
# the number of rows actually returned if less then requested are available from the query.
92+
size_tag_key = 'db.fetch.size'
93+
if 'size' in kwargs:
94+
extra_tags = {size_tag_key: kwargs.get('size')}
95+
elif len(args) == 1 and isinstance(args[0], int):
96+
extra_tags = {size_tag_key: args[0]}
97+
else:
98+
default_array_size = getattr(self.__wrapped__, 'arraysize', None)
99+
extra_tags = {size_tag_key: default_array_size} if default_array_size else {}
100+
101+
return self._trace_method(self.__wrapped__.fetchmany, span_name, self._self_last_execute_operation, extra_tags,
102+
*args, **kwargs)
55103

56104
def callproc(self, proc, args):
57-
return self._trace_method(self.__wrapped__.callproc, proc, {}, proc,
58-
args)
105+
""" Wraps the cursor.callproc method"""
106+
self._self_last_execute_operation = proc
107+
return self._trace_method(self.__wrapped__.callproc, self._self_datadog_name, proc, {}, proc, args)
59108

60109
def __enter__(self):
61110
# previous versions of the dbapi didn't support context managers. let's
@@ -73,16 +122,36 @@ class TracedConnection(wrapt.ObjectProxy):
73122
def __init__(self, conn, pin=None):
74123
super(TracedConnection, self).__init__(conn)
75124
name = _get_vendor(conn)
125+
self._self_datadog_name = '{}.connection'.format(name)
76126
db_pin = pin or Pin(service=name, app=name, app_type=AppTypes.db)
77127
db_pin.onto(self)
78128

129+
def _trace_method(self, method, name, extra_tags, *args, **kwargs):
130+
pin = Pin.get_from(self)
131+
if not pin or not pin.enabled():
132+
return method(*args, **kwargs)
133+
service = pin.service
134+
135+
with pin.tracer.trace(name, service=service) as s:
136+
s.set_tags(pin.tags)
137+
s.set_tags(extra_tags)
138+
139+
return method(*args, **kwargs)
140+
79141
def cursor(self, *args, **kwargs):
80142
cursor = self.__wrapped__.cursor(*args, **kwargs)
81143
pin = Pin.get_from(self)
82144
if not pin:
83145
return cursor
84146
return TracedCursor(cursor, pin)
85147

148+
def commit(self, *args, **kwargs):
149+
span_name = '{}.{}'.format(self._self_datadog_name, 'commit')
150+
return self._trace_method(self.__wrapped__.commit, span_name, {}, *args, **kwargs)
151+
152+
def rollback(self, *args, **kwargs):
153+
span_name = '{}.{}'.format(self._self_datadog_name, 'rollback')
154+
return self._trace_method(self.__wrapped__.rollback, span_name, {}, *args, **kwargs)
86155

87156
def _get_vendor(conn):
88157
""" Return the vendor (e.g postgres, mysql) of the given

ddtrace/contrib/django/cache.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ def patch_cache(tracer):
4141
Django supported cache servers (Redis, Memcached, Database, Custom)
4242
"""
4343
# discover used cache backends
44-
cache_backends = [cache['BACKEND'] for cache in django_settings.CACHES.values()]
44+
cache_backends = set([cache['BACKEND'] for cache in django_settings.CACHES.values()])
4545

4646
def _trace_operation(fn, method_name):
4747
"""
@@ -102,7 +102,7 @@ def unpatch_method(cls, method_name):
102102
delattr(cls, DATADOG_NAMESPACE.format(method=method_name))
103103

104104
def unpatch_cache():
105-
cache_backends = [cache['BACKEND'] for cache in django_settings.CACHES.values()]
105+
cache_backends = set([cache['BACKEND'] for cache in django_settings.CACHES.values()])
106106
for cache_module in cache_backends:
107107
cache = import_from_string(cache_module, cache_module)
108108

ddtrace/contrib/django/conf.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -87,15 +87,19 @@ def __init__(self, user_settings=None, defaults=None, import_strings=None):
8787
self.defaults['TAGS'].update({'env': os.environ.get('DATADOG_ENV')})
8888
if os.environ.get('DATADOG_SERVICE_NAME'):
8989
self.defaults['DEFAULT_SERVICE'] = os.environ.get('DATADOG_SERVICE_NAME')
90-
if os.environ.get('DATADOG_TRACE_AGENT_HOSTNAME'):
91-
self.defaults['AGENT_HOSTNAME'] = os.environ.get('DATADOG_TRACE_AGENT_HOSTNAME')
92-
if os.environ.get('DATADOG_TRACE_AGENT_PORT'):
90+
91+
host = os.environ.get('DD_AGENT_HOST', os.environ.get('DATADOG_TRACE_AGENT_HOSTNAME'))
92+
if host:
93+
self.defaults['AGENT_HOSTNAME'] = host
94+
95+
port = os.environ.get('DD_TRACE_AGENT_PORT', os.environ.get('DATADOG_TRACE_AGENT_PORT'))
96+
if port:
9397
# if the agent port is a string, the underlying library that creates the socket
9498
# stops working
9599
try:
96-
port = int(os.environ.get('DATADOG_TRACE_AGENT_PORT'))
100+
port = int(port)
97101
except ValueError:
98-
log.warning('DATADOG_TRACE_AGENT_PORT is not an integer value; default to 8126')
102+
log.warning('DD_TRACE_AGENT_PORT is not an integer value; default to 8126')
99103
else:
100104
self.defaults['AGENT_PORT'] = port
101105

ddtrace/contrib/django/db.py

Lines changed: 24 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
from ...ext import AppTypes
99

1010
from .conf import settings
11-
11+
from ..dbapi import TracedCursor as DbApiTracedCursor
12+
from ddtrace import Pin
1213

1314
log = logging.getLogger(__name__)
1415

@@ -30,6 +31,7 @@ def all_connections(self):
3031

3132
connections.all = all_connections.__get__(connections, type(connections))
3233

34+
3335
def unpatch_db():
3436
for c in connections.all():
3537
unpatch_conn(c)
@@ -41,95 +43,42 @@ def unpatch_db():
4143
connections.all = all_connections
4244
delattr(connections, ALL_CONNS_ATTR)
4345

46+
4447
def patch_conn(tracer, conn):
4548
if hasattr(conn, CURSOR_ATTR):
4649
return
4750

4851
setattr(conn, CURSOR_ATTR, conn.cursor)
4952

5053
def cursor():
51-
return TracedCursor(tracer, conn, conn._datadog_original_cursor())
52-
53-
conn.cursor = cursor
54-
55-
def unpatch_conn(conn):
56-
cursor = getattr(conn, CURSOR_ATTR, None)
57-
if cursor is None:
58-
log.debug('nothing to do, the connection is not patched')
59-
return
60-
conn.cursor = cursor
61-
delattr(conn, CURSOR_ATTR)
62-
63-
class TracedCursor(object):
64-
65-
def __init__(self, tracer, conn, cursor):
66-
self.tracer = tracer
67-
self.conn = conn
68-
self.cursor = cursor
69-
70-
self._vendor = getattr(conn, 'vendor', 'db') # e.g sqlite, postgres
71-
self._alias = getattr(conn, 'alias', 'default') # e.g. default, users
72-
73-
prefix = sqlx.normalize_vendor(self._vendor)
74-
self._name = "%s.%s" % (prefix, "query") # e.g sqlite.query
75-
7654
database_prefix = (
7755
'{}-'.format(settings.DEFAULT_DATABASE_PREFIX)
7856
if settings.DEFAULT_DATABASE_PREFIX else ''
7957
)
80-
81-
self._service = "%s%s%s" % (
82-
database_prefix,
83-
self._alias,
84-
"db"
85-
) # e.g. service-defaultdb or service-postgresdb
86-
87-
self.tracer.set_service_info(
88-
service=self._service,
58+
alias = getattr(conn, 'alias', 'default')
59+
service = '{}{}{}'.format(database_prefix, alias, 'db')
60+
vendor = getattr(conn, 'vendor', 'db')
61+
prefix = sqlx.normalize_vendor(vendor)
62+
tags = {
63+
'django.db.vendor': vendor,
64+
'django.db.alias': alias,
65+
}
66+
tracer.set_service_info(
67+
service=service,
8968
app=prefix,
9069
app_type=AppTypes.db,
9170
)
9271

93-
def _trace(self, func, sql, params):
94-
span = self.tracer.trace(
95-
self._name,
96-
resource=sql,
97-
service=self._service,
98-
span_type=sqlx.TYPE
99-
)
100-
101-
with span:
102-
# No reason to tag the query since it is set as the resource by the agent. See:
103-
# https://github.com/DataDog/datadog-trace-agent/blob/bda1ebbf170dd8c5879be993bdd4dbae70d10fda/obfuscate/sql.go#L232
104-
span.set_tag("django.db.vendor", self._vendor)
105-
span.set_tag("django.db.alias", self._alias)
106-
try:
107-
return func(sql, params)
108-
finally:
109-
rows = self.cursor.cursor.rowcount
110-
if rows and 0 <= rows:
111-
span.set_tag(sqlx.ROWS, self.cursor.cursor.rowcount)
112-
113-
def callproc(self, procname, params=None):
114-
return self._trace(self.cursor.callproc, procname, params)
115-
116-
def execute(self, sql, params=None):
117-
return self._trace(self.cursor.execute, sql, params)
118-
119-
def executemany(self, sql, param_list):
120-
return self._trace(self.cursor.executemany, sql, param_list)
121-
122-
def close(self):
123-
return self.cursor.close()
124-
125-
def __getattr__(self, attr):
126-
return getattr(self.cursor, attr)
72+
pin = Pin(service, tags=tags, tracer=tracer, app=prefix)
73+
return DbApiTracedCursor(conn._datadog_original_cursor(), pin)
12774

128-
def __iter__(self):
129-
return iter(self.cursor)
75+
conn.cursor = cursor
13076

131-
def __enter__(self):
132-
return self
13377

134-
def __exit__(self, type, value, traceback):
135-
self.close()
78+
def unpatch_conn(conn):
79+
cursor = getattr(conn, CURSOR_ATTR, None)
80+
if cursor is None:
81+
log.debug('nothing to do, the connection is not patched')
82+
return
83+
conn.cursor = cursor
84+
delattr(conn, CURSOR_ATTR)

0 commit comments

Comments
 (0)