Diving into Custom Exceptions in Ruby
Tags: ruby
Published: 2023-04-05 14:15:42

Customizing exceptions is usually not a common concern during software development. But if you catch an error in an observability tool and can't correctly and quickly identify the problem, you may need more information and details about an exception.

This article will show you how to customize exceptions in Ruby and mitigate potential future problems due to a lack of error information.

Let's dive straight in!

## A Quick Side-Note

This post is a natural progression to [Debugging in Ruby with AppSignal](https://blog.appsignal.com/2022/09/21/debugging-in-ruby-with-appsignal.html), so we recommend reading that first for an overview of debugging and an introduction to custom exceptions.

## How to Rescue an Exception with Ruby

Ruby has a class called `Exception`, from which error-handling classes inherit. In this section, we'll better understand Ruby's structural exception flow before we create our custom exception.

`Exception` is the main exception class in Ruby, and `rescue Exception` will catch all exceptions inherited from `Exception`. It isn't best practice to use this in our app, as it will catch all exceptions used internally by Ruby.

The easiest way to rescue an exception in Ruby is to create a `begin ... rescue` block of code like the example below:

```ruby
begin
  call_some_method()
rescue Exception => e
  ...
end
```

Since `Exception` will catch all exceptions, we should try to use something more specific to filter and get only the error we want. One example is the `StandardError` class, a standard rescuer used to catch exceptions related to application code errors by ignoring Ruby's internal exceptions.

To catch a `StandardException`, you can use the example below:

```ruby
begin
  call_some_method()
rescue StandardException => e
  ...
end
```

Or, if no exception class is stated, Ruby will return the exceptions handled by the `StandardException` class.

```ruby
begin
  call_some_method()
rescue => e
  ...
end
```

To create a custom exception with Ruby, you'll probably create a class that inherits from `StandardError`. We'll cover that in the next few sections.

To learn more about exceptions and find the best one to inherit, check [Ruby's exceptions list](https://ruby-doc.org/core-2.5.0/Exception.html).

## Adding Additional Information to the Ruby Exception

Creating a new exception without sending relevant information will not effectively help in debugging — identifying and fixing — the problem. You need to add the error details. Here, we will see how to pass information from objects to the custom exception.

Let's create a new custom exception just to receive the data we want to log. As mentioned in the previous section, the custom exception needs to inherit from `StandardError`.

You can use a simple Rails project to test this implementation. Create a new folder called `exceptions` inside the `app` folder and a class called `custom_exception.rb` in your Rails project.

```ruby
# app/exceptions/custom_exception.rb

class CustomException < StandardError
  def initialize(code, name, msg, details)
    @code = code
    @name = name
    @msg = msg
    @details = details
  end
end
```

In this exception class, we can define all the information that needs to be shown in the logs and we use four attributes for our data:

- a code to enumerate the exception
- a name
- an informational message
- internal information details about the exception already provided by Ruby

And now we create a `begin...rescue` block to raise the `CustomException` and pass the parameters to the exception attributes.

```ruby
# app/models/employee.rb

class Employee
  def calculate_fees
    begin
      nil.object # Any code that throws an exception.
    rescue => exception
      raise CustomException.new(
        333,
        "My customized exception",
        "A new exception occurred in the application",
        exception)
    end
  end
end
```

This looks useful, so let's continue the implementation and use this data to customize the exception log.

## Extending an Exception in Ruby

Extending an exception allows us to elevate customization to a more specific and reusable level. Just as Ruby has several exceptions for different types of errors, we can also follow this line of programming in our systems.

Next, let's understand how to override our exception and use it in a context that demands an extension.

[Finding information in logs](https://www.appsignal.com/tour/log-management) is a painful activity. Developers often blame themselves for not including more information about errors or how to search and filter. If you are not using any [monitoring tools that provide this](https://www.appsignal.com/ruby), including meaningful data could save you in the foreseeable future.

**Pro-tip**: _Check out the section on 'Debugging Exceptions in Ruby with AppSignal' in our post [Debugging in Ruby with AppSignal](https://blog.appsignal.com/2022/09/21/debugging-in-ruby-with-appsignal.html) for information about how to set up and track custom exceptions in AppSignal._

In the example below, we will customize the display of exceptions in the log. Initially, you might think that it's just a light and pretty way to show data. However, the keywords used in the data are key to finding an error and helping whoever will be monitoring the application.

Add the [colorize](https://github.com/fazibear/colorize) gem to your project by including the code below in your Gemfile.

```ruby
# Gemfile

gem 'colorize'
```

Run the bundle to install the colorize gem and launch your Rails application.

```bash
$ bundle install
$ rails s
```

Now, in your exception class, import the gem. Print the attributes in a formatted way, so it's easy to see and quickly find an important error.

```ruby
# app/exceptions/custom_exception.rb

require 'colorize'

class CustomException < StandardError
  def initialize(code, name, msg, details)
    ...
    log_exception
  end

  def log_exception
    print_separator

    STDERR.puts "🚨🚨🚨 #{self.class} 🚨🚨🚨".colorize(:red)

    self.instance_variables.each do |attr|
      # All attributes will be printed.
      STDERR.puts "#{attr}: #{self.instance_variable_get(attr)}".colorize(:red)
    end

    print_separator
  end

  def print_separator
    85.times { print "-".colorize(color: :black, background: :red) }
    STDERR.puts "\n"
  end

  def to_s
    @name.colorize(:red)
  end
end
```

Check out the result of this visual customization and test some options to create your format and style.

![Custom Exception](https://blog.appsignal.com/images/blog/2023-03/custom-exception.png)

## Wrapping Up and Next Steps

In this post, we covered how to customize and highlight a Ruby exception in your logs. Many other customizations are possible, but what's most important is that you handle the exceptions relevant to your application. And remember, don't apply the same formatting style to all exceptions, as Rails already does.

Happy coding!

**P.S. If you'd like to read Ruby Magic posts as soon as they get off the press, [subscribe to our Ruby Magic newsletter and never miss a single post](https://blog.appsignal.com/ruby-magic)!**