# safely: a Ruby block that swallows non-critical exceptions and reports them

> Safely wraps a block of code so exceptions inside it are rescued and forwarded to an error reporting service, with context, defaults, exception filters and throttling as options. In development and test the exceptions are raised instead, so a broken block fails loudly where you can fix it.

**ankane/safely** — Rescue and report exceptions in non-critical code

- Repository: https://github.com/ankane/safely
- Stars: 565 · Forks: 15
- Language: Ruby
- License: MIT
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/ankane-safely

## A block that decides what is allowed to fail

Safely is a Ruby gem with a very small surface, and the whole idea fits in one snippet:

```ruby
safely do
  # keep going if this code fails
end
```

Exceptions are rescued and automatically reported to your favourite reporting service, and in development and test environments the exceptions are raised instead so you can fix them.

That last part is what makes the gem usable rather than a way to hide bugs. The failure of a block is only ignored where the block is genuinely non-critical, and the environment check means the same code that swallows an error in production raises it in your test suite, where you want to see it.

The use cases the documentation gives are the ones where this pattern is genuinely right. Analytics brought down search, so you wrap the tracking call. Recommendations stopped updating because of one bad user, so you wrap the per-user update and pass the user id as context:

```ruby
users.each do |user|
  safely(context: {user_id: user.id}) { update_recommendations(user) }
end
```

Note that the context is attached per call inside the loop rather than once outside it, which is what makes the report actionable. A rescued exception with a user id attached is a bug report you can act on; a rescued exception with no context is a line in a log.

The block form is also aliased as yolo, for the code where the author is aware of the risk and is asserting that the failure is acceptable.

The gem installs as safely_block in the Gemfile, and the pattern is written up in more detail at ankane.org/safely-pattern.

## The option set, and what each one changes

The options are the substance of the gem, and each one answers a different question about what should happen when the block fails.

Context is extra data reported with the exception:

```ruby
safely context: {user_id: 123} do
  # code
end
```

A default value handles the return value when an exception is rescued, so a caller that expects a value still gets one:

```ruby
show_banner = safely(default: true) { show_banner_logic }
```

The except option raises specific exceptions rather than rescuing them, which is how you carve out the failures that really do matter:

```ruby
safely except: ActiveRecord::RecordNotUnique do
  # all other exceptions will be rescued
end
```

The only option is the mirror image, rescuing only the exceptions you list and letting everything else through:

```ruby
safely only: ActiveRecord::RecordNotUnique do
  # all other exceptions will be raised
end
```

The silence option rescues without reporting, which is the option to use for failures you have genuinely decided do not need to be seen.

And throttle bounds the reporting volume:

```ruby
safely throttle: {limit: 10, period: 1.minute} do
  # reports only first 10 exceptions each minute
end
```

The note attached to the throttle is the one to keep in mind. The throttle limit is approximate and per process. Approximate because the count is not synchronised across processes, and per process because each process keeps its own count, so a deployment with eight workers can report up to eight times the limit in a minute. That is a sensible design for a guard against a loop generating thousands of reports, and it is not a precise rate limiter.

One more option worth noting, because it changes the shape of the call site, is that only and except can be given an array for multiple exception classes.

## Reporting services, and how to swap the reporter

The gem reports to a long list of services out of the box, and the list is worth reading because it tells you which integrations are maintained rather than aspirational.

Airbrake, Appsignal, Bugsnag, Datadog, Exception Notification, Google Stackdriver, Honeybadger, New Relic, Raygun, Rollbar, Scout APM and Sentry are all supported.

There is one limitation that matters and it is called out explicitly: context is not supported with Google Stackdriver and Scout APM. So on those two services the extra data you attach, which is the most useful part of the report, is dropped. If you want the context, you pick a different service.

Swapping the reporter is a single assignment, which means you are not tied to whichever integration happens to be installed:

```ruby
Safely.report_exception_method = ->(e) { Rollbar.error(e) }
```

With Rails, you add this in an initializer, and that is the right place because it runs once at boot rather than per call.

Two defaults control how the reports read. By default, exception messages are prefixed with [safely], which makes it easier to spot rescued exceptions in a service where other errors are mixed in, and that is turned off with:

```ruby
Safely.tag = false
```

By default, exceptions are raised in the development and test environments, and you extend that list with:

```ruby
Safely.raise_envs += ["staging"]
```

That second setting is the one most worth changing in a real deployment, because a staging environment with real integrations and real data is where you find out whether a rescued exception was hiding a real problem. Reporting manually is also possible with Safely.report_exception(e), which is useful when you want to report something that was not raised inside a safely block.

The project is MIT licensed, with the licence in LICENSE.txt, and the top level of the repository is conventional Ruby: a Gemfile, a Rakefile, a safely_block.gemspec, lib/, test/, a gemfiles/ directory for testing against different dependency versions, and a CHANGELOG.md. The last push was on 2026-04-02 and there are no GitHub releases, so the changelog is the version history.

