Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions docs/ext/pymysql/pymysql.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
OpenTelemetry PyMySQL Integration
=================================

.. automodule:: opentelemetry.ext.pymysql
:members:
:undoc-members:
:show-inheritance:
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,9 @@ def get_connection_attributes(self, connection):
self.name = self.database_component
self.database = self.connection_props.get("database", "")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
self.database = self.connection_props.get("database", "")
self.database = self.connection_props.get("database", b"")

if self.database:
# PyMySQL encodes names with utf-8
if hasattr(self.database, "decode"):
self.database = self.database.decode(errors="ignore")
Comment on lines +182 to +183
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if hasattr(self.database, "decode"):
self.database = self.database.decode(errors="ignore")
self.database = self.database.decode(errors="ignore")

self.name += "." + self.database
user = self.connection_props.get("user")
if user is not None:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# 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 os
import time
import unittest

import pymysql as pymy

from opentelemetry import trace as trace_api
from opentelemetry.ext.pymysql import trace_integration
from opentelemetry.sdk.trace import Tracer, TracerProvider
from opentelemetry.sdk.trace.export import SimpleExportSpanProcessor
from opentelemetry.sdk.trace.export.in_memory_span_exporter import (
InMemorySpanExporter,
)

MYSQL_USER = os.getenv("MYSQL_USER ", "testuser")
MYSQL_PASSWORD = os.getenv("MYSQL_PASSWORD ", "testpassword")
MYSQL_HOST = os.getenv("MYSQL_HOST ", "localhost")
MYSQL_PORT = int(os.getenv("MYSQL_PORT ", "3306"))
MYSQL_DB_NAME = os.getenv("MYSQL_DB_NAME ", "opentelemetry-tests")


class TestFunctionalPyMysql(unittest.TestCase):
Copy link
Copy Markdown
Member

@mauriciovasquezbernal mauriciovasquezbernal Apr 20, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also here, using TestBase could help a lot.

@classmethod
def setUpClass(cls):
cls._connection = None
cls._cursor = None
cls._tracer_provider = TracerProvider()
cls._tracer = Tracer(cls._tracer_provider, None)
cls._span_exporter = InMemorySpanExporter()
cls._span_processor = SimpleExportSpanProcessor(cls._span_exporter)
cls._tracer_provider.add_span_processor(cls._span_processor)
trace_integration(cls._tracer_provider)
cls._connection = pymy.connect(
user=MYSQL_USER,
password=MYSQL_PASSWORD,
host=MYSQL_HOST,
port=MYSQL_PORT,
database=MYSQL_DB_NAME,
)
cls._cursor = cls._connection.cursor()

@classmethod
def tearDownClass(cls):
if cls._connection:
cls._connection.close()

def setUp(self):
self._span_exporter.clear()

def validate_spans(self):
spans = self._span_exporter.get_finished_spans()
self.assertEqual(len(spans), 2)
for span in spans:
if span.name == "rootSpan":
root_span = span
else:
db_span = span
self.assertIsInstance(span.start_time, int)
self.assertIsInstance(span.end_time, int)
self.assertIsNotNone(root_span)
self.assertIsNotNone(db_span)
self.assertEqual(root_span.name, "rootSpan")
self.assertEqual(db_span.name, "mysql.opentelemetry-tests")
self.assertIsNotNone(db_span.parent)
self.assertEqual(db_span.parent.name, root_span.name)
self.assertIs(db_span.kind, trace_api.SpanKind.CLIENT)
self.assertEqual(db_span.attributes["db.instance"], MYSQL_DB_NAME)
self.assertEqual(db_span.attributes["net.peer.name"], MYSQL_HOST)
self.assertEqual(db_span.attributes["net.peer.port"], MYSQL_PORT)

def test_execute(self):
"""Should create a child span for execute
"""
with self._tracer.start_as_current_span("rootSpan"):
self._cursor.execute("CREATE TABLE IF NOT EXISTS test (id INT)")
self.validate_spans()

def test_executemany(self):
"""Should create a child span for executemany
"""
with self._tracer.start_as_current_span("rootSpan"):
data = ["1", "2", "3"]
stmt = "INSERT INTO test (id) VALUES (%s)"
self._cursor.executemany(stmt, data)
self.validate_spans()

def test_callproc(self):
"""Should create a child span for callproc
"""
with self._tracer.start_as_current_span("rootSpan"), self.assertRaises(
Exception
):
Comment on lines +104 to +106
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think two separated with statements would be a lot clearer in this particular case.

