What is Semantic Logger?

Semantic Logger is a feature rich logging framework for Ruby, and a replacement for the built-in Ruby and Rails loggers.

It does two things that ordinary loggers do not:

  1. It logs structured data, not just strings. Along with the usual text message you can attach a payload (any Hash), an exception, a duration, metrics, and tags. That data is preserved all the way to the destination, so it stays searchable instead of being flattened into a line of text.
  2. It logs asynchronously. Log events are placed on an in-memory queue and written to their destinations by a separate background thread. Your application is not blocked while logs are written to disk, a database, or a remote service.

If you are using Rails, use the companion gem rails_semantic_logger, which replaces the Rails logger automatically. See the Rails guide.

Why use it?

The problem with traditional logging

With the standard Ruby logger you eventually end up building messages by hand:

logger.info("Queried users table in #{duration} ms, with a result code of #{result}")

That reads fine for a human, but to a machine it is just a string. To answer a question like “show me every query that took longer than 100 ms” you have to write fragile regular expressions against your log files, and every developer formats their messages slightly differently.

The Semantic Logger way

Log the message and the data separately:

logger.info("Queried users table",
  duration: duration,
  result:   result,
  table:    "users",
  action:   "query")

Now the same event is still readable by a human, but a machine can index duration, result, table, and action as real fields. Send it to a JSON file, Elasticsearch, or Splunk and you can search, filter, and build dashboards on those fields directly, no log parsing required.

Reasons developers choose Semantic Logger

Quick start

Install the gem:

gem install semantic_logger

Or add it to your Gemfile:

gem "semantic_logger"

Configure it once when your application starts, then log:

require "semantic_logger"

# Log :info and above. Use :trace, :debug, :info, :warn, :error, or :fatal.
SemanticLogger.default_level = :info

# Send log output to the screen using the colorized formatter.
SemanticLogger.add_appender(io: $stdout, formatter: :color)

# Create a logger for the current class. The class name is added to every message.
logger = SemanticLogger["MyApp"]

logger.info("Application started")

That is the whole setup. The Programmer’s Guide covers the full logging API.

A tour of the features

The examples below assume you already have a logger from SemanticLogger["MyApp"].

Log a message with structured data

logger.error("Outbound call failed", result: :failed, reason_code: -10)

Log an exception

Pass the exception directly. The class, message, and backtrace are all captured, including any nested “caused by” exceptions.

begin
  # ... code that may raise
rescue => exception
  logger.error("Import failed", exception)
end

Only evaluate expensive messages when needed

Pass a block. It runs only when the log level is active, so it is skipped entirely in production when the level is higher.

logger.debug { "Processed a total of #{records.sum(&:size)} bytes" }

Measure how long something takes

The message is written when the block completes, together with its duration. If the block raises, the exception is logged with the duration and then re-raised.

logger.measure_info("Called external interface") do
  # Code to call the external service ...
end

Every log entry inside the block carries the tags, which is invaluable for tracing one request across many classes and threads.

SemanticLogger.tagged(user: "jbloggs", request_id: "abc123") do
  logger.info("Hello World")
  logger.debug("More detail")
end

Name your threads

So concurrent log entries are easy to tell apart.

Thread.current.name = "worker-1"

How it works

Every logger forwards its log events to a single, shared background thread through an in-memory queue. That thread writes each event to every registered appender (destination) in turn. Because the writing happens off to the side, the thread that called logger.info returns immediately.

Log message flow diagram

This design is why Semantic Logger is both fast and thread safe: log calls from hundreds of concurrent threads simply enqueue events, and each appender writes them out sequentially in the correct order. For the rare cases where you want logging to happen inline on the calling thread, see Synchronous Operation.

Ruby Support

For the complete list of supported Ruby versions, see the Testing file.

Next: Rails ==>