Skip to content

Commit

Permalink
Merge branch 'main' into dbapi-db-statement-comment-opt-in
Browse files Browse the repository at this point in the history
  • Loading branch information
lzchen authored Jan 9, 2025
2 parents 4d0a449 + 26bcc93 commit 5e328b2
Show file tree
Hide file tree
Showing 5 changed files with 497 additions and 29 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `opentelemetry-instrumentation` Fix `get_dist_dependency_conflicts` if no distribution requires
([#3168](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3168))

### Breaking changes

- `opentelemetry-instrumentation-sqlalchemy` including sqlcomment in `db.statement` span attribute value is now opt-in
([#3112](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3112))

### Breaking changes

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,30 @@
::
Enabling this flag will add traceparent values /*traceparent='00-03afa25236b8cd948fa853d67038ac79-405ff022e8247c46-01'*/
SQLComment in span attribute
****************************
If sqlcommenter is enabled, you can further configure SQLAlchemy instrumentation to append sqlcomment to the `db.statement` span attribute for convenience of your platform.
.. code:: python
from opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor
SQLAlchemyInstrumentor().instrument(
enable_commenter=True,
commenter_options={},
enable_attribute_commenter=True,
)
For example,
::
Invoking `engine.execute("select * from auth_users")` will lead to sql query "select * from auth_users" but when SQLCommenter and `attribute_commenter` is enabled
the query will get appended with some configurable tags like "select * from auth_users /*tag=value*/;" for both server query and `db.statement` span attribute.
Warning: capture of sqlcomment in ``db.statement`` may have high cardinality without platform normalization. See `Semantic Conventions for database spans <https://opentelemetry.io/docs/specs/semconv/database/database-spans/#generating-a-summary-of-the-query-text>`_ for more information.
Usage
-----
.. code:: python
Expand Down Expand Up @@ -138,6 +162,7 @@ def _instrument(self, **kwargs):
``meter_provider``: a MeterProvider, defaults to global
``enable_commenter``: bool to enable sqlcommenter, defaults to False
``commenter_options``: dict of sqlcommenter config, defaults to {}
``enable_attribute_commenter``: bool to enable sqlcomment addition to span attribute, defaults to False. Must also set `enable_commenter`.
Returns:
An instrumented engine if passed in as an argument or list of instrumented engines, None otherwise.
Expand Down Expand Up @@ -166,19 +191,30 @@ def _instrument(self, **kwargs):

enable_commenter = kwargs.get("enable_commenter", False)
commenter_options = kwargs.get("commenter_options", {})
enable_attribute_commenter = kwargs.get(
"enable_attribute_commenter", False
)

_w(
"sqlalchemy",
"create_engine",
_wrap_create_engine(
tracer, connections_usage, enable_commenter, commenter_options
tracer,
connections_usage,
enable_commenter,
commenter_options,
enable_attribute_commenter,
),
)
_w(
"sqlalchemy.engine",
"create_engine",
_wrap_create_engine(
tracer, connections_usage, enable_commenter, commenter_options
tracer,
connections_usage,
enable_commenter,
commenter_options,
enable_attribute_commenter,
),
)
# sqlalchemy.engine.create is not present in earlier versions of sqlalchemy (which we support)
Expand All @@ -191,6 +227,7 @@ def _instrument(self, **kwargs):
connections_usage,
enable_commenter,
commenter_options,
enable_attribute_commenter,
),
)
_w(
Expand All @@ -207,6 +244,7 @@ def _instrument(self, **kwargs):
connections_usage,
enable_commenter,
commenter_options,
enable_attribute_commenter,
),
)
if kwargs.get("engine") is not None:
Expand All @@ -216,6 +254,7 @@ def _instrument(self, **kwargs):
connections_usage,
kwargs.get("enable_commenter", False),
kwargs.get("commenter_options", {}),
kwargs.get("enable_attribute_commenter", False),
)
if kwargs.get("engines") is not None and isinstance(
kwargs.get("engines"), Sequence
Expand All @@ -227,6 +266,7 @@ def _instrument(self, **kwargs):
connections_usage,
kwargs.get("enable_commenter", False),
kwargs.get("commenter_options", {}),
kwargs.get("enable_attribute_commenter", False),
)
for engine in kwargs.get("engines")
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,11 @@ def _normalize_vendor(vendor):


