-
Notifications
You must be signed in to change notification settings - Fork 610
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adding DB API integration + MySQL connector integration (#264)
Adding the ext.dbapi and ext.mysql package.
- Loading branch information
1 parent
b72cab5
commit b01f7e8
Showing
15 changed files
with
731 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
OpenTelemetry Database API integration | ||
================================= | ||
|
||
The trace integration with Database API supports libraries following the specification. | ||
|
||
.. PEP 249 -- Python Database API Specification v2.0: https://www.python.org/dev/peps/pep-0249/ | ||
Usage | ||
----- | ||
|
||
.. code:: python | ||
import mysql.connector | ||
from opentelemetry.trace import tracer | ||
from opentelemetry.ext.dbapi import trace_integration | ||
# Ex: mysql.connector | ||
trace_integration(tracer(), mysql.connector, "connect", "mysql") | ||
References | ||
---------- | ||
|
||
* `OpenTelemetry Project <https://opentelemetry.io/>`_ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
# Copyright 2019, 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. | ||
# | ||
[metadata] | ||
name = opentelemetry-ext-dbapi | ||
description = OpenTelemetry Database API integration | ||
long_description = file: README.rst | ||
long_description_content_type = text/x-rst | ||
author = OpenTelemetry Authors | ||
author_email = cncf-opentelemetry-contributors@lists.cncf.io | ||
url = https://github.com/open-telemetry/opentelemetry-python/ext/opentelemetry-ext-dbapi | ||
platforms = any | ||
license = Apache-2.0 | ||
classifiers = | ||
Development Status :: 3 - Alpha | ||
Intended Audience :: Developers | ||
License :: OSI Approved :: Apache Software License | ||
Programming Language :: Python | ||
Programming Language :: Python :: 3 | ||
Programming Language :: Python :: 3.4 | ||
Programming Language :: Python :: 3.5 | ||
Programming Language :: Python :: 3.6 | ||
Programming Language :: Python :: 3.7 | ||
|
||
[options] | ||
python_requires = >=3.4 | ||
package_dir= | ||
=src | ||
packages=find_namespace: | ||
install_requires = | ||
opentelemetry-api >= 0.4.dev0 | ||
wrapt >= 1.0.0, < 2.0.0 | ||
|
||
[options.packages.find] | ||
where = src |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
# Copyright 2019, 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 os | ||
|
||
import setuptools | ||
|
||
BASE_DIR = os.path.dirname(__file__) | ||
VERSION_FILENAME = os.path.join( | ||
BASE_DIR, "src", "opentelemetry", "ext", "dbapi", "version.py" | ||
) | ||
PACKAGE_INFO = {} | ||
with open(VERSION_FILENAME) as f: | ||
exec(f.read(), PACKAGE_INFO) | ||
|
||
setuptools.setup(version=PACKAGE_INFO["__version__"]) |
225 changes: 225 additions & 0 deletions
225
ext/opentelemetry-ext-dbapi/src/opentelemetry/ext/dbapi/__init__.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,225 @@ | ||
# Copyright 2019, 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. | ||
|
||
""" | ||
The opentelemetry-ext-dbapi package allows tracing queries made by the | ||
ibraries following Ptyhon Database API specification: | ||
https://www.python.org/dev/peps/pep-0249/ | ||
""" | ||
|
||
import logging | ||
import typing | ||
|
||
import wrapt | ||
|
||
from opentelemetry.trace import SpanKind, Tracer | ||
from opentelemetry.trace.status import Status, StatusCanonicalCode | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
def trace_integration( | ||
tracer: Tracer, | ||
connect_module: typing.Callable[..., any], | ||
connect_method_name: str, | ||
database_component: str, | ||
database_type: str = "", | ||
connection_attributes: typing.Dict = None, | ||
): | ||
"""Integrate with DB API library. | ||
https://www.python.org/dev/peps/pep-0249/ | ||
Args: | ||
tracer: The :class:`Tracer` to use. | ||
connect_module: Module name where connect method is available. | ||
connect_method_name: The connect method name. | ||
database_component: Database driver name or database name "JDBI", "jdbc", "odbc", "postgreSQL". | ||
database_type: The Database type. For any SQL database, "sql". | ||
connection_attributes: Attribute names for database, port, host and user in Connection object. | ||
""" | ||
|
||
# pylint: disable=unused-argument | ||
def wrap_connect( | ||
wrapped: typing.Callable[..., any], | ||
instance: typing.Any, | ||
args: typing.Tuple[any, any], | ||
kwargs: typing.Dict[any, any], | ||
): | ||
db_integration = DatabaseApiIntegration( | ||
tracer, | ||
database_component, | ||
database_type=database_type, | ||
connection_attributes=connection_attributes, | ||
) | ||
return db_integration.wrapped_connection(wrapped, args, kwargs) | ||
|
||
try: | ||
wrapt.wrap_function_wrapper( | ||
connect_module, connect_method_name, wrap_connect | ||
) | ||
except Exception as ex: # pylint: disable=broad-except | ||
logger.warning("Failed to integrate with DB API. %s", str(ex)) | ||
|
||
|
||
class DatabaseApiIntegration: | ||
# pylint: disable=unused-argument | ||
def __init__( | ||
self, | ||
tracer: Tracer, | ||
database_component: str, | ||
database_type: str = "sql", | ||
connection_attributes=None, | ||
): | ||
if tracer is None: | ||
raise ValueError("The tracer is not provided.") | ||
self.connection_attributes = connection_attributes | ||
if self.connection_attributes is None: | ||
self.connection_attributes = { | ||
"database": "database", | ||
"port": "port", | ||
"host": "host", | ||
"user": "user", | ||
} | ||
self.tracer = tracer | ||
self.database_component = database_component | ||
self.database_type = database_type | ||
self.connection_props = {} | ||
self.span_attributes = {} | ||
self.name = "" | ||
self.database = "" | ||
|
||
def wrapped_connection( | ||
self, | ||
connect_method: typing.Callable[..., any], | ||
args: typing.Tuple[any, any], | ||
kwargs: typing.Dict[any, any], | ||
): | ||
"""Add object proxy to connection object. | ||
""" | ||
connection = connect_method(*args, **kwargs) | ||
|
||
for key, value in self.connection_attributes.items(): | ||
attribute = getattr(connection, value, None) | ||
if attribute: | ||
self.connection_props[key] = attribute | ||
traced_connection = TracedConnection(connection, self) | ||
return traced_connection | ||
|
||
|
||
# pylint: disable=abstract-method | ||
class TracedConnection(wrapt.ObjectProxy): | ||
|
||
# pylint: disable=unused-argument | ||
def __init__( | ||
self, | ||
connection, | ||
db_api_integration: DatabaseApiIntegration, | ||
*args, | ||
**kwargs | ||
): | ||
wrapt.ObjectProxy.__init__(self, connection) | ||
self._db_api_integration = db_api_integration | ||
|
||
self._db_api_integration.name = ( | ||
self._db_api_integration.database_component | ||
) | ||
self._db_api_integration.database = self._db_api_integration.connection_props.get( | ||
"database", "" | ||
) | ||
if self._db_api_integration.database: | ||
self._db_api_integration.name += ( | ||
"." + self._db_api_integration.database | ||
) | ||
user = self._db_api_integration.connection_props.get("user") | ||
if user is not None: | ||
self._db_api_integration.span_attributes["db.user"] = user | ||
host = self._db_api_integration.connection_props.get("host") | ||
if host is not None: | ||
self._db_api_integration.span_attributes["net.peer.name"] = host | ||
port = self._db_api_integration.connection_props.get("port") | ||
if port is not None: | ||
self._db_api_integration.span_attributes["net.peer.port"] = port | ||
|
||
def cursor(self, *args, **kwargs): | ||
return TracedCursor( | ||
self.__wrapped__.cursor(*args, **kwargs), self._db_api_integration | ||
) | ||
|
||
|
||
# pylint: disable=abstract-method | ||
class TracedCursor(wrapt.ObjectProxy): | ||
|
||
# pylint: disable=unused-argument | ||
def __init__( | ||
self, | ||
cursor, | ||
db_api_integration: DatabaseApiIntegration, | ||
*args, | ||
**kwargs | ||
): | ||
wrapt.ObjectProxy.__init__(self, cursor) | ||
self._db_api_integration = db_api_integration | ||
|
||
def execute(self, *args, **kwargs): | ||
return self._traced_execution( | ||
self.__wrapped__.execute, *args, **kwargs | ||
) | ||
|
||
def executemany(self, *args, **kwargs): | ||
return self._traced_execution( | ||
self.__wrapped__.executemany, *args, **kwargs | ||
) | ||
|
||
def callproc(self, *args, **kwargs): | ||
return self._traced_execution( | ||
self.__wrapped__.callproc, *args, **kwargs | ||
) | ||
|
||
def _traced_execution( | ||
self, | ||
query_method: typing.Callable[..., any], | ||
*args: typing.Tuple[any, any], | ||
**kwargs: typing.Dict[any, any] | ||
): | ||
|
||
statement = args[0] if args else "" | ||
with self._db_api_integration.tracer.start_as_current_span( | ||
self._db_api_integration.name, kind=SpanKind.CLIENT | ||
) as span: | ||
span.set_attribute( | ||
"component", self._db_api_integration.database_component | ||
) | ||
span.set_attribute( | ||
"db.type", self._db_api_integration.database_type | ||
) | ||
span.set_attribute( | ||
"db.instance", self._db_api_integration.database | ||
) | ||
span.set_attribute("db.statement", statement) | ||
|
||
for ( | ||
attribute_key, | ||
attribute_value, | ||
) in self._db_api_integration.span_attributes.items(): | ||
span.set_attribute(attribute_key, attribute_value) | ||
|
||
if len(args) > 1: | ||
span.set_attribute("db.statement.parameters", str(args[1])) | ||
|
||
try: | ||
result = query_method(*args, **kwargs) | ||
span.set_status(Status(StatusCanonicalCode.OK)) | ||
return result | ||
except Exception as ex: # pylint: disable=broad-except | ||
span.set_status(Status(StatusCanonicalCode.UNKNOWN, str(ex))) | ||
raise ex |
15 changes: 15 additions & 0 deletions
15
ext/opentelemetry-ext-dbapi/src/opentelemetry/ext/dbapi/version.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
# Copyright 2019, 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. | ||
|
||
__version__ = "0.4.dev0" |
Empty file.
Oops, something went wrong.