11from sqlalchemy .event import listen , remove
2+ from sqlalchemy .orm import Session
23
34g_tracer = None
45g_trace_all = True
@@ -17,92 +18,138 @@ def init_tracing(tracer, trace_all=False):
1718
1819 g_trace_all = trace_all
1920
20- def get_traced (stmt_obj ):
21+ def get_traced (obj ):
2122 '''
2223 Gets a bool indicating whether or not this
23- statement is marked for tracing.
24+ object is marked for tracing.
2425 '''
25- return getattr (stmt_obj , '_traced' , False )
26+ return getattr (obj , '_traced' , False )
2627
27- def set_traced (stmt_obj ):
28+ def set_traced (obj ):
2829 '''
29- Mark a statement to be traced.
30+ Mark a statement/session to be traced.
3031 '''
31- stmt_obj ._traced = True
32+ obj ._traced = True
3233
33- def get_parent_span (stmt_obj ):
34+ # Session needs to have its connection/statements
35+ # decorated as soon as a connection is acquired.
36+ if isinstance (obj , Session ):
37+ _register_session_connection_event (obj )
38+
39+ def get_parent_span (obj ):
3440 '''
35- Gets a parent span for this statement , if any.
41+ Gets a parent span for this object , if any.
3642 '''
37- return getattr (stmt_obj , '_parent_span' , None )
43+ return getattr (obj , '_parent_span' , None )
3844
39- def set_parent_span (stmt_obj , parent_span ):
45+ def set_parent_span (obj , parent_span ):
4046 '''
41- Marks a statement as a child of a span.
47+ Marks an object as a child of a span.
4248 It gets marked to be traced if it wasn't before.
4349 '''
44- stmt_obj ._parent_span = parent_span
45- stmt_obj . _traced = True
50+ obj ._parent_span = parent_span
51+ set_traced ( obj )
4652
47- def has_parent_span (stmt_obj ):
53+ def has_parent_span (obj ):
4854 '''
4955 Get whether or not the statement has
5056 a parent span.
5157 '''
52- return hasattr (stmt_obj , '_parent_span' )
58+ return hasattr (obj , '_parent_span' )
5359
54- def get_span (stmt_obj ):
60+ def get_span (obj ):
5561 '''
5662 Get the span of a statement object, if any.
5763 '''
58- return getattr (stmt_obj , '_span' , None )
64+ return getattr (obj , '_span' , None )
5965
6066def register_connectable (obj ):
6167 '''
6268 Register an object to have its events be traced.
6369 Any Connectable object is accepted, which
6470 includes Connection and Engine.
6571 '''
66- listen (obj , 'before_cursor_execute' , _before_cursor_handler )
67- listen (obj , 'after_cursor_execute' , _after_cursor_handler )
68- listen (obj , 'handle_error' , _error_handler )
72+ listen (obj , 'before_cursor_execute' , _connectable_before_cursor_handler )
73+ listen (obj , 'after_cursor_execute' , _connectable_after_cursor_handler )
74+ listen (obj , 'handle_error' , _connectable_error_handler )
6975
7076def unregister_connectable (obj ):
7177 '''
7278 Remove a connectable from having its events being
7379 traced.
7480 '''
75- remove (obj , 'before_cursor_execute' , _before_cursor_handler )
76- remove (obj , 'after_cursor_execute' , _after_cursor_handler )
77- remove (obj , 'handle_error' , _error_handler )
81+ remove (obj , 'before_cursor_execute' , _connectable_before_cursor_handler )
82+ remove (obj , 'after_cursor_execute' , _connectable_after_cursor_handler )
83+ remove (obj , 'handle_error' , _connectable_error_handler )
84+
85+ def _can_operation_be_traced (conn , stmt_obj ):
86+ '''
87+ Get whether an operation can be traced, depending on its
88+ connection or the statement being executed, having the latter
89+ the priority.
90+ '''
91+ if hasattr (stmt_obj , '_traced' ):
92+ return stmt_obj ._traced
93+ if hasattr (conn , '_traced' ):
94+ return conn ._traced
95+
96+ return False
97+
98+ def _clear_traced (obj ):
99+ '''
100+ Clear an object's decorated tracing fields,
101+ to prevent unintended further tracing.
102+ '''
103+ if hasattr (obj , '_parent_span' ):
104+ del obj ._parent_span
105+ if hasattr (obj , '_traced' ):
106+ del obj ._traced
107+
108+ def _set_traced_with_session (conn , session ):
109+ '''
110+ Mark a connection to be traced with a session tracing information.
111+ '''
112+ conn ._traced = True
113+ parent_span = get_parent_span (session )
114+ if parent_span is not None :
115+ conn ._parent_span = parent_span
78116
79117def _get_operation_name (stmt_obj ):
80118 return stmt_obj .__visit_name__
81119
82120def _normalize_stmt (statement ):
83121 return statement .strip ().replace ('\n ' , '' ).replace ('\t ' , '' )
84122
85- def _before_cursor_handler (conn , cursor , statement , parameters , context , executemany ):
123+ def _connectable_before_cursor_handler (conn , cursor ,
124+ statement , parameters ,
125+ context , executemany ):
86126 if context .compiled is None : # PRAGMA
87127 return
88128
89- # Don't trace if trace_all is disabled and the statement wasn't marked
129+ # Don't trace if trace_all is disabled
130+ # and the connection/statement wasn't marked explicitly.
90131 stmt_obj = context .compiled .statement
91- if not (g_trace_all or get_traced ( stmt_obj )):
132+ if not (g_trace_all or _can_operation_be_traced ( conn , stmt_obj )):
92133 return
93134
135+ # Retrieve the parent span, if any,
136+ # either from the statement or inherited from the connection.
94137 parent_span = get_parent_span (stmt_obj )
95- operation_name = _get_operation_name (stmt_obj )
138+ if parent_span is None :
139+ parent_span = get_parent_span (conn )
96140
97141 # Start a new span for this query.
98- span = g_tracer .start_span (operation_name = operation_name , child_of = parent_span )
142+ name = _get_operation_name (stmt_obj )
143+ span = g_tracer .start_span (operation_name = name , child_of = parent_span )
99144 span .set_tag ('component' , 'sqlalchemy' )
100145 span .set_tag ('db.type' , 'sql' )
101146 span .set_tag ('db.statement' , _normalize_stmt (statement ))
102147
103148 stmt_obj ._span = span
104149
105- def _after_cursor_handler (conn , cursor , statement , parameters , context , executemany ):
150+ def _connectable_after_cursor_handler (conn , cursor ,
151+ statement , parameters ,
152+ context , executemany ):
106153 if context .compiled is None : # PRAGMA
107154 return
108155
@@ -113,7 +160,7 @@ def _after_cursor_handler(conn, cursor, statement, parameters, context, executem
113160
114161 span .finish ()
115162
116- def _error_handler (exception_context ):
163+ def _connectable_error_handler (exception_context ):
117164 execution_context = exception_context .execution_context
118165 stmt_obj = execution_context .compiled .statement
119166 span = get_span (stmt_obj )
@@ -123,3 +170,28 @@ def _error_handler(exception_context):
123170 span .set_tag ('error' , 'true' )
124171 span .finish ()
125172
173+ def _register_session_connection_event (session ):
174+ listen (session , 'after_begin' , _session_after_begin_handler )
175+
176+ def _session_after_begin_handler (session , transaction , conn ):
177+ # Have the connections inherit the tracing info
178+ # from the session (including parent span, if any).
179+ _set_traced_with_session (conn , session )
180+
181+ # Plug post-operation clean up handlers.
182+ # The actual session commit/rollback is not traced by us.
183+ listen (session , 'before_commit' , _session_before_commit_handler , once = True )
184+ listen (session , 'after_rollback' , _session_rollback_handler , once = True )
185+
186+ # Event handlers can't remove themselves
187+ # Fine, as long as we only fire one of them *once*.
188+ def _session_before_commit_handler (session ):
189+ _clear_traced (session )
190+ remove (session , 'after_begin' , _session_after_begin_handler )
191+ remove (session , 'after_rollback' , _session_rollback_handler )
192+
193+ def _session_rollback_handler (session ):
194+ _clear_traced (session )
195+ remove (session , 'after_begin' , _session_after_begin_handler )
196+ remove (session , 'before_commit' , _session_before_commit_handler )
197+
0 commit comments