By Pavel Sheida
Last Validated on June 17 2021 · Originally Published on June 17, 2021 · Viewed 4.5k times

Introduction

Logging is an important part of every application life cycle. Having a good logging system becomes a key feature that helps developers, sysadmins, and support teams to understand and solve appearing problems.

Every log message has an associated log level. The log level helps you understand the severity and urgency of the message. Usually, each log level has an assigned integer representing the severity of the message.

As for Ruby, it has a powerful built-in tool and plenty of great third-party packages.

In the tutorial you are will learn how to :

  • Create a logger with JSON-structured output using the built-in Ruby tools .
  • Create and configure a complex logging system using the logging package.

Prerequisites

You will need:

  • Ruby installed
  • Ubuntu installed

Option 1 — Logging Using Built-in Tools

Ruby provides a powerful built-in option for logging — the Logger class. It is a sophisticated logging utility available from the box. The Ruby's default logger supports different logging targets, logs formatting, and structured output.


Step 1 — Creating a Logger

The logger creation is a pretty-straightforward. You need to import the logger module and create an instance of the Logger class.

The first argument that Logger.new method accepts is the logging device. The logging device could be a filename or an IO object, such as STDOUT and STDERR.

For the example, we are going to create a logger in the main.rb file.

main.rb
# import the logger module
require 'logger'

# create a logger instance with logs.log file as a logging target
logger = Logger.new('logs.log')

The additional information and all overloads of the Logger.new method are available in the documentation.


Step 2 — Configuring the Logger

Ruby default logger has rich configuration ability. The basic configuration is done during the creation, but you also can change the configuration in the runtime.

For our application, we are going to set the minimum logging level to INFO and change the date-time format to the American one from the default %Y-%m-%d %H:%M:%S.

main.rb
# import the logger module
require 'logger'

# create a logger instance with logs.log file as a logging target
logger = Logger.new('logs.log')

# set the minimum logging level to INFO
logger.level = Logger::INFO

# create a string variable with the American date-time format
american_datetime_format = '%m/%d/%y %H:%M:%S'
# change the date-time format to the American one
logger.datetime_format = american_datetime_format


Step 3 — Adding a JSON Logs Formatter

Sometimes, you may need to fully modify the format of your logs. For a such task, Ruby provides the Formatter class. Using the class, you can overwrite the logger's message template.

We want to write our logs in the JSON format. It allows them to be treated as data sets rather than text.

Fortunately, the Formatter class is fully compatible with the default JSON module.

main.rb
# import the logger module
require 'logger'
# import the JSON module
require "json"

# create a logger instance with logs.log file as a logging target
logger = Logger.new('logs.log')

# set the minimum logging level to INFO
logger.level = Logger::INFO

# create a string variable with the American date-time format
american_datetime_format = '%m/%d/%y %H:%M:%S'
# change the date-time format to the American one
logger.datetime_format = american_datetime_format

# set new logs formatter
logger.formatter = proc do |severity, datetime, progname, msg|
    # specify date format
    date_format = datetime.strftime(american_datetime_format)
    # dump the log event to JSON
    JSON.dump(date: "#{date_format}", severity:"#{severity}", message: msg) + "\\n"
end

The advanced information about the Formatter class is available in the documentation.


Step 4 — Logging

After the logger is configured, you are almost ready to start logging.

The only thing you need to know before the logging is the Logger's log levels system. It consists of the 6 levels:

  • Unknown — used for reporting about events with unknown severity level.
  • Fatal — used for reporting about errors that are forcing shutdown of the application.
  • Error — used for logging serious problems occurred during execution of the program.
  • Warn  — used for reporting non-critical unusual behaviour.
  • Info — used for informative messages highlighting the progress of the application for sysadmin and end user.
  • Debug — used for debugging messages with extended information about application processing.

To demonstrate how the logger works, we will log 6 messages, one for each log level. According to the logger configuration, the minimum log level is info, so the debug logs must be omitted in the logs.log file.

The following code should be written in the main.rb file:

main.rb
# import the logger module
require 'logger'
# import the JSON module
require "json"

# create a logger instance with logs.log file as a logging target
logger = Logger.new('logs.log')

# set the minimum logging level to INFO
logger.level = Logger::INFO

# create a string variable with the American date-time format
american_datetime_format = '%m/%d/%y %H:%M:%S'
# change the date-time format to the American one
logger.datetime_format = american_datetime_format

# set new logs formatter
logger.formatter = proc do |severity, datetime, progname, msg|
    # specify date format
    date_format = datetime.strftime(american_datetime_format)
    # dump the log event to JSON
    JSON.dump(date: "#{date_format}", severity:"#{severity}", message: msg) + "\\n"
end

# logging
logger.debug('debug log message')
logger.info('info log message')
logger.warn('warn log message')
logger.error('error log message')
logger.fatal('fatal log message')
logger.unknown('unknown log message')

Step 5 — Testing

Now, it's time to test our code! Let's run the program via the following bash command:

$ ruby main.rb

After the program execution, you can display the contents of the logs.log file.

The simplest way to do it is cat. The command prints the contents of the specified file to the console.

$ cat logs.log

The output should be similar to that:

