Project

betterlog

0.0
A long-lived project that still receives updates
This library provides structure json logging for our rails projects
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
 Dependencies

Development

Runtime

>= 0
~> 1.0
~> 2.0
~> 1.3, >= 1.22.0
 Project Readme

betterlog

Description

Structured logging support for Rails applications with flexible formatting and filtering capabilities.

Installation

You can use rubygems to fetch the gem and install it for you:

# gem install betterlog

You can also put this line into your Gemfile:

gem 'betterlog'

and bundle. This will make the Betterlog module and its logging facilities available.

Usage

Basic Logging

The main logging interface provides structured JSON logging with automatic Rails integration:

# Log simple messages
Log.info("User logged in", meta: { module: 'session', user_id: 123 })
Log.error("Database connection failed", meta: { module: 'database', error: "Connection timeout" })

# Log exceptions with full backtrace information
begin
  # some code that might fail
rescue => e
  Log.error(e, meta: { module: 'api_v4', context: "API request processing" })
end

Command-Line Interface

The betterlog CLI tool provides powerful log file processing and filtering capabilities:

Basic Usage

Tail local logfiles:

$ betterlog -f

Filter from stdin:

$ cat log/development.log | betterlog

Search for GET in the last 1000 rails log lines:

$ betterlog -F rails -n 1000 -s GET

Display help for all options:

$ betterlog -h

Command-Line Options

  • -c Enable colors during pretty printing
  • -f Follow the log files in real-time
  • -h Display help information
  • -p FORMAT Pretty print log files (e.g., long, short)
  • -e EMITTER Only output events from specific emitters
  • -s MATCH Only display events matching search string
  • -S SEVERITY Only output events with specified severity level
  • -n NUMBER Rewind this many lines backwards before tailing
  • -F SHORTCUT Use configuration file shortcuts

Examples

Follow Rails logs with colorized output for errors or greater:

$ betterlog -f -F rails -p long -c -S ">=error"

Search for specific terms in log files:

$ betterlog -f -s SELECT

This filters out all logging messages where meta: { module: 'api_v4' } was given:

$ betterlog -f -s meta:module=api_v4

Follow multiple log files with default format in colors including the last 10 lines:

$ betterlog -f -F rails -F redis -pd -c -n 10

Filter stdin from file with default format in color:

$ betterlog -pd -c < unicorn.log

Filter last 10 lines of a file with default format in color:

$ betterlog -c -pd -n 10 unicorn.log

Architecture Overview

Betterlog follows a modular architecture where different components work together to provide structured logging:

graph TD
    A[Application Code] --> B(Log Interface)
    B --> C[Betterlog::Log]
    C --> D[Event Creation]
    D --> E[JSON Serialization]
    E --> F[Formatting Engine]
    F --> G[Output to Logger]

    subgraph "Rails Integration"
        H[Rails.logger]
        I[LegacyFormatter]
        J[Structured Logging]
        H <---> I
        I <---> J
    end

    C --> H
Loading

Diagram Explanation: This shows how the system is structured. Application code calls the logging interface (Log.info), which creates structured events and processes them through JSON serialization and formatting before outputting to either Rails' logger or a default logger. The Rails integration section shows how it seamlessly connects with Rails' existing logging infrastructure.

Logging Flow

When you call a logging method, the following process occurs:

sequenceDiagram
    participant App as Application
    participant Log as Betterlog::Log
    participant Formatter as EventFormatter
    participant Logger as Rails.logger/DefaultLogger

    App->>Log: Log.info(message, data)
    Log->>Formatter: format(pretty: false)
    Formatter->>Formatter: JSON.generate(event)
    Formatter->>Logger: logger.info(json_string)
    Logger-->>App: Success/Failure
Loading

Diagram Explanation: This illustrates the complete logging workflow. When you call Log.info(), it goes through the Betterlog::Log (or Log) class, gets formatted into a structured event with JSON serialization, and finally outputs to either Rails' logger or the default logger. The process is designed to maintain structured logging while being compatible with existing Rails logging systems.

