Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ext/pymysql: Add Instrumentor #611

Merged
merged 19 commits into from
Apr 29, 2020
Merged

ext/pymysql: Add Instrumentor #611

merged 19 commits into from
Apr 29, 2020

Conversation

mauriciovasquezbernal
Copy link
Member

@mauriciovasquezbernal mauriciovasquezbernal commented Apr 23, 2020

  • Implement a way to uninstrument dbapi connections
  • Update pymysql to implement the instrumentor interface and provide a way to uninstrument

How to try it:

# install
pip install -e ext/opentelemetry-ext-pymysql/
# set up test db
opentelemetry-ext-docker-tests/tests
docker-compose up

pymysql_example.py

# Configuration code
from opentelemetry import trace

from opentelemetry.sdk.trace.export import (
    SimpleExportSpanProcessor,
    ConsoleSpanExporter,
)

trace.get_tracer_provider().add_span_processor(
    SimpleExportSpanProcessor(ConsoleSpanExporter())
)

# Real example starts here
import os

import pymysql

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")

_connection = None
cursor = None
_connection = pymysql.connect(
   user=MYSQL_USER,
   password=MYSQL_PASSWORD,
   host=MYSQL_HOST,
   port=MYSQL_PORT,
   database=MYSQL_DB_NAME,
)
cursor = _connection.cursor()

cursor.execute("CREATE TABLE IF NOT EXISTS test (id integer)")

data = ((1,), (2,), (3,))
stmt = "INSERT INTO test (id) VALUES (%s)"
cursor.executemany(stmt, data)

Run the example:

OPENTELEMETRY_PYTHON_TRACER_PROVIDER=sdk_tracer_provider opentelemetry-auto-instrumentation pymysql_example.py

If everything went fine you see some spans on the terminal.

@mauriciovasquezbernal mauriciovasquezbernal added ext instrumentation Related to the instrumentation of third party libraries or frameworks labels Apr 23, 2020
Unwrap connect restores the original behaviour of the connect method.
- Implement instrumentor interface to be usable by autoinstrumentation
- Provide mechanishm to uninstrument
@mauriciovasquezbernal mauriciovasquezbernal marked this pull request as ready for review April 24, 2020 21:56
@mauriciovasquezbernal mauriciovasquezbernal requested a review from a team April 24, 2020 21:56
if isinstance(conn, wrapt.ObjectProxy):
setattr(connect_module, connect_method_name, conn.__wrapped__)


class DatabaseApiIntegration:
Copy link
Member

@hectorhdzg hectorhdzg Apr 24, 2020

Choose a reason for hiding this comment

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

Any reason to not use BaseInstrumentor in here as well?, people could be using dbapi instrumentor directly

Copy link
Member Author

Choose a reason for hiding this comment

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

BaseInstrumentor is used to implement an interface that allows to instrument all the calls made to a given library, for instance pymysql. AFAIU dbapi is a specification and not a library, so having an instrumentor here doesn't make sense. If a person wants to use dpapi directly, they should use the wrap_connect and unwrap_connect functions.

Copy link
Contributor

Choose a reason for hiding this comment

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

Should document this? In the docstrings trace_integration() is the recommended way to instrument. Perhaps we should expose an API method to uninstrument (since there isn't really a semantic similarity between trace_integration and unwrap_connect.

Copy link
Member Author

Choose a reason for hiding this comment

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

The PyMySQL docstrings was wrong, trace_integration doesn't exist anymore.
PymysqlInstrumentor offers two methods, instrument and uninstrument.

I'm not sure what to do with the trace_integration on dpapi, it's very similar to wrap_connect but creates a tracer before. Maybe we could remove it and let the user play directly with wrap_connect...

Copy link
Member

@hectorhdzg hectorhdzg left a comment

Choose a reason for hiding this comment

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

LGTM

Copy link
Contributor

@ocelotl ocelotl left a comment

Choose a reason for hiding this comment

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

Just some suggestions. 👍

def tearDown(self):
super().tearDown()
# ensure that it's uninstrumented if some of the tests fail
PymysqlInstrumentor().uninstrument()
Copy link
Contributor

Choose a reason for hiding this comment

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

Please enclose this between logging.disable calls to suppress unnecessary logging:

from logging import disable, WARNING, NOTSET

...

    def tearDown(self):
        ...
        disable(WARNING)
        PymysqlInstrumentor().uninstrument()
        disable(NOTSET)

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks, I followed a similar approach.

@@ -184,7 +193,7 @@ def get_connection_attributes(self, connection):
self.name += "." + self.database
user = self.connection_props.get("user")
if user is not None:
self.span_attributes["db.user"] = user
self.span_attributes["db.user"] = str(user)
Copy link
Contributor

Choose a reason for hiding this comment

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

User is not returned as string in some cases?

Copy link
Member Author

Choose a reason for hiding this comment

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

Right, PyMySQL returns bytes and it was causing problems in the console exporter. I opened an issue about it #623.

pymysql: pip install {toxinidir}/ext/opentelemetry-ext-dbapi
pymysql: pip install {toxinidir}/ext/opentelemetry-ext-pymysql
pymysql: pip install {toxinidir}/ext/opentelemetry-ext-pymysql[test]
Copy link
Contributor

Choose a reason for hiding this comment

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

Is the [test] for using TestBase? How does this work?

Copy link
Member Author

Choose a reason for hiding this comment

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

It's a way to tell pip to install opentelemetry-ext-pymysql and the dependencies on the extra test section: https://github.com/open-telemetry/opentelemetry-python/pull/611/files#diff-3874eac15d9a093a3db07c9283d97d12R48

Copy link
Contributor

@lzchen lzchen left a comment

Choose a reason for hiding this comment

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

LGTM. Some non-blocking comments.

mauriciovasquezbernal and others added 2 commits April 28, 2020 09:20
…__.py

Co-Authored-By: Diego Hurtado <ocelotl@users.noreply.github.com>
- update changelog with link to previous PR
- disable logging to avoid noise in teardown
- rename to PyMySQLInstrumentor
@c24t c24t merged commit 2992950 into open-telemetry:master Apr 29, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
instrumentation Related to the instrumentation of third party libraries or frameworks
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants