Add support for programmatic instrumentation#579
Add support for programmatic instrumentation#579toumorokoshi merged 13 commits intoopen-telemetry:masterfrom
Conversation
|
I think
That interface is important because it's called by the autoinstrumenation command here I think we could implement the I also think we should just call them About programmatic instrumentation, I think it is a concept, having a function with that name is not clear at all. In the specific case of from opentelemetry.ext.flask import InstrumentedFlask
app = InstrumentedFlask(__name__)
# app is now instrumentedTo summarize my proposal:
I'd love to get more feedback on this. |
We should not get rid of BaseInstrumentor, because if we do, we'll end up implementing manually the very same checks, accurate error reporting and idempotence that this ABC does for us already.
Just to be clear, I am not suggesting that we have a programmatic instrumentation function for every instrumentation (that is why there is no
|
| def _instrument(self): | ||
| self._original_flask = flask.Flask | ||
| def _instrument( | ||
| self, flask_class=None |
There was a problem hiding this comment.
any reason not to use kwargs here? and in _uninstrument?
There was a problem hiding this comment.
yes, only because kwargs is not used in _instrument nor in _uninstrument. I mean, there is no reference to kwargs in the code of _instrument or _uninstrument
There was a problem hiding this comment.
but you could pull flask_class from the kwargs instead of overriding the method signature correct? any reason not to go that route?
There was a problem hiding this comment.
we could do that but it may not play nice with the documentation of the optional flask_class argument (not that there is any here, though 😅)
codeboten
left a comment
There was a problem hiding this comment.
Only question regarding overriding the interface with different arguments. Otherwise this is a good improvement!
There was a problem hiding this comment.
Even if I don't completely agree with having an ABC and so on, I think the changes to BaseInstrumentor in the last iteration are good. Making it a singleton and allowing arbitrary arguments helps.
However I think the changes to the Flask are not that good, the proposed way for users to enable instrumentation is rather complicated. I still think we should have instrument and uninstrument methods that are simple to use (just a single call, few parameters and not return value) that are used by both, the automatic instrumentation command and the users. I'm aware that in the users case this function could not work in all the cases, for instance if you call it too late in the code after importing some modules, but I think that with correct documentation and warnings they would be very useful.
For the programmatic case, we could use a much simpler approach, just expose and let the user interact with InstrumentedFlask.
from opentelemetry.ext.flask import InstrumentedFlask as Flask
app = Flask(...) # app will be instrumented
or
from flask import Flask
from opentelemetry.ext.flask import InstrumentedFlask
app1 = Flask(...) # this won't be instrument
app2 = InstrumentedFlask(...) # this will be
The instrument method should be used for automatic instrumentation and when the user want's to instrument everything in that framework, the later with some restrictions, like enforcing a proper order in the imports.
from opentelemetry.ext.flask import FlaskInstrumentor
FlaskInstrumentor().instrument()
from flask import Flask # needed to do after
app = Flask(...) # instrumented
The following case won't work, but we could print a warning to the user that the flask module is already imported and it might not work.
from flask import Flask
from opentelemetry.ext.flask import FlaskInstrumentor
FlaskInstrumentor().instrument()
app = Flask(...) # uninstrumented, reference to Flask was taken before instrumenting
| from flask import Flask | ||
| from opentelemetry.ext.flask import FlaskInstrumentor | ||
|
|
||
| Flask = FlaskInstrumentor().instrument(flask_class=Flask) |
There was a problem hiding this comment.
This looks too complicated to me. The instrument method does not do any instrumentation but just returns an instrumented version of the Flask class that the user has to assign to the local Flask reference. Btw, the current uninstrument will do nothing after that call, somehow the user will have to update the Flask reference to the original flask.Flask class.
There was a problem hiding this comment.
This leads me to believe we might need to patch the flask object instead of the class itself (like a middleware)? This way we will have direct control over what is instrumented or not. What if the user wants only some flask instances to be instrumented and others not to be? And same goes for uninstrumenting as well.
| def _instrument( | ||
| self, flask_class=None | ||
| ): # pylint: disable=arguments-differ | ||
| if flask_class is not None: |
There was a problem hiding this comment.
This if makes this function to behave very different. Related to my long comment above, the case where you pass flask_class is not doing any instrumentation but just returning _InstrumentedFlask.
There was a problem hiding this comment.
I think we can find another way of using _instrument here. Maybe pass the Flask app as was done before? That would be fine. I still would like to have instrumentation depend on it being done before something is imported only as a very last option because of how fragile and hard to debug this approach is (it also breaks PEP8).
There was a problem hiding this comment.
What is the reason to implement extra functionalities in _instrument()?, I think the scope of this method is to be called by the autoinstrument command and by users that want to enable instrumentation in all that framework. If we start adding extra functionalities and special cases we'll end up with a lot of different _instrument() what must be used in a specific way to work.
What do you think about exposing InstrumentedFlask to the user for the "programmatic instrumentation" case as I exposed before?
| from flask import Flask | ||
| from opentelemetry.ext.flask import FlaskInstrumentor | ||
|
|
||
| Flask = FlaskInstrumentor().instrument(flask_class=Flask) |
There was a problem hiding this comment.
Any naming conflicts in this example due to Flask imported library and instantiated variable?
There was a problem hiding this comment.
That's actually the purpose of it, to overwrite the imported Flask to make it instrumented.
|
|
||
| def _uninstrument(self): | ||
| flask.Flask = self._original_flask | ||
| return None |
There was a problem hiding this comment.
Why is None returned if the original class is not supplied? Shouldn't InstrumentedFlask be returned?
|
I'm not able to find the |
Thanks for pointing that out, I have updated the comment. |
|
I'd propose to split this PR up. I think we could easily agree on the changes to the instrumentor (make it a singleton and add kwargs). About the Flask I think there is still some discussion to do, so better to move ahead with the part we agree now. |
Good idea, splitting... |
|
Ok, this has been split, let's move the conversation of Flask changes to #601. |
mauriciovasquezbernal
left a comment
There was a problem hiding this comment.
A couple of non blocking comments.
opentelemetry-auto-instrumentation/src/opentelemetry/auto_instrumentation/instrumentor.py
Show resolved
Hide resolved
|
|
||
| _LOG.warning("Attempting to instrument while already instrumented") | ||
| _LOG.warning( | ||
| "Attempting to automatically instrument while already instrumented" |
There was a problem hiding this comment.
What about removing "auomatically" and keeping the message as it was?
There was a problem hiding this comment.
+1, since this can be called both from auto and programmatic instrumentation, the original warning was clearer
codeboten
left a comment
There was a problem hiding this comment.
Couple of minor comments, otherwise this makes the interface more usable. thanks for separating this from the flask changes, it makes the review much simpler
ext/opentelemetry-ext-flask/src/opentelemetry/ext/flask/__init__.py
Outdated
Show resolved
Hide resolved
|
|
||
| _LOG.warning("Attempting to instrument while already instrumented") | ||
| _LOG.warning( | ||
| "Attempting to automatically instrument while already instrumented" |
There was a problem hiding this comment.
+1, since this can be called both from auto and programmatic instrumentation, the original warning was clearer
|
|
||
| _LOG.warning("Attempting to uninstrument while already uninstrumented") | ||
| _LOG.warning( | ||
| "Attempting to automatically uninstrument while already" |
There was a problem hiding this comment.
same comment about the warning message
…try#579) Fixes open-telemetry#554 This makes it possible to call the instrument method with arguments that make programmatic instrumentation possible. This also makes the children of BaseInstrumentors to be singletons. In this way regardless of how many times the programmatic instrumentation or uninstrumentation methods are called they will only be executed once.
closes open-telemetry#579 Signed-off-by: Olivier Albertini <olivier.albertini@montreal.ca>
Fixes #554
This makes it possible to call the
instrumentmethod with arguments that make programmatic instrumentation possible.This also makes the children of
BaseInstrumentorsto be singletons. In this way regardless of how many times the programmatic instrumentation or uninstrumentation methods are called they will only be executed once.