Skip to content

Commit

Permalink
Add support for programmatic instrumentation
Browse files Browse the repository at this point in the history
  • Loading branch information
ocelotl committed Apr 14, 2020
1 parent c8b336d commit c97076b
Show file tree
Hide file tree
Showing 8 changed files with 126 additions and 58 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@
SimpleExportSpanProcessor(ConsoleSpanExporter())
)

FlaskInstrumentor().instrument()
app = flask.Flask(__name__)
Flask = FlaskInstrumentor().programmatic_instrument(flask.Flask)
app = Flask(__name__)
opentelemetry.ext.http_requests.enable(trace.get_tracer_provider())


Expand Down
8 changes: 4 additions & 4 deletions docs/getting-started.rst
Original file line number Diff line number Diff line change
Expand Up @@ -184,9 +184,6 @@ And let's write a small Flask application that sends an HTTP request, activating
.. code-block:: python
# flask_example.py
from opentelemetry.ext.flask import FlaskInstrumentor
FlaskInstrumentor().instrument() # This needs to be executed before importing Flask
import flask
import requests
Expand All @@ -195,13 +192,16 @@ And let's write a small Flask application that sends an HTTP request, activating
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import ConsoleSpanExporter
from opentelemetry.sdk.trace.export import SimpleExportSpanProcessor
from opentelemetry.ext.flask import FlaskInstrumentor
Flask = FlaskInstrumentor().programmatic_instrument(flask.Flask)
trace.set_tracer_provider(TracerProvider())
trace.get_tracer_provider().add_span_processor(
SimpleExportSpanProcessor(ConsoleSpanExporter())
)
app = flask.Flask(__name__)
app = Flask(__name__)
opentelemetry.ext.http_requests.enable(trace.get_tracer_provider())
@app.route("/")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,11 +158,20 @@ class FlaskInstrumentor(BaseInstrumentor):

def __init__(self):
super().__init__()
self._original_flask = None
self._original_flask_class = None

def _instrument(self):
self._original_flask = flask.Flask
def _automatic_instrument(self):
self._original_flask_class = flask.Flask
flask.Flask = _InstrumentedFlask

def _uninstrument(self):
flask.Flask = self._original_flask
def _automatic_uninstrument(self):
flask.Flask = self._original_flask_class

@BaseInstrumentor.protect_instrument
def programmatic_instrument(self, flask_class):
self._original_flask_class = flask_class
return _InstrumentedFlask

@BaseInstrumentor.protect_uninstrument
def programmatic_uninstrument(self):
return self._original_flask_class
25 changes: 0 additions & 25 deletions ext/opentelemetry-ext-flask/tests/conftest.py

This file was deleted.

3 changes: 3 additions & 0 deletions ext/opentelemetry-ext-flask/tests/test_flask_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,11 @@
from werkzeug.wrappers import BaseResponse

from opentelemetry import trace as trace_api
from opentelemetry.ext.flask import FlaskInstrumentor
from opentelemetry.ext.testutil.wsgitestutil import WsgiTestBase

Flask = FlaskInstrumentor().programmatic_instrument(Flask)


def expected_attributes(override_attributes):
default_attributes = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def run() -> None:

for entry_point in iter_entry_points("opentelemetry_instrumentor"):
try:
entry_point.load()().instrument() # type: ignore
entry_point.load()().automatic_instrument() # type: ignore
logger.debug("Instrumented %s", entry_point.name)

except Exception: # pylint: disable=broad-except
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,38 +26,82 @@
class BaseInstrumentor(ABC):
"""An ABC for instrumentors"""

def __init__(self):
self._is_instrumented = False
_instance = None
_is_instrumented = False

def __new__(cls):

if cls._instance is None:
cls._instance = object.__new__(cls)

return cls._instance

@staticmethod
def protect_instrument(method) -> None:
def inner(self, *args, **kwargs):
if self._is_instrumented: # pylint: disable=protected-access
_LOG.warning(
"Attempting to call %(method.__name__)s while "
"already instrumented"
)
return None