Output
{"date":"05/25/21 04:45:56","severity":"INFO","message":"info log message"}
{"date":"05/25/21 04:45:56","severity":"WARN","message":"warn log message"}
{"date":"05/25/21 04:45:56","severity":"ERROR","message":"error log message"}
{"date":"05/25/21 04:45:56","severity":"FATAL","message":"fatal log message"}
{"date":"05/25/21 04:45:56","severity":"ANY","message":"unknown log message"}

Option 2 — Logging With Logging Library

The logging library is a long-lived project based on the design of Java's log4j library. It is a flexible and easy-to-use library with many powerful features, great documentation, and big developer community.

In the long list of the logging library features, you can find:

  • Hierarchical logging system
  • Custom log levels
  • Multiple logging targets per each events
  • Custom formatting

Step 1 — Installing Dependencies

The logging package and all its dependencies can be installed with the default Ruby package manager, RubyGems.

We can install it with the following command.

$ gem install logging

Step 2 — Creating a Logger

The logger creation is a pretty-straightforward. You need to import the logging module and call the Logging.logger method.

For the example, we are going to create a logger in the main.rb file.

main.rb
# import the logging module
require 'logging'

# create a logger, named "Main logger"
log = Logging.logger['Main logger']

The additional information is available in the documentation.


Step 3 — Configuring the Logger

The logger configuration consists of several steps, such as configuring appenders, restricting log levels, and applying the configuration to a logger.

The first thing we are going to do is to restrict low-severity logs to be displayed in the console.

main.rb
# import the logging module
require 'logging'

# show only "info" and higher severity messages on STDOUT
Logging.appenders.stdout(:level => :info)

# create a logger, named "Main logger"
log = Logging.logger['Main logger']

The next thing we are going to do is to configure the appenders. For the application, we will add a logs.json file appender.

main.rb
# import the logging module
require 'logging'

# only show "info" or higher messages on STDOUT using the Basic layout
Logging.appenders.stdout(:level => :info)

# send all log events to the development log (including debug) as JSON
Logging.appenders.file(
    # filename
    'logs.json',
    # JSON formatting
    :layout => Logging.layouts.json
)

# create a logger, named "Main logger"
log = Logging.logger['Main logger']

# add appenders to the logger
log.add_appenders 'stdout', 'logs.json'
# set the default minimum log level
log.level = :debug

Step 4 — Logging

After we have configured the logger we can start logging.

The only thing you need to know before the logging is the log levels system. It consists of the 5 levels:

  • Fatal — used for reporting about errors that are forcing shutdown of the application.
  • Error — used for logging serious problems occurred during execution of the program.
  • Warn  — used for reporting non-critical unusual behavior.
  • Info — used for informative messages highlighting the progress of the application for sysadmin and end user.
  • Debug — used for debugging messages with extended information about application processing.

To demonstrate how the logger works, we will log 5 messages, one for each log level. According to the logger configuration, only info and higher severity log will be displayed in the console, while all logs should be available in the logs.json file.

The following code should be written in the main.rb file:

main.rb
# import the logging module
require 'logging'

# only show "info" or higher messages on STDOUT using the Basic layout
Logging.appenders.stdout(:level => :info)

# send all log events to the development log (including debug) as JSON
Logging.appenders.file(
    # filename
    'logs.json',
    # JSON formatting
    :layout => Logging.layouts.json
)

# create a logger, named "Main logger"
log = Logging.logger['Main logger']

# add appenders to the logger
log.add_appenders 'stdout', 'logs.json'
# set the default minimum log level
log.level = :debug

# logging
log.debug "A very nice little debug message."
log.info "A normal informative message."
log.warn "A warning about something unusual happened."
log.error "An error occurred!"
log.fatal "A fatal error!"

Step 4 — Testing

Now, it's time to test our code! Let's run the program via the following bash command:

$ ruby main.rb

After the program execution, your console's output should look like this:

Output
 INFO  Main logger : A normal informative message.
 WARN  Main logger : A warning about something unusual happened.
ERROR  Main logger : An error occurred!
FATAL  Main logger : A fatal error!

Now, let's display the contents of the logs.json file.

The simplest way to do it is cat. The command prints the contents of the specified file to the console.

$ cat logs.json

The output should be similar to that:

Output
{"timestamp":"2021-06-09T11:42:54.745101+02:00","level":"DEBUG","logger":"Main logger","message":"A very nice little debug message."}
{"timestamp":"2021-06-09T11:42:54.753815+02:00","level":"INFO","logger":"Main logger","message":"A normal informative message."}
{"timestamp":"2021-06-09T11:42:54.753912+02:00","level":"WARN","logger":"Main logger","message":"A warning about something unusual happened."}
{"timestamp":"2021-06-09T11:42:54.753960+02:00","level":"ERROR","logger":"Main logger","message":"An error occurred!"}
{"timestamp":"2021-06-09T11:42:54.754003+02:00","level":"FATAL","logger":"Main logger","message":"A fatal error!"}

Conclusion

Proper logging can greatly assist in the support and development of your application. This may seem like a daunting task, but Ruby has fast and configurable built-in tools.

In the tutorial, you've created a Ruby application with a complex logging system with the built-in tools and a third-party gem package, the logging library.

If you'd like to continue learning about logging in Ruby, you may be interested in other third-party logging libraries. You can find information about the most popular ones on The Ruby Toolbox pages.

Now developing and maintaining your Ruby applications will be much easier!