Skip to content

feat(django): add instrumentation for django signals #1526

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

Merged
merged 15 commits into from
Sep 19, 2022
Merged

feat(django): add instrumentation for django signals #1526

merged 15 commits into from
Sep 19, 2022

Conversation

BeryJu
Copy link
Contributor

@BeryJu BeryJu commented Jul 28, 2022

Add instrumentation for Django signals

@antonpirker antonpirker added this to the Django update milestone Aug 2, 2022
@antonpirker
Copy link
Member

Hey @BeryJu !
Thanks for this contribution! On the first glance it looks great.
Please fix the one problem the linter is reporting.
I will see If I can review and test this properly this week, so we can get this merged!

@antonpirker
Copy link
Member

Here is a request without the Signals instrumentation:

Screenshot 2022-08-03 at 13 20 47

Here is the same request with the Signals instrumentation:

Screenshot 2022-08-03 at 13 20 23

@antonpirker
Copy link
Member

Hey @BeryJu !

I have now tried your signals instrumentation. See the screenshots above.
Are you creating a span for each signal that is triggered, right?
Because those timings are always 0.01 it is not really adding a lot of value.

But, would be it be very difficult to add a span for all the receiver functions? So we would get the timing of the actual functions that where running because of signals? This would add really a LOT of value for all the Django developers out there!

@BeryJu
Copy link
Contributor Author

BeryJu commented Aug 3, 2022

Hey @antonpirker, the PR should add a span for each signal receiver function. I used to run this modified library for https://github.com/goauthentik/authentik but have since reverted to the upstream SDK so I haven't actually tested this in a bit. I'll have a look this evening

def patch_signals():
"""Patch django signal receivers to create a span"""

def send(self: Signal, sender, **kwargs):
Copy link
Member

Choose a reason for hiding this comment

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

Here you basically replace the existing send() function with your own version.

Best practice is, that we just wrap the existing function and run our code before or after the existing function.

See this as a template: https://github.com/getsentry/sentry-python/blob/master/sentry_sdk/integrations/starlette.py#L204-L218

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yeah I wanted to originally just wrap the existing function for improved compatibility, but with that there's no way to instrument individual signal handlers

Copy link
Member

Choose a reason for hiding this comment

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

I guess it can work by patching _live_receivers() and instead of returning an array of receivers (=functions) we wrap all the elements of the array in a function that creates a span and then calls the old receiver.

Something like this:

old_live_receivers = Signal._live_receivers

def _sentry_live_receivers(self, sender)
    receivers = old_live_receivers(self, sender)

    def _receiver_with_span(*args, **kwargs):
        old_receiver = receivers[idx]
        name = old_receiver.__name__
        with hub.start_span(    
            op="django.signals",
            description=name,
        ) as span:
            span.set_data("signal", name)
            return old_receiver(*args, **kwargs)
            
    for idx, receiver in enumerate(receivers):
        receivers[idx] = _receiver_with_span

    return receivers

Signal._live_receivers = _sentry_live_receivers

Note: this is untested code, I just wrote this on top of my head without ever running it. But I did something similar in another integration a couple of days ago, so it might work.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yeah thats a good idea actually

@BeryJu
Copy link
Contributor Author

BeryJu commented Aug 3, 2022

image

I just re-checked and indeed a trace is created for each handler, the dotted-import-path for each entry is a signal handler (the times still seem a bit low though)

@antonpirker
Copy link
Member

I just updated my sample project to actually doing some work in the signal receivers (added a time.sleep()) and now the screenshot looks like I envisioned it:

Screenshot 2022-08-04 at 09 34 03

So this adds really value to Django projects!

Now we just need to find a way to instrument individual signal handlers with wrapping instead of overriding the send function and we are good. Having it like as is is not going to work, because it could change the behavior of users code when they update the django version and we never do that.

@antonpirker
Copy link
Member

I added a comment with a possible solution to my review @BeryJu . Please have a look.

And a heads up: I will be on vacation next week and the following week. So this will probably be merged after my vacation.

@antonpirker
Copy link
Member

Thanks for the quick update @BeryJu !
This is now working for .send() and .send_robut() right?

Unfortunately I will not be able to review it befor my 2 week vacation, but maybe a colleague (@sl0thentr0py ) picks it up and reviews it.

@antonpirker
Copy link
Member

Hey @BeryJu !
Before I head into my vacation: if you send me your shipping address to: anton (dot) pirker at sentry |dot| io, I can send you a little token of appreciation. (after my vacation) :-)

@BeryJu
Copy link
Contributor Author

BeryJu commented Aug 5, 2022

Thanks for the quick update @BeryJu ! This is now working for .send() and .send_robut() right?

Should work for both, I have not actually tested it myself though (will see if I get some time to do so this weekend). Funnily enough I'll also be on vacation for the next two weeks, so no worries about the review

@sl0thentr0py sl0thentr0py self-requested a review August 8, 2022 10:06
@sl0thentr0py sl0thentr0py removed their assignment Aug 29, 2022
@antonpirker
Copy link
Member

Hey @BeryJu

Sorry for the delay! I will have a look at the PR tomorrow, promised!

@antonpirker
Copy link
Member

The new behavior breaks some our our tests. Most notably this one: https://github.com/getsentry/sentry-python/blob/master/tests/integrations/django/test_basic.py#L687-L731

Could you change this test (and maybe other failing ones) to reflect the new behavior @BeryJu ?

HINT: Navigating the test errors is still a bit cumbersome. But If you click on the details of the "CI / Run Tests" check above and then search for "error:" you find the actual error message the fastest.

Thanks a lot!

@BeryJu
Copy link
Contributor Author

BeryJu commented Sep 13, 2022

Tests should be fixed now, also added type annotations

btw, stickers arrived, thank you very much!

@antonpirker
Copy link
Member

That was fast! Amazing! Only the tests for old versions (Python 2.7 and 3.5) are failing. Can you please take a look @BeryJu ?

@BeryJu
Copy link
Contributor Author

BeryJu commented Sep 14, 2022

Should be all good now, missed some special cases for older django versions

@antonpirker
Copy link
Member

I will do some manual testing again, and then it will be released with the next version. (so probably early next week) Congratulations and Thankyou @BeryJu

@antonpirker antonpirker enabled auto-merge (squash) September 16, 2022 14:52
@antonpirker
Copy link
Member

Hey @BeryJu

Really great work. You are not by any chance a Go Freelancer and want to help us give our Go SDK a little love?

@antonpirker
Copy link
Member

If yes, you can message me at anton dot pirker ät sentry dot io

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants