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

Add support for programmatic instrumentation #579

Merged
merged 13 commits into from
Apr 22, 2020

Conversation

ocelotl
Copy link
Contributor

@ocelotl ocelotl commented Apr 14, 2020

Fixes #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.

@ocelotl ocelotl added doc Documentation-related ext instrumentation Related to the instrumentation of third party libraries or frameworks labels Apr 14, 2020
@ocelotl ocelotl requested a review from a team as a code owner April 14, 2020 21:11
@ocelotl ocelotl self-assigned this Apr 14, 2020
@mauriciovasquezbernal
Copy link
Member

I think BaseInstrumentor is becoming too complicated for the value it adds. I strongly feels we could achieve the same without it.

BaseInstrumentor has currently two goals, to force developers of an instrumented library to implement an interface, _automatic_instrument and _automatic_uninstrument in terms of this PR glossary, and to avoid instrumenting twice, i.e, to guarantee idempotence.

That interface is important because it's called by the autoinstrumenation command here

I think we could implement the instrument and uninstrument functions directly at the module level. If we are worried about the signature not being the correct one, we can do a runtime check for it. The developer of the library should guarantee idempotence, it is simple to implement and will give the developer full control.

I also think we should just call them instrument() and uninstrument(), both functions could be called by the autoinstrumentation command and by the user too. The instrument function has some limitations, for instance it could not work if it is called after some module is imported or so on, we could make it clear in the documentation and we could also add some warnings to inform the user about it.

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 Flask we could make InstrumentedFlask public to be use as:

from opentelemetry.ext.flask import InstrumentedFlask
app = InstrumentedFlask(__name__)
# app is now instrumented

To summarize my proposal:

  1. Get rid of BaseInstrumentor.
  2. Implement instrument, uninstrument functions in the library modules. Developer should take care of idempotence. In the instrument case some warnings should be printed if it is not possible to perform the instrumentation, for instance if the module was already imported.
  3. To avoid having a "programmatic instrumentation" function. Each framework could provide different mechanisms when the user wants a more granular control of instrumentation, for instance to provide an instrumented class (Flask), to provide a function to disable instrumentation in a Session object (requests) and so on.

I'd love to get more feedback on this.

@ocelotl
Copy link
Contributor Author

ocelotl commented Apr 15, 2020

I think BaseInstrumentor is becoming too complicated for the value it adds. I strongly feels we could achieve the same without it.

BaseInstrumentor has currently two goals, to force developers of an instrumented library to implement an interface, _automatic_instrument and _automatic_uninstrument in terms of this PR glossary, and to avoid instrumenting twice, i.e, to guarantee idempotence.

That interface is important because it's called by the autoinstrumenation command here

I think we could implement the instrument and uninstrument functions directly at the module level. If we are worried about the signature not being the correct one, we can do a runtime check for it. The developer of the library should guarantee idempotence, it is simple to implement and will give the developer full control.

I also think we should just call them instrument() and uninstrument(), both functions could be called by the autoinstrumentation command and by the user too. The instrument function has some limitations, for instance it could not work if it is called after some module is imported or so on, we could make it clear in the documentation and we could also add some warnings to inform the user about it.

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 Flask we could make InstrumentedFlask public to be use as:

from opentelemetry.ext.flask import InstrumentedFlask
app = InstrumentedFlask(__name__)
# app is now instrumented

To summarize my proposal:

  1. Get rid of BaseInstrumentor.
  2. Implement instrument, uninstrument functions in the library modules. Developer should take care of idempotence. In the instrument case some warnings should be printed if it is not possible to perform the instrumentation, for instance if the module was already imported.

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.

  1. To avoid having a "programmatic instrumentation" function. Each framework could provide different mechanisms when the user wants a more granular control of instrumentation, for instance to provide an instrumented class (Flask), to provide a function to disable instrumentation in a Session object (requests) and so on.

Just to be clear, I am not suggesting that we have a programmatic instrumentation function for every instrumentation (that is why there is no programmatic_instrument method in BaseInstrumentor). This PR only adds some convenience functionality (the BaseInstrumentor.protect_instrument) decorator that may be used for programmatic instrumentation if the developer finds it convenient to implement instrumentation in this way.

