In Progress
Unit 1, Lesson 1
In Progress

Colored Logs

Video transcript & code

Logging is the kind of thing we usually think about in conjunction with server-side programs, like web servers or batch jobs. But it can sometimes be helpful to have logs on user-facing, command-line programs as well.

When logging to an interactive console, we may want to think about how to make our output more attractive to the human who is using the program. We've already seen one example of this kind of log beautification: in episode #349, we saw how to simplify log formatting by removing most of the metadata that usually accompanies it.

require "logger"
logger = Logger.new($stdout)
logger.formatter = ->(severity, datetime, progname, message) {
  "#{severity}: #{message}\n"
}

logger.debug "Logging enabled"
logger.info "Ready for launch"
logger.warn "These readings look weird"
logger.error "Tang reservoir empty!"
logger.fatal "Aborting launch"

# !> DEBUG: Logging enabled
# !> INFO: Ready for launch
# !> WARN: These readings look weird
# !> ERROR: Tang reservoir empty!
# !> FATAL: Aborting launch

Today I thought we might take this a step further. Let's add some color to our logs!

The way we add color to the text in a console is to use certain ANSI-standardized escape codes, which act a bit like HTML markup for terminal emulators. If you are curious about the nitty-gritty of how to do this, there is plenty of information available online. But today we're not going to get our hands dirty with escape codes, because other programmers have written libraries that make colorizing output simple and easy.

There are several Ruby gems available for this purpose. We'll use one called "pastel". After installing it, we can require it and instantiate a Pastel object. Then we can send messages to the pastel object, instructing it to wrap the given strings in colorizing escape sequences.

If we just look at the resulting string, we can see that it now has some garbagy-looking characters around it. But if we output the string to the console, we can see the colorization at work.

irb(main):001:0> require "pastel"
=> true
irb(main):002:0> pastel = Pastel.new
=> #<Pastel @styles=[]>
irb(main):003:0> pastel.blue("hello")
=> "\e[34mhello\e[0m"
irb(main):004:0> puts pastel.blue("hello")
hello
=> nil

There are various colors predefined. We can also chain colors with modifiers, such as bold or underline.

irb(main):005:0> puts pastel.green("hello")
hello
=> nil
irb(main):007:0> puts pastel.yellow("hello")
hello
=> nil
irb(main):008:0> puts pastel.yellow.bold("hello")
hello
=> nil
irb(main):009:0> puts pastel.yellow.bold.underline("hello")
hello
=> nil

Or even add background colors.

irb(main):012:0> puts pastel.red.on_green("Merry Christmas!")
Merry Christmas!
=> nil

If we want, we can save a style for later. To do that, we chain on the message #detach. Then we can use the detached style later on by treating it as a callable object.

irb(main):013:0> cyan = pastel.cyan.detach
=> #<Pastel::Detached styles=[:cyan]>
irb(main):015:0> puts cyan.("Hello")
Hello
=> nil

The usefulness of this feature might not be immediately obvious, but it's actually key to how we're going to colorize log output.

Back in our logging code, let's require the pastel gem and create a new Pastel instance. Then we'll set up a table of colors. In the table, we map logger severity names to detached Pastel colorizer objects.

We'll use a bolded red for fatal, plain red for errors, yellow for warnings, green for info, and plain white for debug messages.

Then, inside the log formatter lambda, we choose a colorizer by indexing into the hash we built, using the log message severity as a key.

Finally, we use the colorizer to add ANSI formatting escapes to the severity prefix on each log message.

require "pastel"
require "logger"

logger = Logger.new($stdout)
pastel = Pastel.new
colors = {
  "FATAL" => pastel.red.bold.detach,
  "ERROR" => pastel.red.detach,
  "WARN"  => pastel.yellow.detach,
  "INFO"  => pastel.green.detach,
  "DEBUG" => pastel.white.detach,
}
logger.formatter = ->(severity, datetime, progname, message) {
  colorizer = colors[severity]
  "#{colorizer.(severity)}: #{message}\n"
}

logger.debug "Logging enabled"
logger.info "Ready for launch"
logger.warn "These readings look weird"
logger.error "Tang reservoir empty!"
logger.fatal "Aborting launch"

When we run this code, we can see some nice pretty colorized log messages.

The down side to having colorized logs is that if we ever do redirect these logs into a file or into some other program, they are going to include the ANSI escape characters. And chances are, if we're piping the output to some other program or saving it in a file, we don't want that kind of noise to be included.

Fortunately, there's an easy way to disable the colorization. As we saw back in episode #105, Ruby enables us to detect whether the program is outputting to a terminal or not.

We add a ternary to the colorizer assignment. We check $stdout, since that's what we're using for log output. We ask it if it is connected to a terminal. If it is, we use the pastel colorizer object. Otherwise, we assign a do-nothing lambda that just returns its input unmodified.

require "pastel"
require "logger"

logger = Logger.new($stdout)
pastel = Pastel.new
colors = {
  "FATAL" => pastel.red.bold.detach,
  "ERROR" => pastel.red.detach,
  "WARN"  => pastel.yellow.detach,
  "INFO"  => pastel.green.detach,
  "DEBUG" => pastel.white.detach,
}
logger.formatter = ->(severity, datetime, progname, message) {
  colorizer = $stdout.tty? ? colors[severity] : ->(s){s}
  "#{colorizer.(severity)}: #{message}\n"
}

logger.debug "Logging enabled"
logger.info "Ready for launch"
logger.warn "These readings look weird"
logger.error "Tang reservoir empty!"
logger.fatal "Aborting launch"

Let's run this program again. If we run it as before from the command line, we see the colored log messages as usual. But if we pipe it into a command, the escape sequences are gone. The program detected it was not hooked up directly to the terminal, and disabled colorization.

And there you have it: an easy way to spiff up your user-facing log messages using a log formatter and the pastel gem. Happy hacking!

Responses