22Generic dbapi tracing code.
33"""
44
5- # stdlib
65import logging
76
8- # 3p
97import wrapt
108
11- # project
129from ddtrace import Pin
13- from ddtrace .ext import sql
14-
15- from ...ext import AppTypes
10+ from ddtrace .ext import AppTypes , sql
1611
1712log = 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
87156def _get_vendor (conn ):
88157 """ Return the vendor (e.g postgres, mysql) of the given
0 commit comments