Add span for connection phase (#1134)

This commit is contained in:
shahargl
2022-07-17 18:39:15 +03:00
committed by GitHub
parent 9e2dbecedc
commit 2ce69a668f
9 changed files with 100 additions and 36 deletions

View File

@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added ### Added
- `opentelemetry-instrumentation-redis` add support to instrument RedisCluster clients - `opentelemetry-instrumentation-redis` add support to instrument RedisCluster clients
([#1177](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1177)) ([#1177](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1177))
- `opentelemetry-instrumentation-sqlalchemy` Added span for the connection phase ([#1133](https://github.com/open-telemetry/opentelemetry-python-contrib/issues/1133))
## [1.12.0rc2-0.32b0](https://github.com/open-telemetry/opentelemetry-python/releases/tag/v1.12.0rc2-0.32b0) - 2022-07-01 ## [1.12.0rc2-0.32b0](https://github.com/open-telemetry/opentelemetry-python/releases/tag/v1.12.0rc2-0.32b0) - 2022-07-01

View File

@ -56,12 +56,14 @@ from typing import Collection
import sqlalchemy import sqlalchemy
from packaging.version import parse as parse_version from packaging.version import parse as parse_version
from sqlalchemy.engine.base import Engine
from wrapt import wrap_function_wrapper as _w from wrapt import wrap_function_wrapper as _w
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
from opentelemetry.instrumentation.sqlalchemy.engine import ( from opentelemetry.instrumentation.sqlalchemy.engine import (
EngineTracer, EngineTracer,
_get_tracer, _get_tracer,
_wrap_connect,
_wrap_create_async_engine, _wrap_create_async_engine,
_wrap_create_engine, _wrap_create_engine,
) )
@ -97,13 +99,17 @@ class SQLAlchemyInstrumentor(BaseInstrumentor):
"create_engine", "create_engine",
_wrap_create_engine(tracer_provider), _wrap_create_engine(tracer_provider),
) )
_w(
"sqlalchemy.engine.base",
"Engine.connect",
_wrap_connect(tracer_provider),
)
if parse_version(sqlalchemy.__version__).release >= (1, 4): if parse_version(sqlalchemy.__version__).release >= (1, 4):
_w( _w(
"sqlalchemy.ext.asyncio", "sqlalchemy.ext.asyncio",
"create_async_engine", "create_async_engine",
_wrap_create_async_engine(tracer_provider), _wrap_create_async_engine(tracer_provider),
) )
if kwargs.get("engine") is not None: if kwargs.get("engine") is not None:
return EngineTracer( return EngineTracer(
_get_tracer(tracer_provider), _get_tracer(tracer_provider),
@ -127,5 +133,6 @@ class SQLAlchemyInstrumentor(BaseInstrumentor):
def _uninstrument(self, **kwargs): def _uninstrument(self, **kwargs):
unwrap(sqlalchemy, "create_engine") unwrap(sqlalchemy, "create_engine")
unwrap(sqlalchemy.engine, "create_engine") unwrap(sqlalchemy.engine, "create_engine")
unwrap(Engine, "connect")
if parse_version(sqlalchemy.__version__).release >= (1, 4): if parse_version(sqlalchemy.__version__).release >= (1, 4):
unwrap(sqlalchemy.ext.asyncio, "create_async_engine") unwrap(sqlalchemy.ext.asyncio, "create_async_engine")

View File

@ -77,6 +77,23 @@ def _wrap_create_engine(tracer_provider=None):
return _wrap_create_engine_internal return _wrap_create_engine_internal
def _wrap_connect(tracer_provider=None):
tracer = trace.get_tracer(
_instrumenting_module_name,
__version__,
tracer_provider=tracer_provider,
)
# pylint: disable=unused-argument
def _wrap_connect_internal(func, module, args, kwargs):
with tracer.start_as_current_span(
"connect", kind=trace.SpanKind.CLIENT
):
return func(*args, **kwargs)
return _wrap_connect_internal
class EngineTracer: class EngineTracer:
def __init__(self, tracer, engine, enable_commenter=False): def __init__(self, tracer, engine, enable_commenter=False):
self.tracer = tracer self.tracer = tracer

View File

@ -46,9 +46,13 @@ class TestSqlalchemyInstrumentation(TestBase):
cnx.execute("SELECT 1 + 1;").fetchall() cnx.execute("SELECT 1 + 1;").fetchall()
spans = self.memory_exporter.get_finished_spans() spans = self.memory_exporter.get_finished_spans()
self.assertEqual(len(spans), 1) self.assertEqual(len(spans), 2)
self.assertEqual(spans[0].name, "SELECT :memory:") # first span - the connection to the db
self.assertEqual(spans[0].name, "connect")
self.assertEqual(spans[0].kind, trace.SpanKind.CLIENT) self.assertEqual(spans[0].kind, trace.SpanKind.CLIENT)
# second span - the query itself
self.assertEqual(spans[1].name, "SELECT :memory:")
self.assertEqual(spans[1].kind, trace.SpanKind.CLIENT)
def test_instrument_two_engines(self): def test_instrument_two_engines(self):
engine_1 = create_engine("sqlite:///:memory:") engine_1 = create_engine("sqlite:///:memory:")
@ -65,8 +69,20 @@ class TestSqlalchemyInstrumentation(TestBase):
cnx_2.execute("SELECT 1 + 1;").fetchall() cnx_2.execute("SELECT 1 + 1;").fetchall()
spans = self.memory_exporter.get_finished_spans() spans = self.memory_exporter.get_finished_spans()
# 2 queries + 2 engine connect
self.assertEqual(len(spans), 4)
self.assertEqual(len(spans), 2) def test_instrument_engine_connect(self):
engine = create_engine("sqlite:///:memory:")
SQLAlchemyInstrumentor().instrument(
engine=engine,
tracer_provider=self.tracer_provider,
)
engine.connect()
spans = self.memory_exporter.get_finished_spans()
self.assertEqual(len(spans), 1)
@pytest.mark.skipif( @pytest.mark.skipif(
not sqlalchemy.__version__.startswith("1.4"), not sqlalchemy.__version__.startswith("1.4"),
@ -85,11 +101,15 @@ class TestSqlalchemyInstrumentation(TestBase):
async with engine.connect() as cnx: async with engine.connect() as cnx:
await cnx.execute(sqlalchemy.text("SELECT 1 + 1;")) await cnx.execute(sqlalchemy.text("SELECT 1 + 1;"))
spans = self.memory_exporter.get_finished_spans() spans = self.memory_exporter.get_finished_spans()
self.assertEqual(len(spans), 1) self.assertEqual(len(spans), 2)
self.assertEqual(spans[0].name, "SELECT :memory:") # first span - the connection to the db
self.assertEqual(spans[0].name, "connect")
self.assertEqual(spans[0].kind, trace.SpanKind.CLIENT) self.assertEqual(spans[0].kind, trace.SpanKind.CLIENT)
# second span - the query
self.assertEqual(spans[1].name, "SELECT :memory:")
self.assertEqual(spans[1].kind, trace.SpanKind.CLIENT)
self.assertEqual( self.assertEqual(
spans[0].instrumentation_scope.name, spans[1].instrumentation_scope.name,
"opentelemetry.instrumentation.sqlalchemy", "opentelemetry.instrumentation.sqlalchemy",
) )
@ -99,7 +119,10 @@ class TestSqlalchemyInstrumentation(TestBase):
mock_tracer = mock.Mock() mock_tracer = mock.Mock()
mock_span = mock.Mock() mock_span = mock.Mock()
mock_span.is_recording.return_value = False mock_span.is_recording.return_value = False
mock_span.__enter__ = mock.Mock(return_value=(mock.Mock(), None))
mock_span.__exit__ = mock.Mock(return_value=None)
mock_tracer.start_span.return_value = mock_span mock_tracer.start_span.return_value = mock_span
mock_tracer.start_as_current_span.return_value = mock_span
with mock.patch("opentelemetry.trace.get_tracer") as tracer: with mock.patch("opentelemetry.trace.get_tracer") as tracer:
tracer.return_value = mock_tracer tracer.return_value = mock_tracer
engine = create_engine("sqlite:///:memory:") engine = create_engine("sqlite:///:memory:")
@ -123,11 +146,15 @@ class TestSqlalchemyInstrumentation(TestBase):
cnx.execute("SELECT 1 + 1;").fetchall() cnx.execute("SELECT 1 + 1;").fetchall()
spans = self.memory_exporter.get_finished_spans() spans = self.memory_exporter.get_finished_spans()
self.assertEqual(len(spans), 1) self.assertEqual(len(spans), 2)
self.assertEqual(spans[0].name, "SELECT :memory:") # first span - the connection to the db
self.assertEqual(spans[0].name, "connect")
self.assertEqual(spans[0].kind, trace.SpanKind.CLIENT) self.assertEqual(spans[0].kind, trace.SpanKind.CLIENT)
# second span - the query
self.assertEqual(spans[1].name, "SELECT :memory:")
self.assertEqual(spans[1].kind, trace.SpanKind.CLIENT)
self.assertEqual( self.assertEqual(
spans[0].instrumentation_scope.name, spans[1].instrumentation_scope.name,
"opentelemetry.instrumentation.sqlalchemy", "opentelemetry.instrumentation.sqlalchemy",
) )
@ -153,7 +180,7 @@ class TestSqlalchemyInstrumentation(TestBase):
cnx.execute("SELECT 1 + 1;").fetchall() cnx.execute("SELECT 1 + 1;").fetchall()
spans = self.memory_exporter.get_finished_spans() spans = self.memory_exporter.get_finished_spans()
self.assertEqual(len(spans), 1) self.assertEqual(len(spans), 2)
self.assertEqual(spans[0].resource.attributes["service.name"], "test") self.assertEqual(spans[0].resource.attributes["service.name"], "test")
self.assertEqual( self.assertEqual(
spans[0].resource.attributes["deployment.environment"], "env" spans[0].resource.attributes["deployment.environment"], "env"
@ -177,11 +204,15 @@ class TestSqlalchemyInstrumentation(TestBase):
async with engine.connect() as cnx: async with engine.connect() as cnx:
await cnx.execute(sqlalchemy.text("SELECT 1 + 1;")) await cnx.execute(sqlalchemy.text("SELECT 1 + 1;"))
spans = self.memory_exporter.get_finished_spans() spans = self.memory_exporter.get_finished_spans()
self.assertEqual(len(spans), 1) self.assertEqual(len(spans), 2)
self.assertEqual(spans[0].name, "SELECT :memory:") # first span - the connection to the db
self.assertEqual(spans[0].name, "connect")
self.assertEqual(spans[0].kind, trace.SpanKind.CLIENT) self.assertEqual(spans[0].kind, trace.SpanKind.CLIENT)
# second span - the query
self.assertEqual(spans[1].name, "SELECT :memory:")
self.assertEqual(spans[1].kind, trace.SpanKind.CLIENT)
self.assertEqual( self.assertEqual(
spans[0].instrumentation_scope.name, spans[1].instrumentation_scope.name,
"opentelemetry.instrumentation.sqlalchemy", "opentelemetry.instrumentation.sqlalchemy",
) )
@ -199,8 +230,8 @@ class TestSqlalchemyInstrumentation(TestBase):
cnx = engine.connect() cnx = engine.connect()
cnx.execute("SELECT 1 + 1;").fetchall() cnx.execute("SELECT 1 + 1;").fetchall()
spans = self.memory_exporter.get_finished_spans() spans = self.memory_exporter.get_finished_spans()
self.assertEqual(len(spans), 1) self.assertEqual(len(spans), 2)
span = spans[0] span = spans[1]
self.assertIn( self.assertIn(
EngineTracer._generate_comment(span), EngineTracer._generate_comment(span),
self.caplog.records[-2].getMessage(), self.caplog.records[-2].getMessage(),

View File

@ -128,8 +128,9 @@ class SQLAlchemyTestMixin(TestBase):
self.session.commit() self.session.commit()
spans = self.memory_exporter.get_finished_spans() spans = self.memory_exporter.get_finished_spans()
self.assertEqual(len(spans), 1) # one span for the connection and one for the query
span = spans[0] self.assertEqual(len(spans), 2)
span = spans[1]
stmt = "INSERT INTO players (id, name) VALUES " stmt = "INSERT INTO players (id, name) VALUES "
if span.attributes.get(SpanAttributes.DB_SYSTEM) == "sqlite": if span.attributes.get(SpanAttributes.DB_SYSTEM) == "sqlite":
stmt += "(?, ?)" stmt += "(?, ?)"
@ -148,8 +149,9 @@ class SQLAlchemyTestMixin(TestBase):
self.assertEqual(len(out), 0) self.assertEqual(len(out), 0)
spans = self.memory_exporter.get_finished_spans() spans = self.memory_exporter.get_finished_spans()
self.assertEqual(len(spans), 1) # one span for the connection and one for the query
span = spans[0] self.assertEqual(len(spans), 2)
span = spans[1]
stmt = "SELECT players.id AS players_id, players.name AS players_name \nFROM players \nWHERE players.name = " stmt = "SELECT players.id AS players_id, players.name AS players_name \nFROM players \nWHERE players.name = "
if span.attributes.get(SpanAttributes.DB_SYSTEM) == "sqlite": if span.attributes.get(SpanAttributes.DB_SYSTEM) == "sqlite":
stmt += "?" stmt += "?"
@ -170,8 +172,9 @@ class SQLAlchemyTestMixin(TestBase):
self.assertEqual(len(rows), 0) self.assertEqual(len(rows), 0)
spans = self.memory_exporter.get_finished_spans() spans = self.memory_exporter.get_finished_spans()
self.assertEqual(len(spans), 1) # one span for the connection and one for the query
span = spans[0] self.assertEqual(len(spans), 2)
span = spans[1]
self._check_span(span, "SELECT") self._check_span(span, "SELECT")
self.assertEqual( self.assertEqual(
span.attributes.get(SpanAttributes.DB_STATEMENT), span.attributes.get(SpanAttributes.DB_STATEMENT),
@ -190,8 +193,9 @@ class SQLAlchemyTestMixin(TestBase):
self.assertEqual(len(rows), 0) self.assertEqual(len(rows), 0)
spans = self.memory_exporter.get_finished_spans() spans = self.memory_exporter.get_finished_spans()
self.assertEqual(len(spans), 2) # one span for the connection and two for the queries
child_span, parent_span = spans self.assertEqual(len(spans), 3)
_, child_span, parent_span = spans
# confirm the parenting # confirm the parenting
self.assertIsNone(parent_span.parent) self.assertIsNone(parent_span.parent)
@ -247,5 +251,5 @@ class SQLAlchemyTestMixin(TestBase):
# batch inserts together which means `insert_players` only generates one span. # batch inserts together which means `insert_players` only generates one span.
# See https://docs.sqlalchemy.org/en/14/changelog/migration_14.html#orm-batch-inserts-with-psycopg2-now-batch-statements-with-returning-in-most-cases # See https://docs.sqlalchemy.org/en/14/changelog/migration_14.html#orm-batch-inserts-with-psycopg2-now-batch-statements-with-returning-in-most-cases
self.assertEqual( self.assertEqual(
len(spans), 5 if self.VENDOR not in ["postgresql"] else 3 len(spans), 8 if self.VENDOR not in ["postgresql"] else 6
) )

View File

@ -69,8 +69,9 @@ class MssqlConnectorTestCase(SQLAlchemyTestMixin):
conn.execute("SELECT * FROM a_wrong_table").fetchall() conn.execute("SELECT * FROM a_wrong_table").fetchall()
spans = self.memory_exporter.get_finished_spans() spans = self.memory_exporter.get_finished_spans()
self.assertEqual(len(spans), 1) # one span for the connection and one for the query
span = spans[0] self.assertEqual(len(spans), 2)
span = spans[1]
# span fields # span fields
self.assertEqual(span.name, "SELECT opentelemetry-tests") self.assertEqual(span.name, "SELECT opentelemetry-tests")
self.assertEqual( self.assertEqual(
@ -96,9 +97,9 @@ class MssqlConnectorTestCase(SQLAlchemyTestMixin):
self.session.commit() self.session.commit()
spans = self.memory_exporter.get_finished_spans() spans = self.memory_exporter.get_finished_spans()
# identity insert on before the insert, insert, and identity insert off after the insert # connect, identity insert on before the insert, insert, and identity insert off after the insert
self.assertEqual(len(spans), 3) self.assertEqual(len(spans), 4)
span = spans[1] span = spans[2]
self._check_span(span, "INSERT") self._check_span(span, "INSERT")
self.assertIn( self.assertIn(
"INSERT INTO players", "INSERT INTO players",

View File

@ -68,8 +68,9 @@ class MysqlConnectorTestCase(SQLAlchemyTestMixin):
conn.execute("SELECT * FROM a_wrong_table").fetchall() conn.execute("SELECT * FROM a_wrong_table").fetchall()
spans = self.memory_exporter.get_finished_spans() spans = self.memory_exporter.get_finished_spans()
self.assertEqual(len(spans), 1) # one span for the connection and one for the query
span = spans[0] self.assertEqual(len(spans), 2)
span = spans[1]
# span fields # span fields
self.assertEqual(span.name, "SELECT opentelemetry-tests") self.assertEqual(span.name, "SELECT opentelemetry-tests")
self.assertEqual( self.assertEqual(

View File

@ -62,8 +62,9 @@ class PostgresTestCase(SQLAlchemyTestMixin):
conn.execute("SELECT * FROM a_wrong_table").fetchall() conn.execute("SELECT * FROM a_wrong_table").fetchall()
spans = self.memory_exporter.get_finished_spans() spans = self.memory_exporter.get_finished_spans()
self.assertEqual(len(spans), 1) # one span for the connection and one for the query
span = spans[0] self.assertEqual(len(spans), 2)
span = spans[1]
# span fields # span fields
self.assertEqual(span.name, "SELECT opentelemetry-tests") self.assertEqual(span.name, "SELECT opentelemetry-tests")
self.assertEqual( self.assertEqual(

View File

@ -38,8 +38,9 @@ class SQLiteTestCase(SQLAlchemyTestMixin):
conn.execute(stmt).fetchall() conn.execute(stmt).fetchall()
spans = self.memory_exporter.get_finished_spans() spans = self.memory_exporter.get_finished_spans()
self.assertEqual(len(spans), 1) # one span for the connection and one span for the query
span = spans[0] self.assertEqual(len(spans), 2)
span = spans[1]
# span fields # span fields
self.assertEqual(span.name, "SELECT :memory:") self.assertEqual(span.name, "SELECT :memory:")
self.assertEqual( self.assertEqual(