Logging in Crystal

Here’s the translation of the Go logging example to Crystal, formatted in Markdown suitable for Hugo:

Our example demonstrates various logging techniques in Crystal. The standard library provides tools for outputting logs from Crystal programs, with the Log module for free-form output.

require "log"

# Simply invoking methods like `info` from the `Log` module uses the standard
# logger, which is already pre-configured for reasonable logging output to STDOUT.
Log.info { "standard logger" }

# Loggers can be configured with various settings to set their output format.
# We can change its severity to debug and add a timestamp, for example.
Log.setup(:debug, Log::IOBackend.new(formatter: Log::Formatter.new do |entry, io|
  io << entry.timestamp << " " << entry.severity << " -- " << entry.message
end))
Log.debug { "with timestamp" }

# It also supports emitting the file name and line from which the log method is called.
Log.setup(:debug, Log::IOBackend.new(formatter: Log::Formatter.new do |entry, io|
  io << entry.timestamp << " " << entry.severity << " -- " << entry.message << " (#{entry.source}:#{entry.line})"
end))
Log.debug { "with file/line" }

# It may be useful to create a custom logger. When creating a new logger,
# we can set a name to distinguish its output from other loggers.
mylog = Log.for("mylog")
mylog.info { "from mylog" }

# We can set the name on existing loggers as well.
mylog = Log.for("ohmy")
mylog.info { "from mylog"  }

# Loggers can have custom output targets; any IO works.
memory_io = IO::Memory.new
Log.setup(:info, Log::IOBackend.new(memory_io))
Log.info { "hello" }

# This will actually show it on standard output.
puts "from memory_io: #{memory_io.to_s}"

# Crystal doesn't have a built-in structured logging package like Go's slog,
# but we can create a simple JSON logger using the JSON module.
require "json"

class JSONLogger
  def initialize(@io : IO)
  end

  def info(message : String, **kwargs)
    log_entry = {
      "time" => Time.utc.to_s,
      "level" => "INFO",
      "msg" => message
    }.merge(kwargs)
    @io.puts(log_entry.to_json)
  end
end

json_logger = JSONLogger.new(STDOUT)
json_logger.info("hi there")

# In addition to the message, our JSON logger output can
# contain an arbitrary number of key-value pairs.
json_logger.info("hello again", key: "val", age: 25)

Sample output; the date and time emitted will depend on when the example ran:

I -- standard logger
2023-08-22 10:45:16 DEBUG -- with timestamp
2023-08-22 10:45:16 DEBUG -- with file/line (example.cr:20)
I -- from mylog
I -- from mylog
from memory_io: I -- hello

{"time":"2023-08-22 10:45:16 UTC","level":"INFO","msg":"hi there"}
{"time":"2023-08-22 10:45:16 UTC","level":"INFO","msg":"hello again","key":"val","age":25}

This example showcases various logging techniques in Crystal. While Crystal’s logging capabilities differ from Go’s in some aspects, it provides flexible ways to output logs, including custom formatting and JSON output. The Log module in Crystal is the primary tool for logging, offering different severity levels and customizable output formats.