mirror of
https://github.com/open-telemetry/opentelemetry-python-contrib.git
synced 2025-08-02 11:31:52 +08:00

Changes: - Update dbapi instrumentation to use the SQL statement name as the span instead of the entire SQL query. - Renamed TracedCursor with CursorTracing. The class was not a valid Cursor so the name was confusing. - Updated CursorTracing's (previously TracedCursor) traced_execution method to accept the cursor instance as the first argument. This is required as for some dbapi implementations, we need a reference to the cursor in order to correctly format the SQL query. - Updated psycopg2 instrumentation to leverage dbapi's `cursor_factory` mechanism instead of wrapping the cursor with wrapt. This results in a simpler instrumentation without monkey patching objects at runtime and allows psycopg2's type registration system to work. This should make it possible to use psycopg2 instrumentation when using the JSONB feature or with frameworks like Django.
193 lines
6.0 KiB
Python
193 lines
6.0 KiB
Python
# 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 types
|
|
from unittest import mock
|
|
|
|
import psycopg2
|
|
|
|
import opentelemetry.instrumentation.psycopg2
|
|
from opentelemetry.instrumentation.psycopg2 import Psycopg2Instrumentor
|
|
from opentelemetry.sdk import resources
|
|
from opentelemetry.test.test_base import TestBase
|
|
|
|
|
|
class MockCursor:
|
|
|
|
execute = mock.MagicMock(spec=types.MethodType)
|
|
execute.__name__ = "execute"
|
|
|
|
executemany = mock.MagicMock(spec=types.MethodType)
|
|
executemany.__name__ = "executemany"
|
|
|
|
callproc = mock.MagicMock(spec=types.MethodType)
|
|
callproc.__name__ = "callproc"
|
|
|
|
rowcount = "SomeRowCount"
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
pass
|
|
|
|
def __enter__(self):
|
|
return self
|
|
|
|
def __exit__(self, *args):
|
|
return self
|
|
|
|
|
|
class MockConnection:
|
|
|
|
commit = mock.MagicMock(spec=types.MethodType)
|
|
commit.__name__ = "commit"
|
|
|
|
rollback = mock.MagicMock(spec=types.MethodType)
|
|
rollback.__name__ = "rollback"
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
self.cursor_factory = kwargs.pop("cursor_factory", None)
|
|
|
|
def cursor(self):
|
|
if self.cursor_factory:
|
|
return self.cursor_factory(self)
|
|
return MockCursor()
|
|
|
|
def get_dsn_parameters(self): # pylint: disable=no-self-use
|
|
return dict(dbname="test")
|
|
|
|
|
|
class TestPostgresqlIntegration(TestBase):
|
|
def setUp(self):
|
|
self.cursor_mock = mock.patch(
|
|
"opentelemetry.instrumentation.psycopg2.pg_cursor", MockCursor
|
|
)
|
|
self.connection_mock = mock.patch("psycopg2.connect", MockConnection)
|
|
|
|
self.cursor_mock.start()
|
|
self.connection_mock.start()
|
|
|
|
def tearDown(self):
|
|
super().tearDown()
|
|
self.memory_exporter.clear()
|
|
self.cursor_mock.stop()
|
|
self.connection_mock.stop()
|
|
with self.disable_logging():
|
|
Psycopg2Instrumentor().uninstrument()
|
|
|
|
# pylint: disable=unused-argument
|
|
def test_instrumentor(self):
|
|
Psycopg2Instrumentor().instrument()
|
|
|
|
cnx = psycopg2.connect(database="test")
|
|
|
|
cursor = cnx.cursor()
|
|
|
|
query = "SELECT * FROM test"
|
|
cursor.execute(query)
|
|
|
|
spans_list = self.memory_exporter.get_finished_spans()
|
|
self.assertEqual(len(spans_list), 1)
|
|
span = spans_list[0]
|
|
|
|
# Check version and name in span's instrumentation info
|
|
self.check_span_instrumentation_info(
|
|
span, opentelemetry.instrumentation.psycopg2
|
|
)
|
|
|
|
# check that no spans are generated after uninstrument
|
|
Psycopg2Instrumentor().uninstrument()
|
|
|
|
cnx = psycopg2.connect(database="test")
|
|
cursor = cnx.cursor()
|
|
query = "SELECT * FROM test"
|
|
cursor.execute(query)
|
|
|
|
spans_list = self.memory_exporter.get_finished_spans()
|
|
self.assertEqual(len(spans_list), 1)
|
|
|
|
# pylint: disable=unused-argument
|
|
def test_not_recording(self):
|
|
mock_tracer = mock.Mock()
|
|
mock_span = mock.Mock()
|
|
mock_span.is_recording.return_value = False
|
|
mock_tracer.start_span.return_value = mock_span
|
|
mock_tracer.use_span.return_value.__enter__ = mock_span
|
|
mock_tracer.use_span.return_value.__exit__ = True
|
|
Psycopg2Instrumentor().instrument()
|
|
with mock.patch("opentelemetry.trace.get_tracer") as tracer:
|
|
tracer.return_value = mock_tracer
|
|
cnx = psycopg2.connect(database="test")
|
|
cursor = cnx.cursor()
|
|
query = "SELECT * FROM test"
|
|
cursor.execute(query)
|
|
self.assertFalse(mock_span.is_recording())
|
|
self.assertTrue(mock_span.is_recording.called)
|
|
self.assertFalse(mock_span.set_attribute.called)
|
|
self.assertFalse(mock_span.set_status.called)
|
|
|
|
Psycopg2Instrumentor().uninstrument()
|
|
|
|
# pylint: disable=unused-argument
|
|
def test_custom_tracer_provider(self):
|
|
resource = resources.Resource.create({})
|
|
result = self.create_tracer_provider(resource=resource)
|
|
tracer_provider, exporter = result
|
|
|
|
Psycopg2Instrumentor().instrument(tracer_provider=tracer_provider)
|
|
|
|
cnx = psycopg2.connect(database="test")
|
|
cursor = cnx.cursor()
|
|
query = "SELECT * FROM test"
|
|
cursor.execute(query)
|
|
|
|
spans_list = exporter.get_finished_spans()
|
|
self.assertEqual(len(spans_list), 1)
|
|
span = spans_list[0]
|
|
|
|
self.assertIs(span.resource, resource)
|
|
|
|
# pylint: disable=unused-argument
|
|
def test_instrument_connection(self):
|
|
cnx = psycopg2.connect(database="test")
|
|
query = "SELECT * FROM test"
|
|
cursor = cnx.cursor()
|
|
cursor.execute(query)
|
|
|
|
spans_list = self.memory_exporter.get_finished_spans()
|
|
self.assertEqual(len(spans_list), 0)
|
|
|
|
cnx = Psycopg2Instrumentor().instrument_connection(cnx)
|
|
cursor = cnx.cursor()
|
|
cursor.execute(query)
|
|
|
|
spans_list = self.memory_exporter.get_finished_spans()
|
|
self.assertEqual(len(spans_list), 1)
|
|
|
|
# pylint: disable=unused-argument
|
|
def test_uninstrument_connection(self):
|
|
Psycopg2Instrumentor().instrument()
|
|
cnx = psycopg2.connect(database="test")
|
|
query = "SELECT * FROM test"
|
|
cursor = cnx.cursor()
|
|
cursor.execute(query)
|
|
|
|
spans_list = self.memory_exporter.get_finished_spans()
|
|
self.assertEqual(len(spans_list), 1)
|
|
|
|
cnx = Psycopg2Instrumentor().uninstrument_connection(cnx)
|
|
cursor = cnx.cursor()
|
|
cursor.execute(query)
|
|
|
|
spans_list = self.memory_exporter.get_finished_spans()
|
|
self.assertEqual(len(spans_list), 1)
|