-
Notifications
You must be signed in to change notification settings - Fork 561
feat(integrations): Add tracing to DramatiqIntegration #4571
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
feat(integrations): Add tracing to DramatiqIntegration #4571
Conversation
Adds tracing support to DramatiqIntegration getsentry#3454
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @Igreh, I took a quick glance at your PR – at a high level this seems reasonable; however, before I do a full review, I would appreciate if you could please add some tests for these changes, since we need tests before we can consider merging this.
sentry_sdk/integrations/dramatiq.py
Outdated
""" | ||
|
||
# type: contextvars.ContextVar[Transaction] | ||
_transaction = contextvars.ContextVar("_transaction", default=None) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are we using a ContextVar
here, instead of just a simple instance variable on the SentryMiddleware
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To isolate variable between threads:
- Dramatiq can be started in multithreading mode:
dramatiq --processes 1 --threads 5 ...
- And I was inspired by official middleware:
CurrentMessageMiddleware
I will try recheck if it's actually needed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Confirmed.
One SentryMiddleware
instance will be shared between threads. So, isolating is necessary.
Previous (actually current) realisation adds _scope_manager to message, but I think ContextVar is much cleaner solution
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #4571 +/- ##
==========================================
+ Coverage 84.47% 84.55% +0.07%
==========================================
Files 158 158
Lines 16506 16535 +29
Branches 2865 2868 +3
==========================================
+ Hits 13944 13981 +37
+ Misses 1712 1706 -6
+ Partials 850 848 -2
|
Hi! Edit: |
Hey @Igreh, it'd be good to have "end-to-end" (i.e., not unit tests) for the different tracing code paths. Stuff like:
...and/or whatever you consider important so that when the test suite is run and ends up green, you'd have a reasonable amount of confidence that tracing in the Dramatiq integration is working. You can have a look at some of these similar integrations and their test suites (e.g. arq, huey) for inspiration. Thanks for working on this! Let us know if you need any help. |
- add trace propagation - set dramatiq_task_id as tag instead of extra - new tests - fix mypy issues Issue: getsentry#3454
Hi @sentrivana ! |
Hi @sentrivana ! I'm using (and testing) my current integration on real project and |
Hey again 👋🏻 Thanks for your time on this!
What you're seeing is likely dramatiq logging the error when it happens, and our What you can do is add an (I know you're not working on the error part of the integration in this PR but it's still a good improvement so feel free to include it.)
Not sure off the top of my head how this works in our other task queue integrations, but my hunch is that retries should be under one trace. |
- Start new trace in case of retrying task. Such decision makes life easier to investigate failed transactions in complicated traces. - also fix logging issue in tests Issue: getsentry#3454
Good day everyone! Last patches are aimed to solve (I hope) all current issues:
I decided to not include retries under one trace. As mentioned in commit: "Start new trace in case of retrying task. Such decision makes life easier to investigate failed transactions in complicated traces.". Let me know if anything needs to be improved or reworked. Thanks in advance! |
Hey @Igreh, thanks for coming back to the PR and addressing the feedback! CI looks great, I'll give the PR a look this week. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey @Igreh, thanks for bearing with me. Left a few comments. :)
sentry_sdk/integrations/dramatiq.py
Outdated
name=message.actor_name, | ||
op=OP.QUEUE_TASK_DRAMATIQ, | ||
source=TransactionSource.TASK, | ||
# origin=DramatiqIntegration.origin, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The origin
looks good, any reason it's commented out?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No reasons. Will fix it. Thanks
sentry_sdk/integrations/dramatiq.py
Outdated
scope = sentry_sdk.get_current_scope() | ||
scope.set_transaction_name(message.actor_name) | ||
scope.set_extra("dramatiq_message_id", message.message_id) | ||
scope.set_tag("dramatiq_message_id", message.message_id) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The SDK shouldn't set tags on its own (we still do this in a couple places, but are avoiding it for new code)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a little bit confused here. What do you mean exactly by "SDK shouldn't set tags"?
Am I do it wrong now or you wanna say setting tags is only allowed for client's code and no tags allowed in SDK?
I definitely want to set dramatiq_message_id for each task like it happens in celery integration:
message_id is extremely helpful in investigating task failures
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
setting tags is only allowed for client's code and no tags allowed in SDK?
This. Going forward, we're not setting tags in the SDK. You can still set dramatiq_message_id
with e.g. span.set_data()
and it should be searchable.
sentry_sdk/integrations/dramatiq.py
Outdated
DramatiqIntegration. | ||
""" | ||
|
||
_transaction = ContextVar("_transaction") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I already may have asked this in an earlier iteration, but do we need the ContextVar here? The scopes themselves are stored in ContextVars and ideally they should govern the reference to the current transaction (this should already be working as long as the transaction/span is correctly set on the scope, which it is as long as you use start_span/transaction
context manager or just __enter__
directly)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It makes sense.
Looks like I really needn't store transaction separately and I can get it directly from scope:
transaction = sentry_sdk.get_current_scope().transaction
Right? =)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Exactly, that'd be the idea.
Hi! Thanks for review - super helpful as usual) |
@Igreh Left some feedback, but we're pretty much almost there :) Thanks for your continued work on this! Looks like the config in |
- No need to store transaction separately. All contextvar mentions removed - dramatiq_mesage_id tag removed Issue: getsentry#3454
@sentrivana |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Heyo @Igreh, I'm back with the last round.
I've tried it out and it works great! The code looks so much nicer without the custom ContextVar
, too. 🙏🏻 I've noticed a couple things in my last round of review, most of them minor, and one that we need to fix (but should be easy since the logic was mostly there before, just needs adapting a little to use a different kind of scope).
LMK if you still have time to make the changes, if not, I'm also happy to take this over the finish line. As always, if anything is unclear, just let me know.
sentry_sdk/integrations/dramatiq.py
Outdated
|
||
def before_enqueue(self, broker, message, delay): | ||
# type: (Broker, Message[R], int) -> None | ||
message.options["sentry_headers"] = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: I don't think we're really in risk of collisions here, but let's maybe call this _sentry_headers
just to denote it's something internal
message.options["sentry_headers"] = { | |
message.options["_sentry_headers"] = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
sentry_sdk/integrations/dramatiq.py
Outdated
scope.clear_breadcrumbs() | ||
scope.add_event_processor(_make_message_event_processor(message, integration)) | ||
|
||
sentry_headers = message.options.get("sentry_headers") or {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you go with my renaming suggestion from above:
sentry_headers = message.options.get("sentry_headers") or {} | |
sentry_headers = message.options.get("_sentry_headers") or {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
integration = sentry_sdk.get_client().get_integration(DramatiqIntegration) | ||
if integration is None: | ||
return |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please keep this -- it's a fallback in case the integration/the SDK/the client has been deactivated in the meantime. In that case we shouldn't do anything.
We should have this also in the new before_enqueue
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done, but I believe it's unnecessary due to this: https://github.com/getsentry/sentry-python/pull/4571/files#diff-2722e3fe31f13ffc24072313765f1fc89f0f0721154b7ca072bb46b1f9573f5bR84
Middleware won't be in use if integration is not enabled
message._scope_manager = sentry_sdk.new_scope() | ||
message._scope_manager.__enter__() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We still need some sort of scope management in order to make sure the data we collect about tasks is isolated.
The general rule of thumb is: if you start a transaction, you should start it in a new isolation scope. See for example huey.
So we should start an isolation scope right after the initial if integration is None: return
check with
scope = sentry_sdk.isolation_scope()
message._scope_manager = scope
scope.__enter__()
Everything that we do on the scope
later in the function can stay, but it should be done on the isolation scope, not current scope as before.
And finally, we need to __exit__
the saved scope in after_process_message
with message._scope_manager.__exit__(None, None, None)
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
but please recheck it)
- new isolation scope for each dramatiq task - slightly changed sentry headers name to prevent collisions - revert: set dramatiq_message_id as additional data Issue: getsentry#3454
Hi @sentrivana! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ty @Igreh! Let's get this merged! :)
Adds tracing support to DramatiqIntegration #3454