I'd love to get more feedback on this.

def _instrument(self):
self._original_flask = flask.Flask
def _instrument(
self, flask_class=None
Copy link
Contributor

Choose a reason for hiding this comment

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

any reason not to use kwargs here? and in _uninstrument?

Copy link
Contributor Author

@ocelotl ocelotl Apr 16, 2020

Choose a reason for hiding this comment

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

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

Copy link
Contributor

Choose a reason for hiding this comment

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

but you could pull flask_class from the kwargs instead of overriding the method signature correct? any reason not to go that route?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

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

Copy link
Contributor

@codeboten codeboten left a comment

Choose a reason for hiding this comment

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

Only question regarding overriding the interface with different arguments. Otherwise this is a good improvement!

@c24t c24t added the needs reviewers PRs with this label are ready for review and needs people to review to move forward. label Apr 16, 2020
Copy link
Member

@mauriciovasquezbernal mauriciovasquezbernal left a comment

Choose a reason for hiding this comment

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

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)

Choose a reason for hiding this comment

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

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.

Copy link
Contributor

Choose a reason for hiding this comment

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

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:

Choose a reason for hiding this comment

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

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

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

Copy link
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.

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)
Copy link
Contributor

Choose a reason for hiding this comment

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

Any naming conflicts in this example due to Flask imported library and instantiated variable?

Choose a reason for hiding this comment

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

That's actually the purpose of it, to overwrite the imported Flask to make it instrumented.

flask.Flask = _InstrumentedFlask

def _uninstrument(self):
flask.Flask = self._original_flask
return None
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is None returned if the original class is not supplied? Shouldn't InstrumentedFlask be returned?

@lzchen
Copy link
Contributor

lzchen commented Apr 19, 2020

I'm not able to find the automatic_instrument and automatic_uninstrument methods. Also the protect_instrument decorator is not there either? It seems like the description does not match the changes or some changes have been made to deviate from the original proposal?

@ocelotl ocelotl changed the title Add some support for programmatic instrumentation Add support for programmatic instrumentation Apr 19, 2020
@ocelotl
Copy link
Contributor Author

ocelotl commented Apr 19, 2020

I'm not able to find the automatic_instrument and automatic_uninstrument methods. Also the protect_instrument decorator is not there either? It seems like the description does not match the changes or some changes have been made to deviate from the original proposal?

Thanks for pointing that out, I have updated the comment.

@mauriciovasquezbernal
Copy link
Member

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.

@ocelotl
Copy link
Contributor Author

ocelotl commented Apr 20, 2020

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...

@ocelotl
Copy link
Contributor Author

ocelotl commented Apr 20, 2020

Ok, this has been split, let's move the conversation of Flask changes to #601.

Copy link
Member

@mauriciovasquezbernal mauriciovasquezbernal left a comment

Choose a reason for hiding this comment

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

A couple of non blocking comments.

self._is_instrumented = True
return result

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

Choose a reason for hiding this comment

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

What about removing "auomatically" and keeping the message as it was?

Copy link
Contributor

Choose a reason for hiding this comment

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

+1, since this can be called both from auto and programmatic instrumentation, the original warning was clearer

Copy link
Contributor

@codeboten codeboten left a comment

Choose a reason for hiding this comment

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

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

self._is_instrumented = True
return result

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

Choose a reason for hiding this comment

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

+1, since this can be called both from auto and programmatic instrumentation, the original warning was clearer

self._is_instrumented = False
return result

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

Choose a reason for hiding this comment

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

same comment about the warning message

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

@toumorokoshi toumorokoshi merged commit 305c1f4 into open-telemetry:master Apr 22, 2020
codeboten pushed a commit to codeboten/opentelemetry-python that referenced this pull request Apr 23, 2020
…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.
srikanthccv pushed a commit to srikanthccv/opentelemetry-python that referenced this pull request Nov 1, 2020
closes open-telemetry#579

Signed-off-by: Olivier Albertini <olivier.albertini@montreal.ca>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
doc Documentation-related instrumentation Related to the instrumentation of third party libraries or frameworks needs reviewers PRs with this label are ready for review and needs people to review to move forward.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add some support for programmatic instrumentation
6 participants