result = method(self, *args, **kwargs)
self._is_instrumented = True # pylint: disable=protected-access
return result

return inner

@staticmethod
def protect_uninstrument(method) -> None:
def inner(self, *args, **kwargs):
if not self._is_instrumented: # pylint: disable=protected-access
_LOG.warning(
"Attempting to call %(method.__name__)s while "
"already uninstrumented"
)
return None

result = method(self, *args, **kwargs)
self._is_instrumented = False # pylint: disable=protected-access
return result

return inner

@abstractmethod
def _instrument(self) -> None:
def _automatic_instrument(self) -> None:
"""Instrument"""

@abstractmethod
def _uninstrument(self) -> None:
def _automatic_uninstrument(self) -> None:
"""Uninstrument"""

def instrument(self) -> None:
def automatic_instrument(self) -> None:
"""Instrument"""

if not self._is_instrumented:
result = self._instrument()
result = self._automatic_instrument()
self._is_instrumented = True
return result

_LOG.warning("Attempting to instrument while already instrumented")
_LOG.warning(
"Attempting to automatically instrument while already instrumented"
)

return None

def uninstrument(self) -> None:
def automatic_uninstrument(self) -> None:
"""Uninstrument"""

if self._is_instrumented:
result = self._uninstrument()
result = self._automatic_uninstrument()
self._is_instrumented = False
return result

_LOG.warning("Attempting to uninstrument while already uninstrumented")
_LOG.warning(
"Attempting to automatically uninstrument while already"
" uninstrumented"
)

return None

Expand Down
59 changes: 48 additions & 11 deletions opentelemetry-auto-instrumentation/tests/test_instrumentor.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,25 +20,62 @@


class TestInstrumentor(TestCase):
class Instrumentor(BaseInstrumentor):
def _automatic_instrument(self):
return "instrumented"

def _automatic_uninstrument(self):
return "uninstrumented"

@BaseInstrumentor.protect_instrument
def programmatic_instrument(self, arg): # pylint: disable=no-self-use
return "programmatically_instrumented {}".format(arg)

@BaseInstrumentor.protect_uninstrument
def programmatic_uninstrument(
self, arg
): # pylint: disable=no-self-use
return "programmatically_uninstrumented {}".format(arg)

def test_protect(self):
class Instrumentor(BaseInstrumentor):
def _instrument(self):
return "instrumented"
instrumentor = self.Instrumentor()

with self.assertLogs(level=WARNING):
self.assertIs(instrumentor.automatic_uninstrument(), None)

self.assertEqual(instrumentor.automatic_instrument(), "instrumented")
with self.assertLogs(level=WARNING):
self.assertIs(instrumentor.automatic_instrument(), None)

self.assertEqual(
instrumentor.automatic_uninstrument(), "uninstrumented"
)

with self.assertLogs(level=WARNING):
self.assertIs(instrumentor.automatic_uninstrument(), None)

def test_singleton(self):
self.assertIs(self.Instrumentor(), self.Instrumentor())

def _uninstrument(self):
return "uninstrumented"
def test_protect_instrument_uninstrument(self):

instrumentor = Instrumentor()
instrumentor = self.Instrumentor()

with self.assertLogs(level=WARNING):
self.assertIs(instrumentor.uninstrument(), None)
self.assertIs(instrumentor.programmatic_uninstrument("test"), None)

self.assertEqual(instrumentor.instrument(), "instrumented")
self.assertEqual(
instrumentor.programmatic_instrument("test"),
"programmatically_instrumented test",
)

with self.assertLogs(level=WARNING):
self.assertIs(instrumentor.instrument(), None)
self.assertIs(instrumentor.programmatic_instrument("test"), None)

self.assertEqual(instrumentor.uninstrument(), "uninstrumented")
self.assertEqual(
instrumentor.programmatic_uninstrument("test"),
"programmatically_uninstrumented test",
)

with self.assertLogs(level=WARNING):
self.assertIs(instrumentor.uninstrument(), None)
self.assertIs(instrumentor.programmatic_uninstrument("test"), None)

0 comments on commit c97076b

Please sign in to comment.