mirror of
https://github.com/open-telemetry/opentelemetry-python-contrib.git
synced 2025-08-01 09:13:23 +08:00
Sqlcommenter integration into SQLAlchemy (#924)
* Integrating sqlcommenter into psycopg2 * Integrating sqlcommenter into psycopg2 - Converted public local variable into private * Added test cases for sqlcommenter & PR Changes * Code refactoring for generate sqlcommenter * Added testcase for sqlcommenter integration into sqlalchemy * updated change log * updated to accept latest logs * Updated lint changes * Fixed errors due to linting * Fixed linting errors * Fixed linting errors * Fixed linting errors * Update CHANGELOG.md * Update CHANGELOG.md Co-authored-by: Srikanth Chekuri <srikanth.chekuri92@gmail.com> Co-authored-by: Srikanth Chekuri <srikanth.chekuri92@gmail.com>
This commit is contained in:
@ -12,6 +12,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
|
- `opentelemetry-instrumentation-sqlalchemy` added experimental sql commenter capability
|
||||||
|
([#924](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/924))
|
||||||
- `opentelemetry-instrumentation-dbapi` add experimental sql commenter capability
|
- `opentelemetry-instrumentation-dbapi` add experimental sql commenter capability
|
||||||
([#908](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/908))
|
([#908](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/908))
|
||||||
- `opentelemetry-instrumentation-requests` make span attribute available to samplers
|
- `opentelemetry-instrumentation-requests` make span attribute available to samplers
|
||||||
|
@ -106,6 +106,7 @@ class SQLAlchemyInstrumentor(BaseInstrumentor):
|
|||||||
return EngineTracer(
|
return EngineTracer(
|
||||||
_get_tracer(kwargs.get("engine"), tracer_provider),
|
_get_tracer(kwargs.get("engine"), tracer_provider),
|
||||||
kwargs.get("engine"),
|
kwargs.get("engine"),
|
||||||
|
kwargs.get("enable_commenter", False),
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@ -17,7 +17,12 @@ from sqlalchemy.event import listen # pylint: disable=no-name-in-module
|
|||||||
|
|
||||||
from opentelemetry import trace
|
from opentelemetry import trace
|
||||||
from opentelemetry.instrumentation.sqlalchemy.version import __version__
|
from opentelemetry.instrumentation.sqlalchemy.version import __version__
|
||||||
|
from opentelemetry.instrumentation.utils import (
|
||||||
|
_generate_opentelemetry_traceparent,
|
||||||
|
_generate_sql_comment,
|
||||||
|
)
|
||||||
from opentelemetry.semconv.trace import NetTransportValues, SpanAttributes
|
from opentelemetry.semconv.trace import NetTransportValues, SpanAttributes
|
||||||
|
from opentelemetry.trace import Span
|
||||||
from opentelemetry.trace.status import Status, StatusCode
|
from opentelemetry.trace.status import Status, StatusCode
|
||||||
|
|
||||||
|
|
||||||
@ -70,12 +75,15 @@ def _wrap_create_engine(tracer_provider=None):
|
|||||||
|
|
||||||
|
|
||||||
class EngineTracer:
|
class EngineTracer:
|
||||||
def __init__(self, tracer, engine):
|
def __init__(self, tracer, engine, enable_commenter=False):
|
||||||
self.tracer = tracer
|
self.tracer = tracer
|
||||||
self.engine = engine
|
self.engine = engine
|
||||||
self.vendor = _normalize_vendor(engine.name)
|
self.vendor = _normalize_vendor(engine.name)
|
||||||
|
self.enable_commenter = enable_commenter
|
||||||
|
|
||||||
listen(engine, "before_cursor_execute", self._before_cur_exec)
|
listen(
|
||||||
|
engine, "before_cursor_execute", self._before_cur_exec, retval=True
|
||||||
|
)
|
||||||
listen(engine, "after_cursor_execute", _after_cur_exec)
|
listen(engine, "after_cursor_execute", _after_cur_exec)
|
||||||
listen(engine, "handle_error", _handle_error)
|
listen(engine, "handle_error", _handle_error)
|
||||||
|
|
||||||
@ -115,6 +123,18 @@ class EngineTracer:
|
|||||||
span.set_attribute(key, value)
|
span.set_attribute(key, value)
|
||||||
|
|
||||||
context._otel_span = span
|
context._otel_span = span
|
||||||
|
if self.enable_commenter:
|
||||||
|
statement = statement + EngineTracer._generate_comment(span=span)
|
||||||
|
|
||||||
|
return statement, params
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _generate_comment(span: Span) -> str:
|
||||||
|
span_context = span.get_span_context()
|
||||||
|
meta = {}
|
||||||
|
if span_context.is_valid:
|
||||||
|
meta.update(_generate_opentelemetry_traceparent(span))
|
||||||
|
return _generate_sql_comment(**meta)
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import logging
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@ -20,12 +21,17 @@ from sqlalchemy import create_engine
|
|||||||
|
|
||||||
from opentelemetry import trace
|
from opentelemetry import trace
|
||||||
from opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor
|
from opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor
|
||||||
|
from opentelemetry.instrumentation.sqlalchemy.engine import EngineTracer
|
||||||
from opentelemetry.sdk.resources import Resource
|
from opentelemetry.sdk.resources import Resource
|
||||||
from opentelemetry.sdk.trace import TracerProvider, export
|
from opentelemetry.sdk.trace import TracerProvider, export
|
||||||
from opentelemetry.test.test_base import TestBase
|
from opentelemetry.test.test_base import TestBase
|
||||||
|
|
||||||
|
|
||||||
class TestSqlalchemyInstrumentation(TestBase):
|
class TestSqlalchemyInstrumentation(TestBase):
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def inject_fixtures(self, caplog):
|
||||||
|
self.caplog = caplog # pylint: disable=attribute-defined-outside-init
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
super().tearDown()
|
super().tearDown()
|
||||||
SQLAlchemyInstrumentor().uninstrument()
|
SQLAlchemyInstrumentor().uninstrument()
|
||||||
@ -150,3 +156,22 @@ class TestSqlalchemyInstrumentation(TestBase):
|
|||||||
self.assertEqual(spans[0].kind, trace.SpanKind.CLIENT)
|
self.assertEqual(spans[0].kind, trace.SpanKind.CLIENT)
|
||||||
|
|
||||||
asyncio.get_event_loop().run_until_complete(run())
|
asyncio.get_event_loop().run_until_complete(run())
|
||||||
|
|
||||||
|
def test_generate_commenter(self):
|
||||||
|
logging.getLogger("sqlalchemy.engine").setLevel(logging.INFO)
|
||||||
|
engine = create_engine("sqlite:///:memory:")
|
||||||
|
SQLAlchemyInstrumentor().instrument(
|
||||||
|
engine=engine,
|
||||||
|
tracer_provider=self.tracer_provider,
|
||||||
|
enable_commenter=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
cnx = engine.connect()
|
||||||
|
cnx.execute("SELECT 1 + 1;").fetchall()
|
||||||
|
spans = self.memory_exporter.get_finished_spans()
|
||||||
|
self.assertEqual(len(spans), 1)
|
||||||
|
span = spans[0]
|
||||||
|
self.assertIn(
|
||||||
|
EngineTracer._generate_comment(span),
|
||||||
|
self.caplog.records[-2].getMessage(),
|
||||||
|
)
|
||||||
|
@ -0,0 +1,53 @@
|
|||||||
|
# Copyright The OpenTelemetry Authors
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from sqlalchemy import create_engine
|
||||||
|
|
||||||
|
from opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor
|
||||||
|
from opentelemetry.test.test_base import TestBase
|
||||||
|
|
||||||
|
|
||||||
|
class TestSqlalchemyInstrumentationWithSQLCommenter(TestBase):
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def inject_fixtures(self, caplog):
|
||||||
|
self.caplog = caplog # pylint: disable=attribute-defined-outside-init
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
super().tearDown()
|
||||||
|
SQLAlchemyInstrumentor().uninstrument()
|
||||||
|
|
||||||
|
def test_sqlcommenter_enabled(self):
|
||||||
|
engine = create_engine("sqlite:///:memory:")
|
||||||
|
SQLAlchemyInstrumentor().instrument(
|
||||||
|
engine=engine,
|
||||||
|
tracer_provider=self.tracer_provider,
|
||||||
|
enable_commenter=True,
|
||||||
|
)
|
||||||
|
cnx = engine.connect()
|
||||||
|
cnx.execute("SELECT 1;").fetchall()
|
||||||
|
self.assertRegex(
|
||||||
|
self.caplog.records[-2].getMessage(),
|
||||||
|
r"SELECT 1; /\*traceparent='\d{1,2}-[a-zA-Z0-9_]{32}-[a-zA-Z0-9_]{16}-\d{1,2}'\*/",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_sqlcommenter_disabled(self):
|
||||||
|
engine = create_engine("sqlite:///:memory:", echo=True)
|
||||||
|
SQLAlchemyInstrumentor().instrument(
|
||||||
|
engine=engine, tracer_provider=self.tracer_provider
|
||||||
|
)
|
||||||
|
cnx = engine.connect()
|
||||||
|
cnx.execute("SELECT 1;").fetchall()
|
||||||
|
|
||||||
|
self.assertEqual(self.caplog.records[-2].getMessage(), "SELECT 1;")
|
@ -23,7 +23,7 @@ from opentelemetry import context, trace
|
|||||||
# pylint: disable=E0611
|
# pylint: disable=E0611
|
||||||
from opentelemetry.context import _SUPPRESS_INSTRUMENTATION_KEY # noqa: F401
|
from opentelemetry.context import _SUPPRESS_INSTRUMENTATION_KEY # noqa: F401
|
||||||
from opentelemetry.propagate import extract
|
from opentelemetry.propagate import extract
|
||||||
from opentelemetry.trace import StatusCode
|
from opentelemetry.trace import Span, StatusCode
|
||||||
|
|
||||||
|
|
||||||
def extract_attributes_from_object(
|
def extract_attributes_from_object(
|
||||||
@ -152,3 +152,14 @@ def _url_quote(s): # pylint: disable=invalid-name
|
|||||||
# thus in our quoting, we need to escape it too to finally give
|
# thus in our quoting, we need to escape it too to finally give
|
||||||
# foo,bar --> foo%%2Cbar
|
# foo,bar --> foo%%2Cbar
|
||||||
return quoted.replace("%", "%%")
|
return quoted.replace("%", "%%")
|
||||||
|
|
||||||
|
|
||||||
|
def _generate_opentelemetry_traceparent(span: Span) -> str:
|
||||||
|
meta = {}
|
||||||
|
_version = "00"
|
||||||
|
_span_id = trace.format_span_id(span.context.span_id)
|
||||||
|
_trace_id = trace.format_trace_id(span.context.trace_id)
|
||||||
|
_flags = str(trace.TraceFlags.SAMPLED)
|
||||||
|
_traceparent = _version + "-" + _trace_id + "-" + _span_id + "-" + _flags
|
||||||
|
meta.update({"traceparent": _traceparent})
|
||||||
|
return meta
|
||||||
|
Reference in New Issue
Block a user