def _wrap_create_async_engine(
tracer, connections_usage, enable_commenter=False, commenter_options=None
tracer,
connections_usage,
enable_commenter=False,
commenter_options=None,
enable_attribute_commenter=False,
):
# pylint: disable=unused-argument
def _wrap_create_async_engine_internal(func, module, args, kwargs):
Expand All @@ -57,14 +61,19 @@ def _wrap_create_async_engine_internal(func, module, args, kwargs):
connections_usage,
enable_commenter,
commenter_options,
enable_attribute_commenter,
)
return engine

return _wrap_create_async_engine_internal


def _wrap_create_engine(
tracer, connections_usage, enable_commenter=False, commenter_options=None
tracer,
connections_usage,
enable_commenter=False,
commenter_options=None,
enable_attribute_commenter=False,
):
def _wrap_create_engine_internal(func, _module, args, kwargs):
"""Trace the SQLAlchemy engine, creating an `EngineTracer`
Expand All @@ -77,6 +86,7 @@ def _wrap_create_engine_internal(func, _module, args, kwargs):
connections_usage,
enable_commenter,
commenter_options,
enable_attribute_commenter,
)
return engine

Expand Down Expand Up @@ -110,12 +120,14 @@ def __init__(
connections_usage,
enable_commenter=False,
commenter_options=None,
enable_attribute_commenter=False,
):
self.tracer = tracer
self.connections_usage = connections_usage
self.vendor = _normalize_vendor(engine.name)
self.enable_commenter = enable_commenter
self.commenter_options = commenter_options if commenter_options else {}
self.enable_attribute_commenter = enable_attribute_commenter
self._engine_attrs = _get_attributes_from_engine(engine)
self._leading_comment_remover = re.compile(r"^/\*.*?\*/")

Expand Down Expand Up @@ -218,6 +230,32 @@ def _operation_name(self, db_name, statement):
return self.vendor
return " ".join(parts)

def _get_commenter_data(self, conn) -> dict:
"""Calculate sqlcomment contents from conn and configured options"""
commenter_data = {
"db_driver": conn.engine.driver,
# Driver/framework centric information.
"db_framework": f"sqlalchemy:{sqlalchemy.__version__}",
}

if self.commenter_options.get("opentelemetry_values", True):
commenter_data.update(**_get_opentelemetry_values())

# Filter down to just the requested attributes.
commenter_data = {
k: v
for k, v in commenter_data.items()
if self.commenter_options.get(k, True)
}
return commenter_data

def _set_db_client_span_attributes(self, span, statement, attrs) -> None:
"""Uses statement and attrs to set attributes of provided Otel span"""
span.set_attribute(SpanAttributes.DB_STATEMENT, statement)
span.set_attribute(SpanAttributes.DB_SYSTEM, self.vendor)
for key, value in attrs.items():
span.set_attribute(key, value)

def _before_cur_exec(
self, conn, cursor, statement, params, context, _executemany
):
Expand All @@ -233,30 +271,30 @@ def _before_cur_exec(
with trace.use_span(span, end_on_exit=False):
if span.is_recording():
if self.enable_commenter:
commenter_data = {
"db_driver": conn.engine.driver,
# Driver/framework centric information.
"db_framework": f"sqlalchemy:{sqlalchemy.__version__}",
}

if self.commenter_options.get(
"opentelemetry_values", True
):
commenter_data.update(**_get_opentelemetry_values())

# Filter down to just the requested attributes.
commenter_data = {
k: v
for k, v in commenter_data.items()
if self.commenter_options.get(k, True)
}

statement = _add_sql_comment(statement, **commenter_data)

span.set_attribute(SpanAttributes.DB_STATEMENT, statement)
span.set_attribute(SpanAttributes.DB_SYSTEM, self.vendor)
for key, value in attrs.items():
span.set_attribute(key, value)
commenter_data = self._get_commenter_data(conn)

if self.enable_attribute_commenter:
# sqlcomment is added to executed query and db.statement span attribute
statement = _add_sql_comment(
statement, **commenter_data
)
self._set_db_client_span_attributes(
span, statement, attrs
)

else:
# sqlcomment is only added to executed query
# so db.statement is set before add_sql_comment
self._set_db_client_span_attributes(
span, statement, attrs
)
statement = _add_sql_comment(
statement, **commenter_data
)

else:
# no sqlcomment anywhere
self._set_db_client_span_attributes(span, statement, attrs)

context._otel_span = span

Expand Down
Loading

0 comments on commit 5e328b2

Please sign in to comment.