mirror of
https://github.com/open-telemetry/opentelemetry-python-contrib.git
synced 2025-07-31 22:23:12 +08:00
Consistent way of not instrumenting multiple times (#549)
This commit is contained in:
@ -24,6 +24,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
([#538](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/538))
|
([#538](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/538))
|
||||||
- Changed the psycopg2-binary to psycopg2 as dependency in production
|
- Changed the psycopg2-binary to psycopg2 as dependency in production
|
||||||
([#543](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/543))
|
([#543](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/543))
|
||||||
|
- Implement consistent way of checking if instrumentation is already active
|
||||||
|
([#549](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/549))
|
||||||
- Require aiopg to be less than 1.3.0
|
- Require aiopg to be less than 1.3.0
|
||||||
([#560](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/560))
|
([#560](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/560))
|
||||||
- `opentelemetry-instrumentation-django` Migrated Django middleware to new-style.
|
- `opentelemetry-instrumentation-django` Migrated Django middleware to new-style.
|
||||||
|
@ -259,7 +259,7 @@ def _instrument(
|
|||||||
span_name=span_name,
|
span_name=span_name,
|
||||||
tracer_provider=tracer_provider,
|
tracer_provider=tracer_provider,
|
||||||
)
|
)
|
||||||
trace_config.opentelemetry_aiohttp_instrumented = True
|
trace_config._is_instrumented_by_opentelemetry = True
|
||||||
trace_configs.append(trace_config)
|
trace_configs.append(trace_config)
|
||||||
|
|
||||||
kwargs["trace_configs"] = trace_configs
|
kwargs["trace_configs"] = trace_configs
|
||||||
@ -282,7 +282,7 @@ def _uninstrument_session(client_session: aiohttp.ClientSession):
|
|||||||
client_session._trace_configs = [
|
client_session._trace_configs = [
|
||||||
trace_config
|
trace_config
|
||||||
for trace_config in trace_configs
|
for trace_config in trace_configs
|
||||||
if not hasattr(trace_config, "opentelemetry_aiohttp_instrumented")
|
if not hasattr(trace_config, "_is_instrumented_by_opentelemetry")
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
import typing
|
import typing
|
||||||
|
|
||||||
import wrapt
|
import wrapt
|
||||||
from aiopg.utils import ( # pylint: disable=no-name-in-module
|
from aiopg.utils import _ContextManager, _PoolAcquireContextManager
|
||||||
_ContextManager,
|
|
||||||
_PoolAcquireContextManager,
|
|
||||||
)
|
|
||||||
|
|
||||||
from opentelemetry.instrumentation.dbapi import (
|
from opentelemetry.instrumentation.dbapi import (
|
||||||
CursorTracer,
|
CursorTracer,
|
||||||
@ -64,9 +61,7 @@ def get_traced_connection_proxy(
|
|||||||
|
|
||||||
def cursor(self, *args, **kwargs):
|
def cursor(self, *args, **kwargs):
|
||||||
coro = self._cursor(*args, **kwargs)
|
coro = self._cursor(*args, **kwargs)
|
||||||
return _ContextManager( # pylint: disable=no-value-for-parameter
|
return _ContextManager(coro)
|
||||||
coro
|
|
||||||
)
|
|
||||||
|
|
||||||
async def _cursor(self, *args, **kwargs):
|
async def _cursor(self, *args, **kwargs):
|
||||||
# pylint: disable=protected-access
|
# pylint: disable=protected-access
|
||||||
|
@ -41,6 +41,7 @@ from aiopg.utils import ( # pylint: disable=no-name-in-module
|
|||||||
|
|
||||||
from opentelemetry.instrumentation.aiopg.aiopg_integration import (
|
from opentelemetry.instrumentation.aiopg.aiopg_integration import (
|
||||||
AiopgIntegration,
|
AiopgIntegration,
|
||||||
|
AsyncProxyObject,
|
||||||
get_traced_connection_proxy,
|
get_traced_connection_proxy,
|
||||||
)
|
)
|
||||||
from opentelemetry.instrumentation.aiopg.version import __version__
|
from opentelemetry.instrumentation.aiopg.version import __version__
|
||||||
@ -153,6 +154,10 @@ def instrument_connection(
|
|||||||
Returns:
|
Returns:
|
||||||
An instrumented connection.
|
An instrumented connection.
|
||||||
"""
|
"""
|
||||||
|
if isinstance(connection, AsyncProxyObject):
|
||||||
|
logger.warning("Connection already instrumented")
|
||||||
|
return connection
|
||||||
|
|
||||||
db_integration = AiopgIntegration(
|
db_integration = AiopgIntegration(
|
||||||
name,
|
name,
|
||||||
database_system,
|
database_system,
|
||||||
@ -173,7 +178,7 @@ def uninstrument_connection(connection):
|
|||||||
Returns:
|
Returns:
|
||||||
An uninstrumented connection.
|
An uninstrumented connection.
|
||||||
"""
|
"""
|
||||||
if isinstance(connection, wrapt.ObjectProxy):
|
if isinstance(connection, AsyncProxyObject):
|
||||||
return connection.__wrapped__
|
return connection.__wrapped__
|
||||||
|
|
||||||
logger.warning("Connection is not instrumented")
|
logger.warning("Connection is not instrumented")
|
||||||
|
@ -207,6 +207,23 @@ class TestAiopgInstrumentor(TestBase):
|
|||||||
spans_list = self.memory_exporter.get_finished_spans()
|
spans_list = self.memory_exporter.get_finished_spans()
|
||||||
self.assertEqual(len(spans_list), 1)
|
self.assertEqual(len(spans_list), 1)
|
||||||
|
|
||||||
|
def test_instrument_connection_after_instrument(self):
|
||||||
|
cnx = async_call(aiopg.connect(database="test"))
|
||||||
|
query = "SELECT * FROM test"
|
||||||
|
cursor = async_call(cnx.cursor())
|
||||||
|
async_call(cursor.execute(query))
|
||||||
|
|
||||||
|
spans_list = self.memory_exporter.get_finished_spans()
|
||||||
|
self.assertEqual(len(spans_list), 0)
|
||||||
|
|
||||||
|
AiopgInstrumentor().instrument()
|
||||||
|
cnx = AiopgInstrumentor().instrument_connection(cnx)
|
||||||
|
cursor = async_call(cnx.cursor())
|
||||||
|
async_call(cursor.execute(query))
|
||||||
|
|
||||||
|
spans_list = self.memory_exporter.get_finished_spans()
|
||||||
|
self.assertEqual(len(spans_list), 1)
|
||||||
|
|
||||||
def test_custom_tracer_provider_instrument_connection(self):
|
def test_custom_tracer_provider_instrument_connection(self):
|
||||||
resource = resources.Resource.create(
|
resource = resources.Resource.create(
|
||||||
{"service.name": "db-test-service"}
|
{"service.name": "db-test-service"}
|
||||||
|
@ -180,6 +180,10 @@ def instrument_connection(
|
|||||||
Returns:
|
Returns:
|
||||||
An instrumented connection.
|
An instrumented connection.
|
||||||
"""
|
"""
|
||||||
|
if isinstance(connection, wrapt.ObjectProxy):
|
||||||
|
logger.warning("Connection already instrumented")
|
||||||
|
return connection
|
||||||
|
|
||||||
db_integration = DatabaseApiIntegration(
|
db_integration = DatabaseApiIntegration(
|
||||||
name,
|
name,
|
||||||
database_system,
|
database_system,
|
||||||
|
@ -12,9 +12,11 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
import logging
|
||||||
from typing import Collection
|
from typing import Collection
|
||||||
|
|
||||||
import fastapi
|
import fastapi
|
||||||
|
from starlette import middleware
|
||||||
from starlette.routing import Match
|
from starlette.routing import Match
|
||||||
|
|
||||||
from opentelemetry.instrumentation.asgi import OpenTelemetryMiddleware
|
from opentelemetry.instrumentation.asgi import OpenTelemetryMiddleware
|
||||||
@ -24,6 +26,7 @@ from opentelemetry.semconv.trace import SpanAttributes
|
|||||||
from opentelemetry.util.http import get_excluded_urls, parse_excluded_urls
|
from opentelemetry.util.http import get_excluded_urls, parse_excluded_urls
|
||||||
|
|
||||||
_excluded_urls_from_env = get_excluded_urls("FASTAPI")
|
_excluded_urls_from_env = get_excluded_urls("FASTAPI")
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class FastAPIInstrumentor(BaseInstrumentor):
|
class FastAPIInstrumentor(BaseInstrumentor):
|
||||||
@ -39,7 +42,10 @@ class FastAPIInstrumentor(BaseInstrumentor):
|
|||||||
app: fastapi.FastAPI, tracer_provider=None, excluded_urls=None,
|
app: fastapi.FastAPI, tracer_provider=None, excluded_urls=None,
|
||||||
):
|
):
|
||||||
"""Instrument an uninstrumented FastAPI application."""
|
"""Instrument an uninstrumented FastAPI application."""
|
||||||
if not getattr(app, "is_instrumented_by_opentelemetry", False):
|
if not hasattr(app, "_is_instrumented_by_opentelemetry"):
|
||||||
|
app._is_instrumented_by_opentelemetry = False
|
||||||
|
|
||||||
|
if not getattr(app, "_is_instrumented_by_opentelemetry", False):
|
||||||
if excluded_urls is None:
|
if excluded_urls is None:
|
||||||
excluded_urls = _excluded_urls_from_env
|
excluded_urls = _excluded_urls_from_env
|
||||||
else:
|
else:
|
||||||
@ -51,7 +57,21 @@ class FastAPIInstrumentor(BaseInstrumentor):
|
|||||||
span_details_callback=_get_route_details,
|
span_details_callback=_get_route_details,
|
||||||
tracer_provider=tracer_provider,
|
tracer_provider=tracer_provider,
|
||||||
)
|
)
|
||||||
app.is_instrumented_by_opentelemetry = True
|
app._is_instrumented_by_opentelemetry = True
|
||||||
|
else:
|
||||||
|
_logger.warning(
|
||||||
|
"Attempting to instrument FastAPI app while already instrumented"
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def uninstrument_app(app: fastapi.FastAPI):
|
||||||
|
app.user_middleware = [
|
||||||
|
x
|
||||||
|
for x in app.user_middleware
|
||||||
|
if x.cls is not OpenTelemetryMiddleware
|
||||||
|
]
|
||||||
|
app.middleware_stack = app.build_middleware_stack()
|
||||||
|
app._is_instrumented_by_opentelemetry = False
|
||||||
|
|
||||||
def instrumentation_dependencies(self) -> Collection[str]:
|
def instrumentation_dependencies(self) -> Collection[str]:
|
||||||
return _instruments
|
return _instruments
|
||||||
|
@ -19,6 +19,7 @@ import fastapi
|
|||||||
from fastapi.testclient import TestClient
|
from fastapi.testclient import TestClient
|
||||||
|
|
||||||
import opentelemetry.instrumentation.fastapi as otel_fastapi
|
import opentelemetry.instrumentation.fastapi as otel_fastapi
|
||||||
|
from opentelemetry.instrumentation.asgi import OpenTelemetryMiddleware
|
||||||
from opentelemetry.sdk.resources import Resource
|
from opentelemetry.sdk.resources import Resource
|
||||||
from opentelemetry.semconv.trace import SpanAttributes
|
from opentelemetry.semconv.trace import SpanAttributes
|
||||||
from opentelemetry.test.test_base import TestBase
|
from opentelemetry.test.test_base import TestBase
|
||||||
@ -57,6 +58,47 @@ class TestFastAPIManualInstrumentation(TestBase):
|
|||||||
super().tearDown()
|
super().tearDown()
|
||||||
self.env_patch.stop()
|
self.env_patch.stop()
|
||||||
self.exclude_patch.stop()
|
self.exclude_patch.stop()
|
||||||
|
with self.disable_logging():
|
||||||
|
self._instrumentor.uninstrument()
|
||||||
|
self._instrumentor.uninstrument_app(self._app)
|
||||||
|
|
||||||
|
def test_instrument_app_with_instrument(self):
|
||||||
|
if not isinstance(self, TestAutoInstrumentation):
|
||||||
|
self._instrumentor.instrument()
|
||||||
|
self._client.get("/foobar")
|
||||||
|
spans = self.memory_exporter.get_finished_spans()
|
||||||
|
self.assertEqual(len(spans), 3)
|
||||||
|
for span in spans:
|
||||||
|
self.assertIn("/foobar", span.name)
|
||||||
|
|
||||||
|
def test_uninstrument_app(self):
|
||||||
|
self._client.get("/foobar")
|
||||||
|
spans = self.memory_exporter.get_finished_spans()
|
||||||
|
self.assertEqual(len(spans), 3)
|
||||||
|
# pylint: disable=import-outside-toplevel
|
||||||
|
from fastapi.middleware.httpsredirect import HTTPSRedirectMiddleware
|
||||||
|
|
||||||
|
self._app.add_middleware(HTTPSRedirectMiddleware)
|
||||||
|
self._instrumentor.uninstrument_app(self._app)
|
||||||
|
print(self._app.user_middleware[0].cls)
|
||||||
|
self.assertFalse(
|
||||||
|
isinstance(
|
||||||
|
self._app.user_middleware[0].cls, OpenTelemetryMiddleware
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self._client = TestClient(self._app)
|
||||||
|
resp = self._client.get("/foobar")
|
||||||
|
self.assertEqual(200, resp.status_code)
|
||||||
|
span_list = self.memory_exporter.get_finished_spans()
|
||||||
|
self.assertEqual(len(span_list), 3)
|
||||||
|
|
||||||
|
def test_uninstrument_app_after_instrument(self):
|
||||||
|
if not isinstance(self, TestAutoInstrumentation):
|
||||||
|
self._instrumentor.instrument()
|
||||||
|
self._instrumentor.uninstrument_app(self._app)
|
||||||
|
self._client.get("/foobar")
|
||||||
|
spans = self.memory_exporter.get_finished_spans()
|
||||||
|
self.assertEqual(len(spans), 0)
|
||||||
|
|
||||||
def test_basic_fastapi_call(self):
|
def test_basic_fastapi_call(self):
|
||||||
self._client.get("/foobar")
|
self._client.get("/foobar")
|
||||||
|
@ -193,7 +193,8 @@ class _InstrumentedFlask(flask.Flask):
|
|||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
self._original_wsgi_ = self.wsgi_app
|
self._original_wsgi_app = self.wsgi_app
|
||||||
|
self._is_instrumented_by_opentelemetry = True
|
||||||
|
|
||||||
self.wsgi_app = _rewrapped_app(
|
self.wsgi_app = _rewrapped_app(
|
||||||
self.wsgi_app, _InstrumentedFlask._response_hook
|
self.wsgi_app, _InstrumentedFlask._response_hook
|
||||||
@ -229,18 +230,21 @@ class FlaskInstrumentor(BaseInstrumentor):
|
|||||||
_InstrumentedFlask._request_hook = request_hook
|
_InstrumentedFlask._request_hook = request_hook
|
||||||
if callable(response_hook):
|
if callable(response_hook):
|
||||||
_InstrumentedFlask._response_hook = response_hook
|
_InstrumentedFlask._response_hook = response_hook
|
||||||
flask.Flask = _InstrumentedFlask
|
|
||||||
tracer_provider = kwargs.get("tracer_provider")
|
tracer_provider = kwargs.get("tracer_provider")
|
||||||
_InstrumentedFlask._tracer_provider = tracer_provider
|
_InstrumentedFlask._tracer_provider = tracer_provider
|
||||||
flask.Flask = _InstrumentedFlask
|
flask.Flask = _InstrumentedFlask
|
||||||
|
|
||||||
def instrument_app(
|
def _uninstrument(self, **kwargs):
|
||||||
self, app, request_hook=None, response_hook=None, tracer_provider=None
|
flask.Flask = self._original_flask
|
||||||
): # pylint: disable=no-self-use
|
|
||||||
if not hasattr(app, "_is_instrumented"):
|
|
||||||
app._is_instrumented = False
|
|
||||||
|
|
||||||
if not app._is_instrumented:
|
@staticmethod
|
||||||
|
def instrument_app(
|
||||||
|
app, request_hook=None, response_hook=None, tracer_provider=None
|
||||||
|
):
|
||||||
|
if not hasattr(app, "_is_instrumented_by_opentelemetry"):
|
||||||
|
app._is_instrumented_by_opentelemetry = False
|
||||||
|
|
||||||
|
if not app._is_instrumented_by_opentelemetry:
|
||||||
app._original_wsgi_app = app.wsgi_app
|
app._original_wsgi_app = app.wsgi_app
|
||||||
app.wsgi_app = _rewrapped_app(app.wsgi_app, response_hook)
|
app.wsgi_app = _rewrapped_app(app.wsgi_app, response_hook)
|
||||||
|
|
||||||
@ -250,28 +254,22 @@ class FlaskInstrumentor(BaseInstrumentor):
|
|||||||
app._before_request = _before_request
|
app._before_request = _before_request
|
||||||
app.before_request(_before_request)
|
app.before_request(_before_request)
|
||||||
app.teardown_request(_teardown_request)
|
app.teardown_request(_teardown_request)
|
||||||
app._is_instrumented = True
|
app._is_instrumented_by_opentelemetry = True
|
||||||
else:
|
else:
|
||||||
_logger.warning(
|
_logger.warning(
|
||||||
"Attempting to instrument Flask app while already instrumented"
|
"Attempting to instrument Flask app while already instrumented"
|
||||||
)
|
)
|
||||||
|
|
||||||
def _uninstrument(self, **kwargs):
|
@staticmethod
|
||||||
flask.Flask = self._original_flask
|
def uninstrument_app(app):
|
||||||
|
if hasattr(app, "_original_wsgi_app"):
|
||||||
def uninstrument_app(self, app): # pylint: disable=no-self-use
|
|
||||||
if not hasattr(app, "_is_instrumented"):
|
|
||||||
app._is_instrumented = False
|
|
||||||
|
|
||||||
if app._is_instrumented:
|
|
||||||
app.wsgi_app = app._original_wsgi_app
|
app.wsgi_app = app._original_wsgi_app
|
||||||
|
|
||||||
# FIXME add support for other Flask blueprints that are not None
|
# FIXME add support for other Flask blueprints that are not None
|
||||||
app.before_request_funcs[None].remove(app._before_request)
|
app.before_request_funcs[None].remove(app._before_request)
|
||||||
app.teardown_request_funcs[None].remove(_teardown_request)
|
app.teardown_request_funcs[None].remove(_teardown_request)
|
||||||
del app._original_wsgi_app
|
del app._original_wsgi_app
|
||||||
|
app._is_instrumented_by_opentelemetry = False
|
||||||
app._is_instrumented = False
|
|
||||||
else:
|
else:
|
||||||
_logger.warning(
|
_logger.warning(
|
||||||
"Attempting to uninstrument Flask "
|
"Attempting to uninstrument Flask "
|
||||||
|
@ -79,7 +79,16 @@ class TestProgrammatic(InstrumentationTest, TestBase, WsgiTestBase):
|
|||||||
with self.disable_logging():
|
with self.disable_logging():
|
||||||
FlaskInstrumentor().uninstrument_app(self.app)
|
FlaskInstrumentor().uninstrument_app(self.app)
|
||||||
|
|
||||||
def test_uninstrument(self):
|
def test_instrument_app_and_instrument(self):
|
||||||
|
FlaskInstrumentor().instrument()
|
||||||
|
resp = self.client.get("/hello/123")
|
||||||
|
self.assertEqual(200, resp.status_code)
|
||||||
|
self.assertEqual([b"Hello: 123"], list(resp.response))
|
||||||
|
span_list = self.memory_exporter.get_finished_spans()
|
||||||
|
self.assertEqual(len(span_list), 1)
|
||||||
|
FlaskInstrumentor().uninstrument()
|
||||||
|
|
||||||
|
def test_uninstrument_app(self):
|
||||||
resp = self.client.get("/hello/123")
|
resp = self.client.get("/hello/123")
|
||||||
self.assertEqual(200, resp.status_code)
|
self.assertEqual(200, resp.status_code)
|
||||||
self.assertEqual([b"Hello: 123"], list(resp.response))
|
self.assertEqual([b"Hello: 123"], list(resp.response))
|
||||||
@ -94,6 +103,16 @@ class TestProgrammatic(InstrumentationTest, TestBase, WsgiTestBase):
|
|||||||
span_list = self.memory_exporter.get_finished_spans()
|
span_list = self.memory_exporter.get_finished_spans()
|
||||||
self.assertEqual(len(span_list), 1)
|
self.assertEqual(len(span_list), 1)
|
||||||
|
|
||||||
|
def test_uninstrument_app_after_instrument(self):
|
||||||
|
FlaskInstrumentor().instrument()
|
||||||
|
FlaskInstrumentor().uninstrument_app(self.app)
|
||||||
|
resp = self.client.get("/hello/123")
|
||||||
|
self.assertEqual(200, resp.status_code)
|
||||||
|
self.assertEqual([b"Hello: 123"], list(resp.response))
|
||||||
|
span_list = self.memory_exporter.get_finished_spans()
|
||||||
|
self.assertEqual(len(span_list), 0)
|
||||||
|
FlaskInstrumentor().uninstrument()
|
||||||
|
|
||||||
# pylint: disable=no-member
|
# pylint: disable=no-member
|
||||||
def test_only_strings_in_environ(self):
|
def test_only_strings_in_environ(self):
|
||||||
"""
|
"""
|
||||||
|
@ -39,6 +39,7 @@ API
|
|||||||
---
|
---
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
import typing
|
import typing
|
||||||
from typing import Collection
|
from typing import Collection
|
||||||
|
|
||||||
@ -53,6 +54,7 @@ from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
|
|||||||
from opentelemetry.instrumentation.psycopg2.package import _instruments
|
from opentelemetry.instrumentation.psycopg2.package import _instruments
|
||||||
from opentelemetry.instrumentation.psycopg2.version import __version__
|
from opentelemetry.instrumentation.psycopg2.version import __version__
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
_OTEL_CURSOR_FACTORY_KEY = "_otel_orig_cursor_factory"
|
_OTEL_CURSOR_FACTORY_KEY = "_otel_orig_cursor_factory"
|
||||||
|
|
||||||
|
|
||||||
@ -91,24 +93,32 @@ class Psycopg2Instrumentor(BaseInstrumentor):
|
|||||||
dbapi.unwrap_connect(psycopg2, "connect")
|
dbapi.unwrap_connect(psycopg2, "connect")
|
||||||
|
|
||||||
# TODO(owais): check if core dbapi can do this for all dbapi implementations e.g, pymysql and mysql
|
# TODO(owais): check if core dbapi can do this for all dbapi implementations e.g, pymysql and mysql
|
||||||
def instrument_connection(
|
@staticmethod
|
||||||
self, connection, tracer_provider=None
|
def instrument_connection(connection, tracer_provider=None):
|
||||||
): # pylint: disable=no-self-use
|
if not hasattr(connection, "_is_instrumented_by_opentelemetry"):
|
||||||
setattr(
|
connection._is_instrumented_by_opentelemetry = False
|
||||||
connection, _OTEL_CURSOR_FACTORY_KEY, connection.cursor_factory
|
|
||||||
)
|
if not connection._is_instrumented_by_opentelemetry:
|
||||||
connection.cursor_factory = _new_cursor_factory(
|
setattr(
|
||||||
tracer_provider=tracer_provider
|
connection, _OTEL_CURSOR_FACTORY_KEY, connection.cursor_factory
|
||||||
)
|
)
|
||||||
|
connection.cursor_factory = _new_cursor_factory(
|
||||||
|
tracer_provider=tracer_provider
|
||||||
|
)
|
||||||
|
connection._is_instrumented_by_opentelemetry = True
|
||||||
|
else:
|
||||||
|
_logger.warning(
|
||||||
|
"Attempting to instrument Psycopg connection while already instrumented"
|
||||||
|
)
|
||||||
return connection
|
return connection
|
||||||
|
|
||||||
# TODO(owais): check if core dbapi can do this for all dbapi implementations e.g, pymysql and mysql
|
# TODO(owais): check if core dbapi can do this for all dbapi implementations e.g, pymysql and mysql
|
||||||
def uninstrument_connection(
|
@staticmethod
|
||||||
self, connection
|
def uninstrument_connection(connection):
|
||||||
): # pylint: disable=no-self-use
|
|
||||||
connection.cursor_factory = getattr(
|
connection.cursor_factory = getattr(
|
||||||
connection, _OTEL_CURSOR_FACTORY_KEY, None
|
connection, _OTEL_CURSOR_FACTORY_KEY, None
|
||||||
)
|
)
|
||||||
|
|
||||||
return connection
|
return connection
|
||||||
|
|
||||||
|
|
||||||
|
@ -172,7 +172,25 @@ class TestPostgresqlIntegration(TestBase):
|
|||||||
self.assertEqual(len(spans_list), 1)
|
self.assertEqual(len(spans_list), 1)
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
def test_uninstrument_connection(self):
|
def test_instrument_connection_with_instrument(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)
|
||||||
|
|
||||||
|
Psycopg2Instrumentor().instrument()
|
||||||
|
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_with_instrument(self):
|
||||||
Psycopg2Instrumentor().instrument()
|
Psycopg2Instrumentor().instrument()
|
||||||
cnx = psycopg2.connect(database="test")
|
cnx = psycopg2.connect(database="test")
|
||||||
query = "SELECT * FROM test"
|
query = "SELECT * FROM test"
|
||||||
@ -188,3 +206,21 @@ class TestPostgresqlIntegration(TestBase):
|
|||||||
|
|
||||||
spans_list = self.memory_exporter.get_finished_spans()
|
spans_list = self.memory_exporter.get_finished_spans()
|
||||||
self.assertEqual(len(spans_list), 1)
|
self.assertEqual(len(spans_list), 1)
|
||||||
|
|
||||||
|
# pylint: disable=unused-argument
|
||||||
|
def test_uninstrument_connection_with_instrument_connection(self):
|
||||||
|
cnx = psycopg2.connect(database="test")
|
||||||
|
Psycopg2Instrumentor().instrument_connection(cnx)
|
||||||
|
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)
|
||||||
|
@ -49,17 +49,16 @@ from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
|
|||||||
from opentelemetry.instrumentation.pymysql.package import _instruments
|
from opentelemetry.instrumentation.pymysql.package import _instruments
|
||||||
from opentelemetry.instrumentation.pymysql.version import __version__
|
from opentelemetry.instrumentation.pymysql.version import __version__
|
||||||
|
|
||||||
|
_CONNECTION_ATTRIBUTES = {
|
||||||
|
"database": "db",
|
||||||
|
"port": "port",
|
||||||
|
"host": "host",
|
||||||
|
"user": "user",
|
||||||
|
}
|
||||||
|
_DATABASE_SYSTEM = "mysql"
|
||||||
|
|
||||||
|
|
||||||
class PyMySQLInstrumentor(BaseInstrumentor):
|
class PyMySQLInstrumentor(BaseInstrumentor):
|
||||||
_CONNECTION_ATTRIBUTES = {
|
|
||||||
"database": "db",
|
|
||||||
"port": "port",
|
|
||||||
"host": "host",
|
|
||||||
"user": "user",
|
|
||||||
}
|
|
||||||
|
|
||||||
_DATABASE_SYSTEM = "mysql"
|
|
||||||
|
|
||||||
def instrumentation_dependencies(self) -> Collection[str]:
|
def instrumentation_dependencies(self) -> Collection[str]:
|
||||||
return _instruments
|
return _instruments
|
||||||
|
|
||||||
@ -73,8 +72,8 @@ class PyMySQLInstrumentor(BaseInstrumentor):
|
|||||||
__name__,
|
__name__,
|
||||||
pymysql,
|
pymysql,
|
||||||
"connect",
|
"connect",
|
||||||
self._DATABASE_SYSTEM,
|
_DATABASE_SYSTEM,
|
||||||
self._CONNECTION_ATTRIBUTES,
|
_CONNECTION_ATTRIBUTES,
|
||||||
version=__version__,
|
version=__version__,
|
||||||
tracer_provider=tracer_provider,
|
tracer_provider=tracer_provider,
|
||||||
)
|
)
|
||||||
@ -83,8 +82,8 @@ class PyMySQLInstrumentor(BaseInstrumentor):
|
|||||||
""""Disable PyMySQL instrumentation"""
|
""""Disable PyMySQL instrumentation"""
|
||||||
dbapi.unwrap_connect(pymysql, "connect")
|
dbapi.unwrap_connect(pymysql, "connect")
|
||||||
|
|
||||||
# pylint:disable=no-self-use
|
@staticmethod
|
||||||
def instrument_connection(self, connection, tracer_provider=None):
|
def instrument_connection(connection, tracer_provider=None):
|
||||||
"""Enable instrumentation in a PyMySQL connection.
|
"""Enable instrumentation in a PyMySQL connection.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -99,13 +98,14 @@ class PyMySQLInstrumentor(BaseInstrumentor):
|
|||||||
return dbapi.instrument_connection(
|
return dbapi.instrument_connection(
|
||||||
__name__,
|
__name__,
|
||||||
connection,
|
connection,
|
||||||
self._DATABASE_SYSTEM,
|
_DATABASE_SYSTEM,
|
||||||
self._CONNECTION_ATTRIBUTES,
|
_CONNECTION_ATTRIBUTES,
|
||||||
version=__version__,
|
version=__version__,
|
||||||
tracer_provider=tracer_provider,
|
tracer_provider=tracer_provider,
|
||||||
)
|
)
|
||||||
|
|
||||||
def uninstrument_connection(self, connection):
|
@staticmethod
|
||||||
|
def uninstrument_connection(connection):
|
||||||
"""Disable instrumentation in a PyMySQL connection.
|
"""Disable instrumentation in a PyMySQL connection.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -140,8 +140,8 @@ class PyramidInstrumentor(BaseInstrumentor):
|
|||||||
""""Disable Pyramid instrumentation"""
|
""""Disable Pyramid instrumentation"""
|
||||||
unwrap(Configurator, "__init__")
|
unwrap(Configurator, "__init__")
|
||||||
|
|
||||||
# pylint:disable=no-self-use
|
@staticmethod
|
||||||
def instrument_config(self, config):
|
def instrument_config(config):
|
||||||
"""Enable instrumentation in a Pyramid configurator.
|
"""Enable instrumentation in a Pyramid configurator.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -149,5 +149,6 @@ class PyramidInstrumentor(BaseInstrumentor):
|
|||||||
"""
|
"""
|
||||||
config.include("opentelemetry.instrumentation.pyramid.callbacks")
|
config.include("opentelemetry.instrumentation.pyramid.callbacks")
|
||||||
|
|
||||||
def uninstrument_config(self, config):
|
@staticmethod
|
||||||
|
def uninstrument_config(config):
|
||||||
config.add_settings({SETTING_TRACE_ENABLED: False})
|
config.add_settings({SETTING_TRACE_ENABLED: False})
|
||||||
|
@ -48,13 +48,13 @@ from opentelemetry.instrumentation.sqlite3.package import _instruments
|
|||||||
from opentelemetry.instrumentation.sqlite3.version import __version__
|
from opentelemetry.instrumentation.sqlite3.version import __version__
|
||||||
from opentelemetry.trace import get_tracer
|
from opentelemetry.trace import get_tracer
|
||||||
|
|
||||||
|
# No useful attributes of sqlite3 connection object
|
||||||
|
_CONNECTION_ATTRIBUTES = {}
|
||||||
|
|
||||||
|
_DATABASE_SYSTEM = "sqlite"
|
||||||
|
|
||||||
|
|
||||||
class SQLite3Instrumentor(BaseInstrumentor):
|
class SQLite3Instrumentor(BaseInstrumentor):
|
||||||
# No useful attributes of sqlite3 connection object
|
|
||||||
_CONNECTION_ATTRIBUTES = {}
|
|
||||||
|
|
||||||
_DATABASE_SYSTEM = "sqlite"
|
|
||||||
|
|
||||||
def instrumentation_dependencies(self) -> Collection[str]:
|
def instrumentation_dependencies(self) -> Collection[str]:
|
||||||
return _instruments
|
return _instruments
|
||||||
|
|
||||||
@ -68,8 +68,8 @@ class SQLite3Instrumentor(BaseInstrumentor):
|
|||||||
__name__,
|
__name__,
|
||||||
sqlite3,
|
sqlite3,
|
||||||
"connect",
|
"connect",
|
||||||
self._DATABASE_SYSTEM,
|
_DATABASE_SYSTEM,
|
||||||
self._CONNECTION_ATTRIBUTES,
|
_CONNECTION_ATTRIBUTES,
|
||||||
version=__version__,
|
version=__version__,
|
||||||
tracer_provider=tracer_provider,
|
tracer_provider=tracer_provider,
|
||||||
)
|
)
|
||||||
@ -78,8 +78,8 @@ class SQLite3Instrumentor(BaseInstrumentor):
|
|||||||
""""Disable SQLite3 instrumentation"""
|
""""Disable SQLite3 instrumentation"""
|
||||||
dbapi.unwrap_connect(sqlite3, "connect")
|
dbapi.unwrap_connect(sqlite3, "connect")
|
||||||
|
|
||||||
# pylint:disable=no-self-use
|
@staticmethod
|
||||||
def instrument_connection(self, connection, tracer_provider=None):
|
def instrument_connection(connection, tracer_provider=None):
|
||||||
"""Enable instrumentation in a SQLite connection.
|
"""Enable instrumentation in a SQLite connection.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -94,13 +94,14 @@ class SQLite3Instrumentor(BaseInstrumentor):
|
|||||||
return dbapi.instrument_connection(
|
return dbapi.instrument_connection(
|
||||||
__name__,
|
__name__,
|
||||||
connection,
|
connection,
|
||||||
self._DATABASE_SYSTEM,
|
_DATABASE_SYSTEM,
|
||||||
self._CONNECTION_ATTRIBUTES,
|
_CONNECTION_ATTRIBUTES,
|
||||||
version=__version__,
|
version=__version__,
|
||||||
tracer_provider=tracer_provider,
|
tracer_provider=tracer_provider,
|
||||||
)
|
)
|
||||||
|
|
||||||
def uninstrument_connection(self, connection):
|
@staticmethod
|
||||||
|
def uninstrument_connection(connection):
|
||||||
"""Disable instrumentation in a SQLite connection.
|
"""Disable instrumentation in a SQLite connection.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -43,7 +43,7 @@ class BaseInstrumentor(ABC):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
_instance = None
|
_instance = None
|
||||||
_is_instrumented = False
|
_is_instrumented_by_opentelemetry = False
|
||||||
|
|
||||||
def __new__(cls, *args, **kwargs):
|
def __new__(cls, *args, **kwargs):
|
||||||
|
|
||||||
@ -52,6 +52,10 @@ class BaseInstrumentor(ABC):
|
|||||||
|
|
||||||
return cls._instance
|
return cls._instance
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_instrumented_by_opentelemetry(self):
|
||||||
|
return self._is_instrumented_by_opentelemetry
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def instrumentation_dependencies(self) -> Collection[str]:
|
def instrumentation_dependencies(self) -> Collection[str]:
|
||||||
"""Return a list of python packages with versions that the will be instrumented.
|
"""Return a list of python packages with versions that the will be instrumented.
|
||||||
@ -90,7 +94,7 @@ class BaseInstrumentor(ABC):
|
|||||||
``opentelemetry-instrument`` command does.
|
``opentelemetry-instrument`` command does.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self._is_instrumented:
|
if self._is_instrumented_by_opentelemetry:
|
||||||
_LOG.warning("Attempting to instrument while already instrumented")
|
_LOG.warning("Attempting to instrument while already instrumented")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@ -99,13 +103,13 @@ class BaseInstrumentor(ABC):
|
|||||||
if not skip_dep_check:
|
if not skip_dep_check:
|
||||||
conflict = self._check_dependency_conflicts()
|
conflict = self._check_dependency_conflicts()
|
||||||
if conflict:
|
if conflict:
|
||||||
_LOG.warning(conflict)
|
_LOG.error(conflict)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
result = self._instrument( # pylint: disable=assignment-from-no-return
|
result = self._instrument( # pylint: disable=assignment-from-no-return
|
||||||
**kwargs
|
**kwargs
|
||||||
)
|
)
|
||||||
self._is_instrumented = True
|
self._is_instrumented_by_opentelemetry = True
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def uninstrument(self, **kwargs):
|
def uninstrument(self, **kwargs):
|
||||||
@ -115,9 +119,9 @@ class BaseInstrumentor(ABC):
|
|||||||
usage of ``kwargs``.
|
usage of ``kwargs``.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self._is_instrumented:
|
if self._is_instrumented_by_opentelemetry:
|
||||||
result = self._uninstrument(**kwargs)
|
result = self._uninstrument(**kwargs)
|
||||||
self._is_instrumented = False
|
self._is_instrumented_by_opentelemetry = False
|
||||||
return result
|
return result
|
||||||
|
|
||||||
_LOG.warning("Attempting to uninstrument while already uninstrumented")
|
_LOG.warning("Attempting to uninstrument while already uninstrumented")
|
||||||
|
@ -53,6 +53,6 @@ class TestDistro(TestCase):
|
|||||||
instrumentor = MockInstrumetor()
|
instrumentor = MockInstrumetor()
|
||||||
entry_point = MockEntryPoint(MockInstrumetor)
|
entry_point = MockEntryPoint(MockInstrumetor)
|
||||||
|
|
||||||
self.assertFalse(instrumentor._is_instrumented)
|
self.assertFalse(instrumentor._is_instrumented_by_opentelemetry)
|
||||||
distro.load_instrumentor(entry_point)
|
distro.load_instrumentor(entry_point)
|
||||||
self.assertTrue(instrumentor._is_instrumented)
|
self.assertTrue(instrumentor._is_instrumented_by_opentelemetry)
|
||||||
|
Reference in New Issue
Block a user