## Working on the gem itself, and where the pattern comes from

The pattern this gem implements is older than the gem, and understanding that shape explains why the options are what they are.

The idiom is the classic belt-and-braces rescue: run a piece of work, and if it raises, do not let the failure take down the caller. What the gem adds is that the rescue is not silent, that it carries context, and that it is disabled where you want the failure to be loud. The naming follows from that, since a block marked as safely is a block you have decided is safe to fail, and the yolo alias exists for when you are less sure and want the shorter word at the call site.

The repository structure reflects a small, focused gem rather than a framework. The lib/ directory holds the implementation, test/ holds the suite, the Rakefile is the task runner, and the gemfiles/ directory exists to test against different dependency versions, which is the standard way to check that a gem still works across a range of dependency resolutions. There is a CHANGELOG.md, a LICENSE.txt with the MIT terms, and the gemspec is named safely_block.gemspec even though the gem is called safely, so the file name reflects the require path.

The development loop is the ordinary Ruby one:

```sh
git clone https://github.com/ankane/safely.git
cd safely
bundle install
bundle exec rake test
```

The contributing section lists the ways in, and they are worth taking seriously if you want to add an integration. Report bugs, fix bugs and submit pull requests, write or clarify or fix documentation, and suggest or add new features. The documentation point is the one that matters most for a gem whose main job is turning a stack trace into a useful report, because a service integration that is not documented is an integration nobody will configure.

The release history lives in the changelog rather than in tags, and there are no GitHub releases, so the changelog is the place to look before upgrading to see whether a default changed.

## The data protection warning, which is the part to read carefully

There is a section on data protection, and it is the most consequential part of the documentation for anyone attaching context to a report.

The advice is that to protect the privacy of your users, you should not send personal data to exception services. The concrete guidance is to filter sensitive form fields, to use ids rather than email addresses to identify users, and to mask IP addresses. The final line is the one to carry with you: while working on exceptions, be on the lookout for personal data and correct as needed.

That is an unusually direct warning for a gem, and it makes sense in context. The whole point of the context option is to make reports actionable, and the easiest way to make a report actionable is to include the thing the exception was about, which is frequently a request, a form or a user record. The gem does not filter anything for you, so the discipline has to be the application's.

The Rollbar example shows what a service-level configuration of that discipline looks like:

```ruby
Rollbar.configure do |config|
  config.person_id_method = "id" # default
  config.scrub_fields |= [:birthday]
  config.anonymize_user_ip = true
end
```

Three things there. The person identifier method is set to the opaque id rather than the email address, which means the report points at a record without carrying the address. The scrub fields list is extended with a field the application considers sensitive, and the semantics of the operator is a set union, so a service-level default list and an application list combine rather than one replacing the other. And IP anonymisation is enabled at the service, so the network address never leaves.

The deeper point is that the reporting service is the last place to fix this. Once a personal datum is in the report it has been sent to a third party, and the receiving service has its own retention and access model. Filtering before the report leaves the process is the only version of this that is reliable, and that is why the section exists in a gem whose main feature is sending data away.

## Conclusion

safely suits a Rails application with side effects that should never take down a request, such as analytics calls, recommendation updates and notifications, because the block is explicit about what is allowed to fail and everything that fails is still reported rather than swallowed silently. It does not suit you for code where a failure means the request is wrong, since the point of the gem is to continue past it. Before you adopt it, set the environment list so staging behaves like production, decide what happens to personal data in the context you attach, and read the note that the throttle limit is approximate and per process, since a multi-process deployment will report more than the limit suggests.

## FAQ

### What does the safely gem do in Ruby?

It rescues exceptions raised inside a block, reports them to an error reporting service such as Sentry or Rollbar, and continues. In the development and test environments the exceptions are raised instead so you can fix them, and the gem is also aliased as yolo.

### How do I add context and a default value to a safely block?

Pass context with the block, for example safely(context: {user_id: user.id}) { update_recommendations(user) }, and pass default for the value returned when an exception is rescued, for example safely(default: true) { show_banner_logic }. The only and except options control which exception classes are rescued or raised.

### How do I limit how many exceptions safely reports?

Use the throttle option, for example safely throttle: {limit: 10, period: 1.minute}. The documentation notes that the throttle limit is approximate and per process, so a deployment with several worker processes can report more than the limit suggests.

### Which safely reporting services support extra context?

Context is not supported with Google Stackdriver and Scout APM. The reporter itself can be replaced with Safely.report_exception_method, and in Rails that assignment belongs in an initializer.

## Sources

- [ankane/safely on GitHub](https://github.com/ankane/safely)
- [Issues](https://github.com/ankane/safely/issues)
- [License: MIT](https://github.com/ankane/safely/blob/master/LICENSE)
- [README](https://github.com/ankane/safely/blob/master/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/ankane-safely