self._cursor.callproc("test", ())
self.validate_spans()
1 change: 1 addition & 0 deletions ext/opentelemetry-ext-mysql/setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ package_dir=
packages=find_namespace:
install_requires =
opentelemetry-api == 0.7.dev0
opentelemetry-ext-dbapi == 0.7.dev0
mysql-connector-python ~= 8.0
wrapt >= 1.0.0, < 2.0.0

Expand Down
3 changes: 3 additions & 0 deletions ext/opentelemetry-ext-pymysql/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Changelog

## Unreleased
24 changes: 24 additions & 0 deletions ext/opentelemetry-ext-pymysql/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
OpenTelemetry PyMySQL integration
=================================

|pypi|

.. |pypi| image:: https://badge.fury.io/py/opentelemetry-ext-pymysql.svg
:target: https://pypi.org/project/opentelemetry-ext-pymysql/

Integration with PyMySQL that supports the PyMySQL library and is
specified to trace_integration using 'PyMySQL'.


Installation
------------

::

pip install opentelemetry-ext-pymysql


References
----------
* `OpenTelemetry PyMySQL Integration <https://opentelemetry-python.readthedocs.io/en/latest/ext/pymysql/pymysql.html>`_
* `OpenTelemetry Project <https://opentelemetry.io/>`_
48 changes: 48 additions & 0 deletions ext/opentelemetry-ext-pymysql/setup.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# 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.
#
[metadata]
name = opentelemetry-ext-pymysql
description = OpenTelemetry PyMySQL 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/tree/master/ext/opentelemetry-ext-pymysql
platforms = any
license = Apache-2.0
classifiers =
Development Status :: 4 - Beta
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
Programming Language :: Python :: 3.8

[options]
python_requires = >=3.4
package_dir=
=src
packages=find_namespace:
install_requires =
opentelemetry-api == 0.7.dev0
opentelemetry-ext-dbapi == 0.7.dev0
PyMySQL ~= 0.9.3

[options.packages.find]
where = src
26 changes: 26 additions & 0 deletions ext/opentelemetry-ext-pymysql/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# 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 os

import setuptools

BASE_DIR = os.path.dirname(__file__)
VERSION_FILENAME = os.path.join(
BASE_DIR, "src", "opentelemetry", "ext", "pymysql", "version.py"
)
PACKAGE_INFO = {}
with open(VERSION_FILENAME) as f:
exec(f.read(), PACKAGE_INFO)

setuptools.setup(version=PACKAGE_INFO["__version__"])
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# 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.

"""
The integration with PyMySQL supports the `PyMySQL`_ library and is specified
to ``trace_integration`` using ``'PyMySQL'``.

.. _PyMySQL: https://pypi.org/project/PyMySQL/

Usage
-----

.. code:: python

import pymysql
from opentelemetry import trace
from opentelemetry.ext.pymysql import trace_integration
from opentelemetry.sdk.trace import TracerProvider

trace.set_tracer_provider(TracerProvider())
trace_integration()
cnx = pymysql.connect(database="MySQL_Database")
cursor = cnx.cursor()
cursor.execute("INSERT INTO test (testField) VALUES (123)"
cnx.commit()
cursor.close()
cnx.close()

API
---
"""

import typing

import pymysql

from opentelemetry.ext.dbapi import wrap_connect
from opentelemetry.ext.pymysql.version import __version__
from opentelemetry.trace import TracerProvider, get_tracer


def trace_integration(tracer_provider: typing.Optional[TracerProvider] = None):
"""Integrate with the PyMySQL library.
https://github.com/PyMySQL/PyMySQL/
"""

tracer = get_tracer(__name__, __version__, tracer_provider)

connection_attributes = {
"database": "db",
"port": "port",
"host": "host",
"user": "user",
}
wrap_connect(
tracer, pymysql, "connect", "mysql", "sql", connection_attributes
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# 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.

__version__ = "0.7.dev0"
Empty file.
38 changes: 38 additions & 0 deletions ext/opentelemetry-ext-pymysql/tests/test_pymysql_integration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# 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.

from unittest import mock

import pymysql

import opentelemetry.ext.pymysql
from opentelemetry.ext.pymysql import trace_integration
from opentelemetry.test.test_base import TestBase


class TestPyMysqlIntegration(TestBase):
def test_trace_integration(self):
with mock.patch("pymysql.connect"):
trace_integration()
cnx = pymysql.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.ext.pymysql)
Loading