Skip to content

Commit 4fb5eb9

Browse files
committed
ext/psycopg2: Implement BaseInstrumentor interface
1 parent 45a9e53 commit 4fb5eb9

File tree

8 files changed

+216
-128
lines changed

8 files changed

+216
-128
lines changed

ext/opentelemetry-ext-dbapi/src/opentelemetry/ext/dbapi/__init__.py

+37-42
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ def instrument_connection(
168168
connection_attributes=connection_attributes,
169169
)
170170
db_integration.get_connection_attributes(connection)
171-
return TracedConnectionProxy(connection, db_integration)
171+
return get_traced_connection_proxy(connection, db_integration)
172172

173173

174174
def uninstrument_connection(connection):
@@ -221,7 +221,7 @@ def wrapped_connection(
221221
"""
222222
connection = connect_method(*args, **kwargs)
223223
self.get_connection_attributes(connection)
224-
return TracedConnectionProxy(connection, self)
224+
return get_traced_connection_proxy(connection, self)
225225

226226
def get_connection_attributes(self, connection):
227227
# Populate span fields using connection
@@ -254,23 +254,21 @@ def get_connection_attributes(self, connection):
254254
self.span_attributes["net.peer.port"] = port
255255

256256

257-
# pylint: disable=abstract-method
258-
class TracedConnectionProxy(wrapt.ObjectProxy):
259-
# pylint: disable=unused-argument
260-
def __init__(
261-
self,
262-
connection,
263-
db_api_integration: DatabaseApiIntegration,
264-
*args,
265-
**kwargs
266-
):
267-
wrapt.ObjectProxy.__init__(self, connection)
268-
self._db_api_integration = db_api_integration
257+
def get_traced_connection_proxy(
258+
connection, db_api_integration, *args, **kwargs
259+
):
260+
# pylint: disable=abstract-method
261+
class TracedConnectionProxy(wrapt.ObjectProxy):
262+
# pylint: disable=unused-argument
263+
def __init__(self, connection, *args, **kwargs):
264+
wrapt.ObjectProxy.__init__(self, connection)
265+
266+
def cursor(self, *args, **kwargs):
267+
return get_traced_cursor_proxy(
268+
self.__wrapped__.cursor(*args, **kwargs), db_api_integration
269+
)
269270

270-
def cursor(self, *args, **kwargs):
271-
return TracedCursorProxy(
272-
self.__wrapped__.cursor(*args, **kwargs), self._db_api_integration
273-
)
271+
return TracedConnectionProxy(connection, *args, **kwargs)
274272

275273

276274
class TracedCursor:
@@ -317,31 +315,28 @@ def traced_execution(
317315
raise ex
318316

319317

320-
# pylint: disable=abstract-method
321-
class TracedCursorProxy(wrapt.ObjectProxy):
318+
def get_traced_cursor_proxy(cursor, db_api_integration, *args, **kwargs):
319+
_traced_cursor = TracedCursor(db_api_integration)
320+
# pylint: disable=abstract-method
321+
class TracedCursorProxy(wrapt.ObjectProxy):
322322

323-
# pylint: disable=unused-argument
324-
def __init__(
325-
self,
326-
cursor,
327-
db_api_integration: DatabaseApiIntegration,
328-
*args,
329-
**kwargs
330-
):
331-
wrapt.ObjectProxy.__init__(self, cursor)
332-
self._traced_cursor = TracedCursor(db_api_integration)
323+
# pylint: disable=unused-argument
324+
def __init__(self, cursor, *args, **kwargs):
325+
wrapt.ObjectProxy.__init__(self, cursor)
333326

334-
def execute(self, *args, **kwargs):
335-
return self._traced_cursor.traced_execution(
336-
self.__wrapped__.execute, *args, **kwargs
337-
)
327+
def execute(self, *args, **kwargs):
328+
return _traced_cursor.traced_execution(
329+
self.__wrapped__.execute, *args, **kwargs
330+
)
338331

339-
def executemany(self, *args, **kwargs):
340-
return self._traced_cursor.traced_execution(
341-
self.__wrapped__.executemany, *args, **kwargs
342-
)
332+
def executemany(self, *args, **kwargs):
333+
return _traced_cursor.traced_execution(
334+
self.__wrapped__.executemany, *args, **kwargs
335+
)
343336

344-
def callproc(self, *args, **kwargs):
345-
return self._traced_cursor.traced_execution(
346-
self.__wrapped__.callproc, *args, **kwargs
347-
)
337+
def callproc(self, *args, **kwargs):
338+
return _traced_cursor.traced_execution(
339+
self.__wrapped__.callproc, *args, **kwargs
340+
)
341+
342+
return TracedCursorProxy(cursor, *args, **kwargs)

ext/opentelemetry-ext-dbapi/tests/test_dbapi_integration.py

-4
Original file line numberDiff line numberDiff line change
@@ -125,15 +125,13 @@ def test_wrap_connect(self, mock_dbapi):
125125
dbapi.wrap_connect(self.tracer, mock_dbapi, "connect", "-")
126126
connection = mock_dbapi.connect()
127127
self.assertEqual(mock_dbapi.connect.call_count, 1)
128-
self.assertIsInstance(connection, dbapi.TracedConnectionProxy)
129128
self.assertIsInstance(connection.__wrapped__, mock.Mock)
130129

131130
@mock.patch("opentelemetry.ext.dbapi")
132131
def test_unwrap_connect(self, mock_dbapi):
133132
dbapi.wrap_connect(self.tracer, mock_dbapi, "connect", "-")
134133
connection = mock_dbapi.connect()
135134
self.assertEqual(mock_dbapi.connect.call_count, 1)
136-
self.assertIsInstance(connection, dbapi.TracedConnectionProxy)
137135

138136
dbapi.unwrap_connect(mock_dbapi, "connect")
139137
connection = mock_dbapi.connect()
@@ -145,7 +143,6 @@ def test_instrument_connection(self):
145143
# Avoid get_attributes failing because can't concatenate mock
146144
connection.database = "-"
147145
connection2 = dbapi.instrument_connection(self.tracer, connection, "-")
148-
self.assertIsInstance(connection2, dbapi.TracedConnectionProxy)
149146
self.assertIs(connection2.__wrapped__, connection)
150147

151148
def test_uninstrument_connection(self):
@@ -154,7 +151,6 @@ def test_uninstrument_connection(self):
154151
# be concatenated
155152
connection.database = "-"
156153
connection2 = dbapi.instrument_connection(self.tracer, connection, "-")
157-
self.assertIsInstance(connection2, dbapi.TracedConnectionProxy)
158154
self.assertIs(connection2.__wrapped__, connection)
159155

160156
connection3 = dbapi.uninstrument_connection(connection2)

ext/opentelemetry-ext-docker-tests/tests/postgres/test_psycopg_functional.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
import psycopg2
1919

2020
from opentelemetry import trace as trace_api
21-
from opentelemetry.ext.psycopg2 import trace_integration
21+
from opentelemetry.ext.psycopg2 import Psycopg2Instrumentor
2222
from opentelemetry.test.test_base import TestBase
2323

2424
POSTGRES_HOST = os.getenv("POSTGRESQL_HOST ", "localhost")
@@ -35,7 +35,7 @@ def setUpClass(cls):
3535
cls._connection = None
3636
cls._cursor = None
3737
cls._tracer = cls.tracer_provider.get_tracer(__name__)
38-
trace_integration(cls.tracer_provider)
38+
Psycopg2Instrumentor().instrument(tracer_provider=cls.tracer_provider)
3939
cls._connection = psycopg2.connect(
4040
dbname=POSTGRES_DB_NAME,
4141
user=POSTGRES_USER,
@@ -52,6 +52,7 @@ def tearDownClass(cls):
5252
cls._cursor.close()
5353
if cls._connection:
5454
cls._connection.close()
55+
Psycopg2Instrumentor().uninstrument()
5556

5657
def validate_spans(self):
5758
spans = self.memory_exporter.get_finished_spans()

ext/opentelemetry-ext-psycopg2/CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## Unreleased
44

5+
- Implement instrumentor interface, enabling auto-instrumentation ([#694]https://github.com/open-telemetry/opentelemetry-python/pull/694)
6+
57
## 0.4a0
68

79
Released 2020-02-21

ext/opentelemetry-ext-psycopg2/setup.cfg

+10
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,18 @@ package_dir=
4141
packages=find_namespace:
4242
install_requires =
4343
opentelemetry-api == 0.8.dev0
44+
opentelemetry-ext-dbapi == 0.8.dev0
45+
opentelemetry-auto-instrumentation == 0.8.dev0
4446
psycopg2-binary >= 2.7.3.1
4547
wrapt >= 1.0.0, < 2.0.0
4648

49+
[options.extras_require]
50+
test =
51+
opentelemetry-test == 0.8.dev0
52+
4753
[options.packages.find]
4854
where = src
55+
56+
[options.entry_points]
57+
opentelemetry_instrumentor =
58+
psycopg2 = opentelemetry.ext.psycopg2:Psycopg2Instrumentor

ext/opentelemetry-ext-psycopg2/src/opentelemetry/ext/psycopg2/__init__.py

+66-71
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
# limitations under the License.
1414

1515
"""
16-
The integration with PostgreSQL supports the `Psycopg`_ library and is specified
17-
to ``trace_integration`` using ``'PostgreSQL'``.
16+
The integration with PostgreSQL supports the `Psycopg`_ library, it can be enabled by
17+
using ``Psycopg2Instrumentor``.
1818
1919
.. _Psycopg: http://initd.org/psycopg/
2020
@@ -26,11 +26,12 @@
2626
import psycopg2
2727
from opentelemetry import trace
2828
from opentelemetry.sdk.trace import TracerProvider
29-
from opentelemetry.trace.ext.psycopg2 import trace_integration
29+
from opentelemetry.trace.ext.psycopg2 import Psycopg2Instrumentor
3030
3131
trace.set_tracer_provider(TracerProvider())
3232
33-
trace_integration()
33+
Psycopg2Instrumentor().instrument()
34+
3435
cnx = psycopg2.connect(database='Database')
3536
cursor = cnx.cursor()
3637
cursor.execute("INSERT INTO test (testField) VALUES (123)")
@@ -41,83 +42,77 @@
4142
---
4243
"""
4344

44-
import logging
4545
import typing
4646

4747
import psycopg2
4848
import wrapt
49-
from psycopg2.sql import Composable
5049

51-
from opentelemetry.ext.dbapi import DatabaseApiIntegration, TracedCursor
50+
from opentelemetry.auto_instrumentation.instrumentor import BaseInstrumentor
51+
from opentelemetry.ext import dbapi
5252
from opentelemetry.ext.psycopg2.version import __version__
53-
from opentelemetry.trace import Tracer, get_tracer
54-
55-
logger = logging.getLogger(__name__)
56-
57-
DATABASE_COMPONENT = "postgresql"
58-
DATABASE_TYPE = "sql"
53+
from opentelemetry.trace import TracerProvider, get_tracer
5954

6055

61-
def trace_integration(tracer_provider=None):
62-
"""Integrate with PostgreSQL Psycopg library.
63-
Psycopg: http://initd.org/psycopg/
64-
"""
65-
66-
tracer = get_tracer(__name__, __version__, tracer_provider)
67-
68-
connection_attributes = {
56+
class Psycopg2Instrumentor(BaseInstrumentor):
57+
_CONNECTION_ATTRIBUTES = {
6958
"database": "info.dbname",
7059
"port": "info.port",
7160
"host": "info.host",
7261
"user": "info.user",
7362
}
74-
db_integration = DatabaseApiIntegration(
75-
tracer,
76-
DATABASE_COMPONENT,
77-
database_type=DATABASE_TYPE,
78-
connection_attributes=connection_attributes,
79-
)
80-
81-
# pylint: disable=unused-argument
82-
def wrap_connect(
83-
connect_func: typing.Callable[..., any],
84-
instance: typing.Any,
85-
args: typing.Tuple[any, any],
86-
kwargs: typing.Dict[any, any],
87-
):
88-
connection = connect_func(*args, **kwargs)
89-
db_integration.get_connection_attributes(connection)
90-
connection.cursor_factory = PsycopgTraceCursor
91-
return connection
92-
93-
try:
94-
wrapt.wrap_function_wrapper(psycopg2, "connect", wrap_connect)
95-
except Exception as ex: # pylint: disable=broad-except
96-
logger.warning("Failed to integrate with pyscopg2. %s", str(ex))
97-
98-
class PsycopgTraceCursor(psycopg2.extensions.cursor):
99-
def __init__(self, *args, **kwargs):
100-
self._traced_cursor = TracedCursor(db_integration)
101-
super(PsycopgTraceCursor, self).__init__(*args, **kwargs)
102-
103-
# pylint: disable=redefined-builtin
104-
def execute(self, query, vars=None):
105-
if isinstance(query, Composable):
106-
query = query.as_string(self)
107-
return self._traced_cursor.traced_execution(
108-
super(PsycopgTraceCursor, self).execute, query, vars
109-
)
110-
111-
# pylint: disable=redefined-builtin
112-
def executemany(self, query, vars):
113-
if isinstance(query, Composable):
114-
query = query.as_string(self)
115-
return self._traced_cursor.traced_execution(
116-
super(PsycopgTraceCursor, self).executemany, query, vars
117-
)
118-
119-
# pylint: disable=redefined-builtin
120-
def callproc(self, procname, vars=None):
121-
return self._traced_cursor.traced_execution(
122-
super(PsycopgTraceCursor, self).callproc, procname, vars
123-
)
63+
64+
_DATABASE_COMPONENT = "postgresql"
65+
_DATABASE_TYPE = "sql"
66+
67+
def _instrument(self, **kwargs):
68+
"""Integrate with PostgreSQL Psycopg library.
69+
Psycopg: http://initd.org/psycopg/
70+
"""
71+
72+
tracer_provider = kwargs.get("tracer_provider")
73+
74+
tracer = get_tracer(__name__, __version__, tracer_provider)
75+
76+
dbapi.wrap_connect(
77+
tracer,
78+
psycopg2,
79+
"connect",
80+
self._DATABASE_COMPONENT,
81+
self._DATABASE_TYPE,
82+
self._CONNECTION_ATTRIBUTES,
83+
)
84+
85+
def _uninstrument(self, **kwargs):
86+
""""Disable Psycopg2 instrumentation"""
87+
dbapi.unwrap_connect(psycopg2, "connect")
88+
89+
# pylint:disable=no-self-use
90+
def instrument_connection(self, connection):
91+
"""Enable instrumentation in a Psycopg2 connection.
92+
93+
Args:
94+
connection: The connection to instrument.
95+
96+
Returns:
97+
An instrumented connection.
98+
"""
99+
tracer = get_tracer(__name__, __version__)
100+
101+
return dbapi.instrument_connection(
102+
tracer,
103+
connection,
104+
self._DATABASE_COMPONENT,
105+
self._DATABASE_TYPE,
106+
self._CONNECTION_ATTRIBUTES,
107+
)
108+
109+
def uninstrument_connection(self, connection):
110+
"""Disable instrumentation in a Psycopg2 connection.
111+
112+
Args:
113+
connection: The connection to uninstrument.
114+
115+
Returns:
116+
An uninstrumented connection.
117+
"""
118+
return dbapi.uninstrument_connection(connection)

0 commit comments

Comments
 (0)