Guide | Ruby

Where are Ruby Errors Logged?

Where are Ruby Errors Logged?

Where Are Ruby Errors Logged?

So, when we encounter errors in our code, where exactly can we find them? At a high level, there are two common places that Ruby errors can be found: inline with program execution, and in a designated log file.

The Logger Class

Before we look at where Ruby errors are logged, let's first take a look at how those errors are logged. The Logger class is a utility class that can be used to write messages to a specified output path. While this may sound relatively simplistic, it is actually a very sophisticated implementation for a built-in logging tool.

Log Files vs. Inline Errors

When instantiating the Logger class, you must define a destination to output all logs to. Output could be one of two types: a standard stream output like STDOUT and STDERR, or a file output like /var/log/ruby.log. The standard output stream could also be considered inline errors, as they are output directly to the command line during program execution.

To instantiate a Logger that writes to STDOUT, for example, all we need to do is pass STDOUT as the first parameter to the initializer:

logger = Logger.new(STDOUT)

While this is incredibly valuable for live debugging, a more long-term logging solution is often more desirable. Thankfully, the Logger class initializer also accepts a file path, allowing us to log data directly to a log file:

logger = Logger.new("/var/log/ruby.log")

Log Levels

Once we select a destination for our log data, how do we actually write to it? It is important to note that, in Ruby, there are a handful of log levels that can be squashed or raised. While these log levels are determined by Ruby itself, understanding what they are and what they mean is a crucial step towards being able to diagnose problems as they happen. Before we can write to the logs, though, we need to understand what log levels are. In Ruby, there are five common log levels: FATAL, ERROR, WARN, INFO, and DEBUG. The meaning of each of these log levels is provided as follows:

FATAL
An unhandleable error that results in a program crash.

ERROR
A handleable error condition.

WARN
A warning.

INFO
Generic (useful) information about system operation.

DEBUG
Low-level information for developers.

With that in mind, writing to the log can be accomplished using one of five instance methods, named after their respective log levels:

logger.debug("Debugging some stuff")
logger.info("Info, info, info")
logger.warn("This is a warning")
logger.error("Err-or :(")
logger.fatal("OH NO!")

Take note that, while each of these log levels are written to the log by default, the current log level can be changed to only write messages to the log that are at a specified level or worse. This is valuable in that more data can be logged in development environments, while less can be logged in production. To accomplish this, simply update the level variable of the logger instance to the desired log level:

logger.level = Logger::WARN

Log Formats

So now you might be thinking, "What do the logs actually look like when written to a log file or STDOUT?" By default, Logger writes log messages in the following format:

SeverityID, [DateTime #pid] SeverityLabel -- ProgName: message

In a live context, that will look something like this:

I, [1999-03-03T02:34:24.895701 #19074]  INFO -- Main: info

But, if you prefer, you can customize the log format to your own liking by updating the datetime_format variable in your logger instance. For example, if you want to write log messages with only the date, you would define that the following way:

logger.datetime_format = '%Y-%m-%d'

With that changed, the previous log line example would look like this instead:

1999-03-03 info

Logging in Ruby on Rails

While lightweight frameworks like Sinatra often do not have its own logging mechanisms in place, Ruby on Rails does. It is important to note that, while the Ruby on Rails Logger is an extension of the built-in Ruby Logger class, it adds support for broadcasting logs to multiple loggers. This can be valuable for storing logs in multiple places, or logging different log levels in different ways. To accomplish this, simply extend the Rails logger property and broadcast the logs into a separate Logger instance:

logger = Logger.new(STDOUT)
Rails.logger.extend(ActiveSupport::Logger.broadcast(logger))