SkillAgentSearch skills...

Lumberjack

A simple, powerful, and very fast logging utility that can be a drop in replacement for Logger. Provides support for automatically rolling log files, formatting log output, and tagging log entries.

Install / Use

/learn @bdurand/Lumberjack
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

Lumberjack

Continuous Integration Ruby Style Guide Gem Version

Lumberjack is an extension to the Ruby standard library Logger class, designed to provide advanced, flexible, and structured logging for Ruby applications. It builds on the familiar Logger API, adding powerful features for modern logging needs:

  • Attributes for Structured Logging: Use attributes to attach structured, machine-readable metadata to log entries, enabling better filtering, searching, and analytics.
  • Context Isolation: Isolate logging behavior to specific blocks of code. The attributes, level, and progname for the logger can all be changed in a context block and only impact the log messages created within that block.
  • Formatters: Control how objects are logged with customizable formatters for messages and attributes.
  • Devices and Templates: Choose from a variety of output devices and templates to define the format and destination of your logs.
  • Forked Loggers: Create independent logger instances that inherit context from a parent logger, allowing for isolated logging configurations in different parts of your application.
  • Testing Tools: Built-in testing devices and helpers make it easy to assert logging behavior in your test suite.

The philosophy behind the library is to promote use of structured logging with the standard Ruby Logger API as a foundation. The developer of a piece of functionality should only need to worry about the data that needs to be logged for that functionality and not how it is logged or formatted. Loggers can be setup with global attributes and formatters that handle these concerns.

Table of Contents

Usage

Context Isolation

Lumberjack provides context isolation that allow you to temporarily modify logging behavior for specific blocks of code or create independent logger instances. This is particularly useful for isolating logging configuration in different parts of your application without affecting the global logger state.

Context Blocks

Context blocks allow you to temporarily change the logger's configuration (level, progname, and attributes) for a specific block of code. When the block exits, the logger returns to its previous state.

Context blocks and forked loggers are thread and fiber-safe, maintaining isolation across concurrent operations:

logger.level = :info

# Temporarily change log level for debugging a specific section
logger.context do
  logger.level = :debug
  logger.debug("This debug message will be logged")
end

# Back to info level - debug messages are filtered out again
logger.debug("This won't be logged")

You can use with_level, with_progname, and tag to setup a context block with a specific level, progname, or attributes.

As a best practice, every main unit of work in your application (i.e. HTTP request, background job, etc.) should have a context block. This ensures that any attributes or changes to the logger are scoped to that unit of work and do not leak into other parts of the application.

Nested Context Blocks

Context blocks can be nested, with inner contexts inheriting and potentially overriding outer context settings:

logger.context do
  logger.tag(user_id: 123, service: "api")
  logger.info("API request started") # Includes user_id: 123, service: "api"

  logger.context(endpoint: "/users", service: "user_service") do
    logger.tag(service: "user_service", endpoint: "/users")
    logger.info("Processing user data") # Includes: user_id: 123, service: "user_service", endpoint: "/users"
  end

  logger.info("API request completed") # Back to: user_id: 123, service: "api"
end

Forking Loggers

Logger forking creates independent logger instances that inherit the parent logger's current context. Changes made to the forked logger will not affect the parent logger.

Forked loggers are useful when there is a section of your application that requires different logging behavior. Forked loggers are cheap to create, so you can use them liberally.

main_logger = Lumberjack::Logger.new
main_logger.tag!(version: "1.0.0")

# Create a forked logger for a specific component
user_service_logger = main_logger.fork(progname: "UserService", level: :debug)
user_service_logger.tag!(component: "user_management")

user_service_logger.debug("Debug info")    # Includes version and component attributes
main_logger.info("Main logger info")       # Includes only version attribute
main_logger.debug("Main logger debug info") # Not logged since level is :info

Isolation Level

By default each fiber will have its own logging context. This is useful in asynchronous applications where multiple fibers may be running concurrently. You can change the isolation level to :thread if you want each thread to have its own logging context instead.

Lumberjack.isolation_level = :thread # Set isolation level globally

logger = Lumberjack::Logger.new(STDOUT, isolation_level: :thread) # Set isolation level per logger

Fiber isolation is the safest behavior since it completely isolates local contexts. However, in applications where threads are the main unit of work and fibers are never shared across threads, thread isolation may be more appropriate. Otherwise you can end up with the logger losing the context block when switching fibers within the same thread.

Structured Logging With Attributes

Lumberjack extends standard logging with attributes (structured key-value pairs) that add context and metadata to your log entries. This enables powerful filtering, searching, and analytics capabilities.

Basic Attribute Logging

Add attributes with any logging method:

# Add attributes to individual log calls
logger.info("User logged in", user_id: 123, ip_address: "192.168.1.100")
logger.error("Payment failed", user_id: 123, amount: 29.99, error: "card_declined")

# Attributes can be any type
logger.debug("Processing data",
  records_count: 1500,
  processing_time: 2.34,
  metadata: { batch_id: "abc-123", source: "api" },
  timestamp: Time.now
)

[!Note] Attributes are passed in log statements in the little used progname argument that is defined in the standard Ruby Logger API. This attribute is normally used to set a specific program name for the log entry that overrides the default program name on the logger.

The only difference in the API is that Lumberjack loggers can take a Hash to set attributes instead of just a string. You can still pass a string to override the program name.

Adding attributes to the logger

Use the tag method to tag the the current context with attributes. The attributes will be included in all log entries within that context.

logger.context do
  logger.tag(user_id: current_user.id, session: "abc-def")
  logger.info("Session started")           # Includes user_id and session
  logger.debug("Loading user preferences") # Includes user_id and session
  logger.info("Dashboard rendered")        # Includes user_id and session
end

logger.info("Outside of context") # Does not include user_id or session

You can also use the tag method with a block to open a new context and assign attributes.

# Apply attributes to all log entries within the block
logger.tag(user_id: 123, session: "abc-def") do
  logger.info("Session started")
  logger.debug("Loading user preferences")
  logger.info("Dashboard rendered")
end

Calling tag outside of a context without a block is a no-op and has no effect on the logger.

You can also use the tag_all_contexts method to add attributes to all parent context blocks. This can be useful in cases where you need to set an attribute that should be included in all subsequent log entries for the duration of the process defined by the outermost context.

Consider this example where we want to include the user_id attribute in all log entries. We need to set it on all parent contexts in order to have it extend beyond the current context block.

logger.context do
  logger.tag(request_id: "req-123") do
    logger.info("Processing request") # Includes request_id

    logger
View on GitHub
GitHub Stars305
CategoryCustomer
Updated8h ago
Forks40

Languages

Ruby

Security Score

95/100

Audited on Apr 7, 2026

No findings