-
Notifications
You must be signed in to change notification settings - Fork 116
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
Monitor serving of Rack response bodies #1037
Conversation
@tombruijn @jeffkreeftmeijer this is a draft - wanted to ask you what you think. I have encountered in a few cases that this would be very useful, as currently streaming (or non-presized) bodies elide the instrumentation and (more importantly) do not send errors. One can install a StreamWrapper but it is not going to be compatible with Rack 3 - and besides, why not use the wrapper everywhere anyway? |
This will invariably move things closer for #329 too |
Hi @julik, this change is looking good so far 👍 |
Ok, let me get the PR to the place where all tests pass and all the bodies are tested, then we can take a look how to split it into two commits in a more logical way. |
9f80955
to
6344521
Compare
Limited to just adding the body, LMK if you want the class hierarchy change in a different PR |
I am curious why |
It doesn't really matter when it's called. The start time we read comes from a header, which doesn't change during the request. We mostly call it at the end when all the other metadata is also set. |
Okido. This should be good to go now. Do you want to add an instrumentation block inside the serving of the body too? |
Thanks for the updates! Do you have an app or some part of a controller that we can test your changes with? That would help ensure we're reviewing the things your fixing/changing. So far, testing it on our ruby/rails7-sidekiq app, I'm getting no data from Rails requests. Getting a lot of these logs from the transaction initializer, so it's a bit confused at the moment about which transaction is active. This happens when a transaction is being created when another is active, or viewed in another way, when the previous request's transaction hasn't been completed.
|
lib/appsignal/rack/body_wrapper.rb
Outdated
@transaction&.set_error(error) | ||
raise error | ||
ensure | ||
@transaction&.complete |
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.
You need to call Appsignal::Transaction.complete_current!
so it unsets the current transaction stored in the Thread variable.
appsignal-ruby/lib/appsignal/transaction.rb
Lines 65 to 74 in 4c2845c
ensure | |
clear_current_transaction! | |
end | |
# Remove current transaction from current Thread. | |
# @api private | |
def clear_current_transaction! | |
Thread.current[:appsignal_transaction] = nil | |
end | |
end |
I will take a look what it could be. I checked out the repo and I was wondering what API key I should be using if I want to run one of the apps there, and how do the actual requests to the apps get done? does it use Net::HTTP or I am supposed to visit the pages by myself?
That's probably the complete bit you mentioned earlier. But why would |
I might actually try it with Cheddar briefly 😆 but let me see whether I can get the |
Thanks for looking into the test setups @julik!
Your organization-level API key should be fine.
You go to http://localhost:4001 and click around. These are primarily for manual testing. |
Aight. I'll click around then. |
When I changed the line 51 I commented on in this comment with I hope you see the same behavior in your app. Check the I'd also to test a streaming request hitting this new code. |
@ioquatix apologies for summoning, but could you help me check this? We want the |
This is a tricky thing to implement. https://github.com/rack/rack/blob/main/lib/rack/body_proxy.rb and similar for older versions of Rack would be the gold standard for "proxy a body". The expectation is you'd define Long running responses could run for minutes or hours or longer. Is that okay? If you want validation, you should test on Rack 1, 2 and 3, using If you want a conformance test suite that runs on lots of different servers, you could check out |
Thanks for your input @ioquatix it does seem that with the fact that we need to |
@tombruijn this is ready to go as far as I am concerned. Does lintje have an autofix option? I've added test code to appsignal/test-setups#186 but I haven't tested them all, I presume it's all working fine to be honest. If you could give the test setups a second look I would be grateful. |
Also: one could in theory wrap the serving out of the body (what's inside |
The Rack response body is _not_ an Enumerable, it just responds to #each. The method was assuming otherwise, which led to breakage when trying to use appsignal/appsignal-ruby#1037
The Rack response body is _not_ an Enumerable, it just responds to #each. The method was assuming otherwise, which led to breakage when trying to use appsignal/appsignal-ruby#1037
Done! |
This is a message from the daily scheduled checks. |
which - it would seem - requires another conditional, but it doesn't.
Add changeset so the change can be released and is shown in the CHANGELOG.md file upon release.
Add changeset so the change can be released and is shown in the CHANGELOG.md file upon release.
047f1b5
to
9984156
Compare
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.
Looks good to me! The test setup works fine. I have some minor nitpicks about how the information is presented.
These are very uninformed suggestions, in that I'm not a Rack expert by any means, nor am I familiar with the streaming use case and which information would be useful in it.
That is, if some of the information that I suggest removing is actually useful, feel free to ignore the suggestion.
- Instead of
response_body_each
, I think the category name might work better as a verb, like "process_action" or "query"? I'm thinkingstream_response
? - I don't know whether there's much value on having different category names for each internal implementation of the body (
response_body_each
,response_body_call
, ...) I feel that this is more likely to confuse folks than to inform them? - As the cherry on top, it would be nice if the category name wasn't too long -- very long category names eat into the event timeline in the UI.
Suggestion: use a single category for all the response body instrumentation blocks (stream_response.rack
?) and, if the information about the specific implementation that's being used is relevant, add that as part of the title, which shows up on the UI on hover:
Appsignal.instrument(
"stream_response.rack",
"Stream response body (#to_path)"
) { @body.to_path }
- Regarding the body close event: is this a relevant event to instrument? I believe that this event would always happen right after the body streaming event. Does this event add any information to the event timeline that isn't already added by the finishing time of the body streaming event?
Suggestion: do not instrument the call to .close
on the body.
I'd vouch it does, because there can be operations there which take a long time. For example - deleting temp data, checking in connections into a connection pool, running some kind of
I'll leave it to @tombruijn be the judge on that, no objections from me. I separated them so that it would be apparent which response body method got called - this is related to the Rails bugs linked inline from the comments, but no strong opinion. |
Instrumenting |
There's a lot of technical stuff in event names now already from libraries like Rails that's not always obvious, but we can make the experience a little easier now that we have the option. The event name should be something And let's keep the close event. @julik can you update the event categories and add the event titles if you agree? |
@tombruijn sure - can you list out the "blessed" event names for me? |
Event names:
|
Ah I mistyped something in my previous comment. I'll add a commit and merge in a bit. |
I mistyped the titles for the events in my comment on the GitHub issue. This is what I meant to type.
Thanks for the PR @julik 🎉 This will be included in the next release, which will be a minor version bump. I think we can publish it tomorrow. |
This has been released in Ruby gem 3.6.0 🚀 |
Instrument what happens when a Rack response body is read and closed. We instrument these events by wrapping the response body in the appropriate response body BodyWrapper subclass, depending on the response object type. This change was previously sent in as PR #1037 and reverted in #1052. We saw issues with multiple requests reported in the same transaction. This problem occurred when there was an error in the middleware stack, and the BodyWrapper never closed the response body. I've removed any transaction complete logic from the BodyWrapper in the original PR. We now have a Rack EventHandler that ensures the transaction is always closed per request, even when such an error scenario occurs again. The only scenario in which we don't support this response body instrumentation is when no EventHandler is present in the middleware stack. This level of support is acceptable to me. We want people to use the EventHandler. Most of our automatic instrumentations are updated to add it to the middleware stack. From the original commit: 7da96e7 > Some work might be getting done within a Rack response body. For > example, when ActionController::Streaming is used, or when a Rack app > elects to stream a response. > > The Rack SPEC doc defines the behavior in sufficient detail to wrap > this into the current AppSignal transaction. > > Sadly, there is some work involved in supporting all the right > methods, so just "one-size-fits-all" wrapper will not quite work. Co-authored-by: Julik Tarkhanov <me@julik.nl>
Instrument what happens when a Rack response body is read and closed. We instrument these events by wrapping the response body in the appropriate response body BodyWrapper subclass, depending on the response object type. This change was previously sent in as PR #1037 and reverted in #1052. We saw issues with multiple requests reported in the same transaction. This problem occurred when there was an error in the middleware stack, and the BodyWrapper never closed the response body. I've removed any transaction complete logic from the BodyWrapper in the original PR. We now have a Rack EventHandler that ensures the transaction is always closed per request, even when such an error scenario occurs again. The only scenario in which we don't support this response body instrumentation is when no EventHandler is present in the middleware stack. This level of support is acceptable to me. We want people to use the EventHandler. Most of our automatic instrumentations are updated to add it to the middleware stack. From the original commit: 7da96e7 > Some work might be getting done within a Rack response body. For > example, when ActionController::Streaming is used, or when a Rack app > elects to stream a response. > > The Rack SPEC doc defines the behavior in sufficient detail to wrap > this into the current AppSignal transaction. > > Sadly, there is some work involved in supporting all the right > methods, so just "one-size-fits-all" wrapper will not quite work. Co-authored-by: Julik Tarkhanov <me@julik.nl>
Instrument what happens when a Rack response body is read and closed. We instrument these events by wrapping the response body in the appropriate response body BodyWrapper subclass, depending on the response object type. This change was previously sent in as PR #1037 and reverted in #1052. We saw issues with multiple requests reported in the same transaction. This problem occurred when there was an error in the middleware stack, and the BodyWrapper never closed the response body. I've removed any transaction complete logic from the BodyWrapper in the original PR. We now have a Rack EventHandler that ensures the transaction is always closed per request, even when such an error scenario occurs again. The only scenario in which we don't support this response body instrumentation is when no EventHandler is present in the middleware stack. This level of support is acceptable to me. We want people to use the EventHandler. Most of our automatic instrumentations are updated to add it to the middleware stack. From the original commit: 7da96e7 > Some work might be getting done within a Rack response body. For > example, when ActionController::Streaming is used, or when a Rack app > elects to stream a response. > > The Rack SPEC doc defines the behavior in sufficient detail to wrap > this into the current AppSignal transaction. > > Sadly, there is some work involved in supporting all the right > methods, so just "one-size-fits-all" wrapper will not quite work. Part of #1099 Co-authored-by: Julik Tarkhanov <me@julik.nl>
Instrument what happens when a Rack response body is read and closed. We instrument these events by wrapping the response body in the appropriate response body BodyWrapper subclass, depending on the response object type. This change was previously sent in as PR #1037 and reverted in #1052. We saw issues with multiple requests reported in the same transaction. This problem occurred when there was an error in the middleware stack, and the BodyWrapper never closed the response body. I've removed any transaction complete logic from the BodyWrapper in the original PR. We now have a Rack EventHandler that ensures the transaction is always closed per request, even when such an error scenario occurs again. The only scenario in which we don't support this response body instrumentation is when no EventHandler is present in the middleware stack. This level of support is acceptable to me. We want people to use the EventHandler. Most of our automatic instrumentations are updated to add it to the middleware stack. From the original commit: 7da96e7 > Some work might be getting done within a Rack response body. For > example, when ActionController::Streaming is used, or when a Rack app > elects to stream a response. > > The Rack SPEC doc defines the behavior in sufficient detail to wrap > this into the current AppSignal transaction. > > Sadly, there is some work involved in supporting all the right > methods, so just "one-size-fits-all" wrapper will not quite work. Part of #1099 Co-authored-by: Julik Tarkhanov <me@julik.nl>
Some work might be getting done within a Rack response body. For example, when ActionController::Streaming is used, or when a Rack app elects to stream a response.
The Rack SPEC doc actually defines the behavior in sufficient detail to wrap this into the same Appsignal transaction.
The Rails instrumentation may also inherit from the more generic one as most methods and constants are shared then
To do