-
-
Notifications
You must be signed in to change notification settings - Fork 21
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
Payload enhancements #32
base: main
Are you sure you want to change the base?
Conversation
3088873
to
05f09f1
Compare
Yield the payload to the instrumented blockThis would mutate the payload and we should not go that way. Not only that, when no payload giving, mutating will actually raise an error: it 'allows modifying the default payload ' do
captured = []
notifications.subscribe(:sql) do |event|
captured << event
end
notifications.instrument(:sql) do |payload|
payload[:modified] = true
end
end
# Effect
1) Subscribing to instrumentation events #instrument allows modifying the default payload
Failure/Error: payload[:modified] = true
FrozenError:
can't modify frozen Hash
# ./spec/integration/instrumentation_spec.rb:19:in `block (4 levels) in <top (required)>'
# ./lib/dry/monitor/notifications.rb:43:in `block in instrument'
# ./lib/dry/monitor/notifications.rb:11:in `measure'
# ./lib/dry/monitor/notifications.rb:43:in `instrument'
# ./spec/integration/instrumentation_spec.rb:18:in `block (3 levels) in <top (required)>' the reason why this feature was not in the Trigger subscribers even when the instrumented code raises an exceptionAs far as I appreciate the effort, I'm also against this feature. First of all, catching the The second argument against the code modifies the payload (that is commented above). Third argument: it makes the end-user code harder to develop (from the subscriber perspective) . I |
Right, I mentioned that in my commit message. I didn't want to make the change for the default payload from the frozen
I'm not clear on who the "we" is in this context? If "we" are dry-monitor, then the first change isn't modifying the payload at all. If "we" are the authors of the code we are instrumenting, then we are the owners of that payload.
I'm aware of the issues with catching Exception vs StandardError, and chose it deliberately. I was also careful to re-raise the exception with Ruby's bare My actual use-case is trying to instrument an HTTP client that I don't control, and send the timing and response information to InfluxDB. Here's a simplified version of that code: instrument("client.write", url: url, body: body) do |payload|
response = client.write(url, body) # raises ClientError on any non-2xx status
payload[:response] = response
response
rescue ClientError => ex
payload[:response] = ex.response
exception_notifier.capture(ex)
raise
end My subscriber needs access to the response object because I want to capture the response status code in my dashboard graphs. With no access to the payload inside the block, I can't add the response status to it. Also, if the status code is 4xx or 5xx, the upstream client code raises an exception, and in the current implementation of dry-monitor, none of my subscribers are called. I can sorta hack around the lack of the first feature by doing this: payload = {url: url, body: body}
instrument("client.write", payload) do
response = client.write(url, body)
payload[:response] = response
response
rescue ClientError => ex
payload[:response] = ex.response # Doesn't work because `payload` isn't in scope here
end However, if the block ever raises an exception (and I want that exception to bubble through), I have to write instrumented code like this (I think, I haven't actually tried it). payload = {url: url, body: body}
exception = nil
instrument("client.write", payload) do
begin
response = client.write(url, body)
payload[:response] = response
response
rescue ClientError => ex
payload[:response] = ex.response # Still not sure if `payload` is in scope
exception = ex # Of if exception is as well
end
end
raise exception # Now I've also lost the original backtrace Even if it does work, this code is far more complicated and error prone for the user. I urge you to reconsider, these features are absolutely critical for anyone looking for an AS::Notifications replacement in non-Rails applications. I would completely agree with you if supporting these features made the notifications code way more complicated, but this is a fairly trivial amount of code to unlock some very powerful functionality. |
eaa2dff
to
bcc5792
Compare
bcc5792
to
f5c2596
Compare
I've rebased this branch with the devtools changes on master. |
In general, we're looking at two things here:
That's why I'd appreciate if this PR could be split into two. I also think that exception handling could be implemented as either a plugin or configurable feature. I'm not sure yet if handling exceptions like in this PR by default is a good idea, especially that you'd like to handle @paul does this make sense?
Yeah dry-monitor is still considered "too beta". We should be able to finally make it stable some time in 2020, then proper docs will be written. |
Quick follow-up after talking to @mensfeld about it - he's got a very good point that handling exceptions within the same instrumentation block is an anti-pattern and it makes more sense to do something like this. I gotta say my brain is not entirely "in the zone" here so I need some time to think about it further. Let's put this on hold for now and I'll get back to you with more thoughts later this month. We can of course continue discussion in the meantime. |
@solnic Sure, I can split this into two PRs, no problem. My reasoning behind rescuing It looks like @josevalim wrote it into AS::Notifications 10 years ago, but the commit doesn't explain why. I'll see if I can ask him if he remembers. rails/rails@a76c7e6 Edited to add: I think this makes the most sense if you're instrumenting something that has a log subscriber. Even if the process gets killed before the instrumented block finished, the logger should probably still get called to log the event. I suppose it really depends on what you're using dry-monitor for, if its for "logging and instrumentation" like AS::Notifications, or if its an event bus like Wisper. My use-case was logging and shipping timing metrics to influxdb which I want to happen even if my server dies. |
I was fooling around with dry-system and enabled monitoring. I did a few tests with a UserRepo and actually expected the exception to be caught and the subscriber called, with an I did learn about rescuing exceptions within blocks, so thank you @paul for that new bit of knowledge! @solnic I believe you meant to highlight this block of code? https://github.com/karafka/karafka/blob/c68e0b0462d016ea04e92e88e728d66b5aef7f42/lib/karafka/params/params.rb#L68-L76 Karafka's master changed since the time you created you link and I was confused for a bit because the lines that are highlighted today don't even have any exception handling code. |
oh boy, I completely lost track on this one, sorry about that! I'll be working on dry-monitor 1.0.0 for hanami 2.0.0 release, so this will be a good moment for me to revisit this. We'll for sure take this library to the next level, so feedback like the one here is really appreciated. For now I'm assigning this PR to
@francois I don't even remember now, but most likely, yes. |
f5c2596
to
d2bab16
Compare
Without this, when I ran `rspec` after cloning the project, I got the following error: ``` An error occurred while loading spec_helper. Failure/Error: SPEC_ROOT = Pathname(__dir__) NoMethodError: undefined method `Pathname' for main:Object # ./spec/spec_helper.rb:22:in `<top (required)>' ```
When instrumenting a block of code, it is sometimes useful for that block to add more things to the payload. A common example is when performing an http request, it is useful to include the response status code in the instrumentation. ``` instrument("example.request", url: url, body: body) do |payload| response = http.post(url, params: body) payload[:response] = response end ``` This change simply yields the payload to the block to enable this. NOTE: If the instrument method is called with no overriding payload, the payload yielded to the block is the default EMPTY_HASH that is frozen, which results in a FrozenError if the block attempts to add anything to the hash.
Occasionally, the instrumented code will raise an exception, and we would still want the subscribers to be notified. For example, if an HTTP client fails by raising an exception, a subscriber doing logging should still be able to log the failed request. ``` instrument("example.request") do client.post("http://url.that.fails.example/") end ``` This will call the subscriber with an event that contains the exception within the payload under a key `:exception`. This implementation also re-raises the original error, so the callstack is unchanged.
d2bab16
to
9e4ff69
Compare
I've added a couple features to Dry::Monitor that I missed from AS::Notifications 🙊. Details about each are in the commit messages.
Yield the payload to the instrumented block
Allows the instrumented code to add more data to the
payload
, useful when needing instrument something involving a response:Trigger subscribers even when the instrumented code raises an exception
If the instrumented code block raises an exception, adds that exception to the
payload
and re-raises (so the backtrace remains the same). This allows subscribers to log the exception as well.I wanted to add docs for these features, but it seems this gem doesn't have any documentation? Also, it seems like this repo needs the "devtools update" that other repos have gotten, since codeclimate won't build it.