Rails standardized error reporting interface#43625
Conversation
|
These error reporting services usually allow for different error levels (similar to log levels). Is there a plan to handle this as part of this API as well? This is mainly relevant when using |
Yeah, I mentioned it in my original issue, but since then I'm a bit on the fence. Some do have this and it's an useful feature, but then they don't all have the same list of levels etc. So I figure that would be something that would be passed as part of the context, e.g.: Rails.error.report(error, handled: true, context: { severity: "warning" }) |
Ah, got it. But would require the error reporting services to "agree" on a similar API for severity on their own, otherwise moving between services would still be difficult. Thinking about it, maybe it would make sense after all to add |
de556d6 to
5affffc
Compare
Yes, but even if we had a
Note that ease of migration isn't one of my stated goals. Pretty much each service I looked at had one or several unique feature. My goal isn't to abstract all provider, but to define an (extensible) common denominator, so that Rails and Rails aware libraries can report error too. Now maybe it is important for Rails and Rails aware libraries to be able to set a severity level, like when they log, hence why I'm a bit on the fence. I'll think more about it. |
Totally, I get this. I just had the feeling that severity is still part of that common denominator, but that's a judgement call for sure.
My use case for this is gems. What we are currently doing in gems where we want to report something is use |
|
Yeah make sense. I think I'll add a severity parameter, but I think I'll have to restrict it to a set of specific values. Probably |
6f1fb18 to
8741a27
Compare
There was a problem hiding this comment.
if one subscriber raises an error during reporting, will it interrupts the reporters after it?
There was a problem hiding this comment.
I think the subscriber.report call should itself be wrapped in a rescue that falls back to the logs if the subscriber throws an error. The subscriber shouldn't, but we can't rely on them not to.
There was a problem hiding this comment.
Yeah, subscribers are expected to never raise, but we definitely shouldn't blindly trust them.
There was a problem hiding this comment.
Hum, so the difficulty here is to have access to a logger. I'll have to dig more into this.
8741a27 to
728986b
Compare
There was a problem hiding this comment.
If we're in dev mode, we might as well let the subscriber error raise.
There was a problem hiding this comment.
I chose fatal level. Normally it's for errors that do crash the program which is not the case here, but I feel it's critical enough to warrant it anyway.
Fix: rails#43472 The reporter is held by the executor, but the `Rails` module provides a nicer `Rails.error` shortcut. For ease of use, two block based specialized methods are exposed. `handle`, which swallow errors and forward them to the subscribers: ```ruby Rails.error.handle do 1 + '1' # raises TypeError end 1 + 1 # This will be executed ``` `record`, which forward the errors to the subscribes but let it continue rewinding the call stack: ```ruby Rails.error.record do 1 + '1' # raises TypeError end 1 + 1 # This won't be executed. ``` For cases where the blocked based API isn't suitable, the lower level `report` method can be used: ```ruby Rails.error.report(error, handled: true / false) ```
728986b to
8cbc19d
Compare
tenderlove
left a comment
There was a problem hiding this comment.
This looks good. I guess my only concern is that we have to find every place inside Rails where we want to report exceptions but we swallow them. I think that's probably fine though?
This might be a bad idea, but have we thought about using TracePoint for intercepting exceptions? Then we don't need to wrap anything. I have no idea what the perf implications would be though.
Yes it's fine, there isn't that many of them, and even if we miss one of two, it's not the end of the world.
That would trigger on a lot of exceptions that are perfectly normal and not worth reporting. Also Tracepoint is global, not per thread / fiber. |
|
Something I noticed today is that Active Job doesn't call But I doubt all queue systems out there do it. So I'm tempted to add it in Active Job itself just to be safe. Worst case |
Hmm, isn't that what rails/activejob/lib/active_job/railtie.rb Line 53 in 3fcadfb |
|
Ah good catch. We're good then. |
|
Ok, let's do this. |
|
is @byroot doing reviews of @casperisfine pull requests? ;-) |
|
One is my professional account, the other my personal. Only one has commit rights. |
|
@st0012, no worries, thanks a lot for this feedback, I'll read through these issues. |
|
Hey, coming from getsentry/sentry-ruby#1680. Not even sure if this is the right place to put this, anyway: I wonder if it would be considered valuable by the rest of you, if there'd be a thing like |
|
I think it would make sense yes, since |
|
@casperisfine Just had this idea: can Rails give hints to the subscriber about where the reporting came from? Like in this part: rails/actionpack/lib/action_dispatch/middleware/executor.rb Lines 16 to 19 in cfa7284 In can be @executor.error_reporter.report(error, handled: false, framework: true)
# or if there want to standardize this source information, we can have
@executor.error_reporter.report(error, handled: false, source: :framework)
# other sources could be
@executor.error_reporter.report(error, handled: false, source: :app) # this will be the default
@executor.error_reporter.report(error, handled: false, source: :library) # for adapters like MemCacheStoreThen, subscribers can use that information to determine if that's something it wants to report. In Sentry's case, we'll be able to opt-in the subscriber by default but skip non-app sources. |
It's a possibility, and could be useful, but I don't think that's the proper solution to your user problem. If I understood correctly, what happened to them is that that one single exception source made them reach their quota very fast. This could have happened with any other regular error in their app, I don't think that justify hiding a specific error. With our custom error reporter, what we do to avoid this is that we throttle error sources, e.g. if we see the same error more than X time in Y minutes, we stop reporting it for Z minutes. That's a feature many error reporters have, and that would make sense to implement in Rails. And for users that really don't care about the CacheStore errors, I believe that the better solution was suggested in the upstream thread, we should report it from the default |
I think the core problem is that the I want to find a way to opt-out this error and similar future cases. Using I think this case's characteristics are:
Maybe a find-grained throttling strategy can work, but let me explain it in the next section:
I think this could be a nice feature but I don't want it to be default or univeral. This is because we already implemented a rate-limiting mechanism between SDK and the server. In addition to that, we also collect the rate-limited stats (basically a But if we can throttle
Sorry I may missed it, which comment are you referring to? To reiterate: as a SDK maintainer, I wish Rails can:
BTW, I think it'd be great if the error reporter can also capture errors from the |
Well, it was one goal of that standardized API to allow framework and library code to report handled internal errors. Otherwise the app owner might not realize the application is performing in degraded mode. Now of course so users may already have dedicated monitoring for redis etc, so there might be a need to customize what is reported or not.
This one: getsentry/sentry-ruby#1765 specifically:
Which I agree with.
Yes, 💯 , that's something I had in mind. Also as mentioned IRL, I think we could pass some kind of |
Yes that'd be great. The SDK will provide some default and allow users to customize them too. But what about the errors captured by the execution wrapper? Always seeing |
Ref: rails#43625 (comment) Some users may not be interested by some internal errors. By providing a `source` attribute we allow to easilly filter these errors out.
* Since upgrading to Honeybadger 5.0 we are getting duplicate errors reported for Sidekiq::JobRetry::Skip and another reported error for the exception that caused the Sidekiq job to be retried. * There was a pr merged to Sentry for a similar issue and Mike Perham said this exception could be ignored. getsentry/sentry-ruby#1763 rails/rails#43625 (comment)
* Since upgrading to Honeybadger 5.0 we are getting duplicate errors reported for Sidekiq::JobRetry::Skip and another reported error for the exception that caused the Sidekiq job to be retried. * There was a pr merged to Sentry for a similar issue and Mike Perham said this exception could be ignored. getsentry/sentry-ruby#1763 rails/rails#43625 (comment)
`Sidekiq::JobRetry::Skip` is used for retry control and isn't really a resolvable error to track As per [recommendation from the Sidekiq maintainer](rails/rails#43625 (comment))
Fix: #43472
The reporter is held by the executor, but the
Railsmodule provides anicer
Rails.errorshortcut.For ease of use, two block based specialized methods are exposed.
handle, which swallow errors and forward them to the subscribers:record, which forward the errors to the subscribes but let itcontinue rewinding the call stack:
For cases where the blocked based API isn't suitable, the lower level
reportmethod can be used:Interface
In this PR I only introduce
Rails.error. However in followups I'd like to introduce "local" error reporters so that gems like Active Support / Active Record can report errors as well. It would work a bit likeRails.loggertoday.e.g.
ActiveSupport.errorwould default to an error reporter that just logs the error, but in the Railtie we'd replace it withRails.error.