Thread-Local Metadata

Betterlog supports thread-local metadata that gets automatically included with all log events:

sequenceDiagram
    participant User as User Code
    participant Meta as GlobalMetadata
    participant Log as Betterlog::Log

    User->>Meta: Betterlog.with_meta(data) { block }
    Meta->>Meta: Add data to thread-local storage
    Log->>Log: emit(event)
    Log->>Log: Merge with GlobalMetadata.current
    Meta->>Meta: Remove data after block
Loading

Diagram Explanation: This shows how thread-local metadata works. When you use Betterlog.with_meta(), it temporarily adds metadata to a thread-local store, which gets merged with your log events during processing. The metadata is automatically cleaned up after the block executes, ensuring thread safety and preventing memory leaks.

Practical Usage Examples:

In Rails Controllers (from application_controller.rb):

class ApplicationController < ActionController::Base
  # …
  around_action :set_global_metadata


  def set_global_metadata(&block)
    Betterlog.with_meta(
      request_id: request.request_id,
      user_id:    current_user&.id,
      &block
    )
  end
end

This ensures all logs within a request include user and request context automatically.

In Background Jobs (from booking_mandate/job.rb):

Betterlog.with_meta(
  module: 'booking',
  booking_mandate_id: id,
) do
  # All logs in this block automatically include:
  # - module: 'booking'
  # - booking_mandate_id: id
  Log.info("Successfully booked booking mandate #{id}")
end

This pattern provides consistent contextual information across all log entries for a given request or job execution while maintaining thread safety.

These examples show how global metadata is actually used in production code:

  1. Controller level: Automatically includes request and user context for all logs during a web request
  2. Job level: Automatically includes job-specific context (like booking mandate ID) for all logs within that job execution

This makes debugging much easier since you can always trace logs back to their specific context without having to manually pass around context information.

Configuration

Configuration is handled through the config/log.yml file. The configuration supports:

  • Log formats and styling definitions
  • Severity filtering rules
  • Configuration shortcuts for different environments
  • Color schemes for terminal output

Example configuration structure:

development: &development
  level: <%= ENV.fetch('LOG_LEVEL', :debug).to_sym %>
  styles:
    'timestamp': [ yellow, bold ]
    'file': [ blue, bold ]
    severity:
      debug: green
      info: green
      warn:  yellow
      error: red
      fatal: [ red, blink ]
      unknown: red
  formats:
    default: &default_format >
      {%lt%timestamp} {%5s%severity}
      "{%0.<%= ENV.fetch('COLUMNS', 80).to_i / 2 %>%message}"
      {host}
      {location}
      {file}{-%O%backtrace}{-%O%meta}
    d: *default_format
    long: &long_format |
      type: {type}
      timestamp: {%lt%timestamp}
      severity: {severity}
      message: "{message}"
      error_class: {error_class}
      backtrace: {%O%backtrace}
      host: {host}
      location: {location}
      file: {file}
      meta: {%O%meta}\n
    l: *long_format
    legacy: >
      {%0.1s%severity} [{%lt%timestamp} #{%d%pid}] {%5s%severity} --
      {program}: {message}
  config_files:
    rails:
      - log/development.log
    test:
      - log/test.log
    mysql:
      - log/mysql/mysqld.log
      - log/mysql/mysql-slow.log
  legacy_supported: yes
test:
  <<: *development
  level: <%= ENV.fetch('LOG_LEVEL', :debug).to_sym %>
staging: *development
production:
  <<: *development
  level: <%= ENV.fetch('LOG_LEVEL', :info).to_sym %>

Rails Integration

When used in a Rails application, betterlog automatically integrates with Rails' logging system by detecting Rails presence and using Rails.logger instead of its default logger. The system also includes a Betterlog::Log::LegacyEventFormatter that ensures compatibility with existing Rails logging formats.

Download

The homepage of this library is located at

Author

The betterplace Developers

License

This software is licensed under the Apache 2.0 license