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, 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:
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:
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.
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.
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.
# 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.
# 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 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, 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 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 gem to your project by including the code below in your Gemfile.
# Gemfile
gem 'colorize'
Run the bundle to install the colorize gem and launch your Rails application.
$ 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.
# 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.
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!
Top comments (0)