Logging in Ruby with Logger and Lograge
Tags: ruby
Published: 2021-04-06 22:38:48

*This article was originally written by [Diogo Souza][1] on the [Honeybadger Developer Blog][2].* 

# Working with Logs in Ruby

Logging is one of the primary tasks an application usually addresses. Logs are used when you need to, for example,

- see what’s happening inside of your apps,
- monitor them, or
- collect metrics for some specific data.

When learning a new programming language, the first obvious pick to log information is the native mechanism. It is usually easy, documented, and well-spread throughout the community.

Log data varies a lot depending on the company, business, and type of application you’re working with. Therefore, it’s very important to understand how the logging solution you and your team chose will impact its overall use.

In this article, we’ll take a ride through the available options of logging for Ruby and Rails, from the built-in ones to the beloved third-party community frameworks. Let’s go!

## Ruby's Built-In Options

Ruby comes with two built-in options to handle logging: printing commands (specially designed for command-line scenarios) and the [Logger](https://ruby-doc.org/stdlib-2.7.0/libdoc/logger/rdoc/Logger.html) class.

Let’s explore them a little bit.

## Printing to the Console

Ruby has four common ways to print text to the console: `puts`, `print`, `p`, and `pp`.

The `puts` method prints anything you pass to it, followed by a new line:

```shell
2.7.0 :001 > puts "Hey, I'm a log!"
Hey, I'm a log!
 => nil
```

The `print` method is similar to `puts`, however it always converts arguments to strings using the `to_s` method.

Finally, the `p` and `pp` methods will both print the raw object you’re passing to them without performing conversions; the only difference is that the latter formats the output in a more indented way, while the former does not.

## Application Logs

For applications running on servers, it doesn't make sense to print to console.

Instead, we use the `Logger` class, which is much more flexible.

Here's how you might use it to print a "debug" log:

```ruby
require "logger"
logger = Logger.new(STDOUT)
logger.debug("I'm a debug log")
```

We could also configure it to store the logs to a file, or send them to a log aggregator instead of printing to STDOUT if we wanted to.

In case you're wondering, "debug" is a log level. Log levels let you tell the system "this log message is related to a certain kind of event." There are six built-in log levels: Fatal, Error, Warn, Info, Debug and Unknown.

```ruby
logger.debug("I'm a debug log")
logger.info("I'm an info log")
logger.warn("I'm a warn log")
logger.error("I'm an error log: error message")
logger.fatal("I'm a fatal log")
```

When we look at the log output, we see that the log level, process id and timestamp have been appended to each line:

![showing logs with Logger](https://www.honeybadger.io/images/blog/posts/ruby-logger-lograge/Figure01.png)

Another great feature is the ability to set your `Logger` object at a specific level:

```ruby
logger.level = Logger::WARN
```

When you do this, `Logger` will only handle the logs equal or higher to _warn_ in importance. This is very useful in production, where we may not want to save "debug" logs due to size or security reasons.

This should be the output:

![Specifying levels](https://www.honeybadger.io/images/blog/posts/ruby-logger-lograge/Figure02.png)

## Customizing Your Logs

If you want to customize your logs, there’s plenty of options. To change the date and time, for example, just overwrite the [default one](https://ruby-doc.org/stdlib-2.7.0/libdoc/logger/rdoc/Logger.html#method-i-datetime_format-3D):

```ruby
logger.datetime_format = "%Y-%m-%d %H:%M"
```

Here’s an example of the output:

```shell
W, [2020-07-28 10:50#87786]  WARN -- : I'm a warn log
```

The `date_time` format follows the [standard specification](https://man7.org/linux/man-pages/man3/strftime.3.html), so be sure to refer to it when performing such changes.

Sometimes, however, you may need to fully modify the format of your logs. For this purpose, Ruby provides the [Formatter](https://ruby-doc.org/stdlib-2.7.0/libdoc/logger/rdoc/Logger/Formatter.html) class. It works by allowing devs to overwrite the default template that `Logger` uses to print logs.

Imagine that your app makes use of a centralized logs analysis tool and needs all of your logs to be printed in the `name=value` format. This would be the new formatter code:

```ruby
logger.formatter = proc do |severity, datetime, progname, msg|
    date_format = datetime.strftime("%Y-%m-%d %H:%M:%S")
    "date=[#{date_format}] severity=#{severity.ljust(5)} pid=##{Process.pid} message='#{msg}'\n"
end
```

Here, we’re making use of a `proc` to intercept all the logs and change the default way they're printed to the desired one. Now, we have the following result:

![Saving logs to a file](https://www.honeybadger.io/images/blog/posts/ruby-logger-lograge/Figure03.png)

With just a few lines of code, all of your app logs can now be indexed by the tool.

## Logging to JSON

With the power of `Formatter` in hand, it’s easy to customize your logs to whatever output type you want. For example, we could use the [JSON module](https://ruby-doc.org/stdlib-2.7.0/libdoc/json/rdoc/JSON.html) to output our logs in that format.

```ruby
require "json"
logger.formatter = proc do |severity, datetime, progname, msg|
    date_format = datetime.strftime("%Y-%m-%d %H:%M:%S")
    JSON.dump(date: "#{date_format}", severity:"#{severity.ljust(5)}", pid:"##{Process.pid}", message: msg) + "\n"
end
```

And, here are the resulting logs:

![Logging to JSON](https://www.honeybadger.io/images/blog/posts/ruby-logger-lograge/Figure04.png)

## Logging to a File

As seen before, the `Logger` constructor receives, as the first argument, the place where the log should be stored.

If you want to save your logs to a file, just say so:

```ruby
require 'logger'
logger = Logger.new('my_logs.log')
```

This will create a new file _my_logs.log_ in the same folder where your Ruby file containing this code is located:

![Formatting date and time](https://www.honeybadger.io/images/blog/posts/ruby-logger-lograge/Figure05.png)

However, the class also allows a bunch of custom options, such as a retention policy config. Take the following example:

```ruby
# Keep data for the current week.
Logger.new('my_weekly_logs.log', 'weekly')
# Keep data for today and the past 2 months.
Logger.new('my_latest_2_months_logs.log', 2, 'monthly')
# Restarts the log over when it exceeds 26GB in size.
Logger.new('my_custom_logs.log', 0, 20 * 1024 * 1024 * 1024)
```

As a second param, `Logger` accepts a definition of how frequently it should keep the logs or erase them.

In the first object, the logs will be kept for one week only. The second will store them for a range of two months, while the latest will analyze just the size of the file (e.g., whenever it reaches 20GB, it’ll be discarded) based on the third param.

## Logging in Ruby on Rails

Most of the benefits of `Logger` we’ve seen so far can all be used along with Rails apps. The Rails version of `Logger` is just a simple extension of Ruby’s native class.

Apart from that, Rails also adds a nice feature to allow devs to broadcast their logs to multiple loggers. Thus, if you want to work with more than one logging library, you can do so within Rails `logger`:

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

This can also be useful when you need some intelligence regarding the places your logs should go. Some parts of your app may need logs in different locations or different formats.

## Choosing a Third-party Logging Framework

If Ruby's built-in logger doesn't do exactly what you need, you have options.

There are [plenty of options](https://www.ruby-toolbox.com/categories/Logging) when it comes to picking up a 3rd-party logging framework. The most popular of these is [Lograge](https://github.com/roidrage/lograge). Let's take a look at it!

## First Steps with Lograge

Lograge was made for Rails apps. If you’re working with vanilla Ruby, it may not be a good choice.

To install it, just add the following gem:

```shell
bundle add lograge
```

Then, create a new config file at _config/initializers/lograge.rb_ with the following code:

```ruby
Rails.application.configure do
    config.lograge.enabled = true
    config.lograge.custom_options = lambda do |event|
        { time: event.time }
    end
end
```

This config enables Lograge and defines the time that a log, considered an event to the framework, has to be printed.

When you start the Rails app and access the first endpoint, Lograge will print the request summary. Comparing the logs before and after Lograge setup, respectively, results in something like shown below:

![Before and after the Lograge](https://www.honeybadger.io/images/blog/posts/ruby-logger-lograge/Figure06.png)

Again, Lograge is not a silver bullet; it is actually a very opinionated framework. So, you’ll probably need to use it along with the built-in `Logger` (or other frameworks of your preference).

## Wrapping Up

In the end, it becomes clearer that logging is not only a crucial part of your projects but also very underestimated. To better understand the tools you’re using, regardless of whether they are native, will help you to achieve better results too.

Be sure to always conduct a lot of testing to adopting a new framework. Sometimes your platform already comes bundled with great built-in options. If that’s not the case, determine whether it allows the use of other frameworks integrated with the one that’s already inside.

Best of luck!
[1]: https://www.honeybadger.io/blog/ruby-logger-lograge/#authorDetails
[2]: https://www.honeybadger.io/blog/ruby-logger-lograge/