Skip to content

Commit

Permalink
Added ability to instrument when exceptions happen
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
paul committed Nov 30, 2019
1 parent 6247f36 commit f5c2596
Show file tree
Hide file tree
Showing 2 changed files with 29 additions and 1 deletion.
7 changes: 6 additions & 1 deletion lib/dry/monitor/notifications.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,12 @@ def stop(event_id, payload)

def instrument(event_id, payload = EMPTY_HASH)
result, time = @clock.measure { yield payload } if block_given?

# We should always try to instrument, even the system-level exceptions
rescue Exception => e # rubocop:disable Lint/RescueException
payload = {} if payload.equal?(EMPTY_HASH)
payload[:exception] = e
raise
ensure
process(event_id, payload) do |event, listener|
if time
listener.(event.payload(payload.merge(time: time)))
Expand Down
23 changes: 23 additions & 0 deletions spec/integration/instrumentation_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -63,5 +63,28 @@
outside_block: true, inside_block: true
)
end

MyError = Class.new(StandardError)
it 'still notifies when the instrumented block raises an exception' do
captured = []

notifications.subscribe(:sql) do |event|
captured << event
end

expect {
notifications.instrument(:sql) do
@line = __LINE__ + 1
raise MyError
end
}.to raise_error(MyError)

expect(captured).to_not be_empty
exception = captured.first.payload[:exception]
expect(exception).to match kind_of(MyError)
# verify the exception backtrace comes from the instrumented code, not
# the `#instrument` method
expect(exception.backtrace.first).to start_with("#{__FILE__}:#{@line}")
end
end
end

0 comments on commit f5c2596

Please sign in to comment.