4242import functools
4343import logging
4444import re
45+ import sys
46+ import traceback
4547from typing import Any , Callable , Generic , TypeVar
4648
4749import wrapt
5456 _get_opentelemetry_values ,
5557 unwrap ,
5658)
57- from opentelemetry .semconv .trace import SpanAttributes
59+ from opentelemetry .semconv ._incubating .attributes .code_attributes import (
60+ CODE_STACKTRACE ,
61+ )
62+ from opentelemetry .semconv ._incubating .attributes .db_attributes import (
63+ DB_NAME ,
64+ DB_STATEMENT ,
65+ DB_SYSTEM ,
66+ DB_USER ,
67+ )
68+ from opentelemetry .semconv ._incubating .attributes .net_attributes import (
69+ NET_PEER_NAME ,
70+ NET_PEER_PORT ,
71+ )
5872from opentelemetry .trace import SpanKind , TracerProvider , get_tracer
5973from opentelemetry .util ._importlib_metadata import version as util_version
6074
@@ -78,6 +92,7 @@ def trace_integration(
7892 enable_commenter : bool = False ,
7993 db_api_integration_factory : type [DatabaseApiIntegration ] | None = None ,
8094 enable_attribute_commenter : bool = False ,
95+ enable_traceback : bool = False ,
8196):
8297 """Integrate with DB API library.
8398 https://www.python.org/dev/peps/pep-0249/
@@ -96,6 +111,7 @@ def trace_integration(
96111 db_api_integration_factory: The `DatabaseApiIntegration` to use. If none is passed the
97112 default one is used.
98113 enable_attribute_commenter: Flag to enable/disable sqlcomment inclusion in `db.statement` span attribute. Only available if enable_commenter=True.
114+ enable_traceback: Enable traceback for every trace.
99115 """
100116 wrap_connect (
101117 __name__ ,
@@ -109,6 +125,7 @@ def trace_integration(
109125 enable_commenter = enable_commenter ,
110126 db_api_integration_factory = db_api_integration_factory ,
111127 enable_attribute_commenter = enable_attribute_commenter ,
128+ enable_traceback = enable_traceback ,
112129 )
113130
114131
@@ -125,6 +142,7 @@ def wrap_connect(
125142 db_api_integration_factory : type [DatabaseApiIntegration ] | None = None ,
126143 commenter_options : dict [str , Any ] | None = None ,
127144 enable_attribute_commenter : bool = False ,
145+ enable_traceback : bool = False ,
128146):
129147 """Integrate with DB API library.
130148 https://www.python.org/dev/peps/pep-0249/
@@ -144,6 +162,7 @@ def wrap_connect(
144162 default one is used.
145163 commenter_options: Configurations for tags to be appended at the sql query.
146164 enable_attribute_commenter: Flag to enable/disable sqlcomment inclusion in `db.statement` span attribute. Only available if enable_commenter=True.
165+ enable_traceback: Enable traceback for every trace.
147166
148167 """
149168 db_api_integration_factory = (
@@ -168,6 +187,7 @@ def wrap_connect_(
168187 commenter_options = commenter_options ,
169188 connect_module = connect_module ,
170189 enable_attribute_commenter = enable_attribute_commenter ,
190+ enable_traceback = enable_traceback ,
171191 )
172192 return db_integration .wrapped_connection (wrapped , args , kwargs )
173193
@@ -204,6 +224,7 @@ def instrument_connection(
204224 commenter_options : dict [str , Any ] | None = None ,
205225 connect_module : Callable [..., Any ] | None = None ,
206226 enable_attribute_commenter : bool = False ,
227+ enable_traceback : bool = False ,
207228 db_api_integration_factory : type [DatabaseApiIntegration ] | None = None ,
208229) -> TracedConnectionProxy [ConnectionT ]:
209230 """Enable instrumentation in a database connection.
@@ -222,6 +243,7 @@ def instrument_connection(
222243 commenter_options: Configurations for tags to be appended at the sql query.
223244 connect_module: Module name where connect method is available.
224245 enable_attribute_commenter: Flag to enable/disable sqlcomment inclusion in `db.statement` span attribute. Only available if enable_commenter=True.
246+ enable_traceback: Enable traceback for every trace.
225247 db_api_integration_factory: A class or factory function to use as a
226248 replacement for :class:`DatabaseApiIntegration`. Can be used to
227249 obtain connection attributes from the connect method instead of
@@ -249,6 +271,7 @@ def instrument_connection(
249271 commenter_options = commenter_options ,
250272 connect_module = connect_module ,
251273 enable_attribute_commenter = enable_attribute_commenter ,
274+ enable_traceback = enable_traceback ,
252275 )
253276 db_integration .get_connection_attributes (connection )
254277 return get_traced_connection_proxy (connection , db_integration )
@@ -285,6 +308,7 @@ def __init__(
285308 commenter_options : dict [str , Any ] | None = None ,
286309 connect_module : Callable [..., Any ] | None = None ,
287310 enable_attribute_commenter : bool = False ,
311+ enable_traceback : bool = False ,
288312 ):
289313 if connection_attributes is None :
290314 self .connection_attributes = {
@@ -307,6 +331,7 @@ def __init__(
307331 self .enable_commenter = enable_commenter
308332 self .commenter_options = commenter_options
309333 self .enable_attribute_commenter = enable_attribute_commenter
334+ self .enable_traceback = enable_traceback
310335 self .database_system = database_system
311336 self .connection_props : dict [str , Any ] = {}
312337 self .span_attributes : dict [str , Any ] = {}
@@ -401,13 +426,13 @@ def get_connection_attributes(self, connection: object) -> None:
401426 if user and isinstance (user , bytes ):
402427 user = user .decode ()
403428 if user is not None :
404- self .span_attributes [SpanAttributes . DB_USER ] = str (user )
429+ self .span_attributes [DB_USER ] = str (user )
405430 host = self .connection_props .get ("host" )
406431 if host is not None :
407- self .span_attributes [SpanAttributes . NET_PEER_NAME ] = host
432+ self .span_attributes [NET_PEER_NAME ] = host
408433 port = self .connection_props .get ("port" )
409434 if port is not None :
410- self .span_attributes [SpanAttributes . NET_PEER_PORT ] = port
435+ self .span_attributes [NET_PEER_PORT ] = port
411436
412437
413438# pylint: disable=abstract-method
@@ -464,6 +489,7 @@ def __init__(self, db_api_integration: DatabaseApiIntegration) -> None:
464489 self ._enable_attribute_commenter = (
465490 self ._db_api_integration .enable_attribute_commenter
466491 )
492+ self ._enable_traceback = self ._db_api_integration .enable_traceback
467493 self ._connect_module = self ._db_api_integration .connect_module
468494 self ._leading_comment_remover = re .compile (r"^/\*.*?\*/" )
469495
@@ -516,13 +542,12 @@ def _populate_span(
516542 if not span .is_recording ():
517543 return
518544 statement = self .get_statement (cursor , args )
519- span .set_attribute (
520- SpanAttributes .DB_SYSTEM , self ._db_api_integration .database_system
521- )
522- span .set_attribute (
523- SpanAttributes .DB_NAME , self ._db_api_integration .database
524- )
525- span .set_attribute (SpanAttributes .DB_STATEMENT , statement )
545+ span .set_attribute (DB_SYSTEM , self ._db_api_integration .database_system )
546+ span .set_attribute (DB_NAME , self ._db_api_integration .database )
547+ span .set_attribute (DB_STATEMENT , statement )
548+
549+ if self ._enable_traceback and (tb := self .get_traceback ()):
550+ span .set_attribute (CODE_STACKTRACE , tb )
526551
527552 for (
528553 attribute_key ,
@@ -549,6 +574,24 @@ def get_statement(self, cursor: CursorT, args: tuple[Any, ...]): # pylint: disa
549574 return statement .decode ("utf8" , "replace" )
550575 return statement
551576
577+ def get_traceback (self ):
578+ filtered_stack_trace = []
579+ for frame , lineno in traceback .walk_stack (
580+ sys ._getframe ().f_back .f_back .f_back
581+ ):
582+ filename = frame .f_code .co_filename
583+ # if frame.f_locals.get("__name__", "").startswith("jobs"):
584+ frame_summary = traceback .FrameSummary (
585+ filename , lineno , frame .f_code .co_name
586+ )
587+ filtered_stack_trace .append (frame_summary )
588+
589+ if filtered_stack_trace :
590+ formatted_stack_trace = traceback .StackSummary .from_list (
591+ filtered_stack_trace
592+ ).format ()
593+ return "" .join (formatted_stack_trace )
594+
552595 def traced_execution (
553596 self ,
554597 cursor : CursorT ,
0 commit comments