ActiveModelLogger

Ruby Version Rails Version ActiveRecord Version Build Status Gem Version

A powerful Ruby gem that provides comprehensive structured logging functionality for ActiveRecord models. Store logs in a separate database table with support for log chains, metadata, different visibility levels, and atomic operations.

🚀 Quick Start

# 1. Add to Gemfile
gem 'active_model_logger'

# 2. Install and generate migration
bundle install
rails generate active_model_logger:install
rails db:migrate

# 3. Include in your models
class User < ApplicationRecord
  include ActiveModelLogger::Loggable
end

# 4. Start logging
user = User.find(1)
user.log("User logged in", metadata: { ip: "192.168.1.1" })

# 5. Use log blocks for atomic operations
user.log_block("Order processing") do |log|
  log.info("Payment processed", amount: 99.99)
  log.info("Inventory updated", items: 3)
  log.info("Order completed", status: "success")
end

📋 Table of Contents

Features

🚀 Core Logging

  • Structured Logging: Store logs in a dedicated active_model_logs table with full metadata support
  • Log Chains: Track related logs using shared log chains with intelligent caching
    • Debug complex actions that take place over multiple processes
    • Automatic chain management for synchronous operations
    • Manual chain control for cross-process workflows
  • Log Blocks: Atomic logging operations that group related logs together
    • Automatic error handling and rollback
    • Nested log block support
    • Performance-optimized batch operations
  • Metadata Support: Store additional structured data with each log entry
    • JSONB storage for flexible data structures
    • Nested key searching across complex metadata
    • Type-safe data handling

🔍 Advanced Querying

  • Powerful Scopes: Built-in scopes for filtering and searching logs
    • Query by log level, visibility, status, category, type
    • Time-based range queries and ordering
    • Deep metadata searching with nested key support
  • Database-Agnostic JSON Queries: Works seamlessly across different databases
    • PostgreSQL: Native JSON operators (->, ->>)
    • MySQL: JSON_EXTRACT and JSON_SEARCH functions
    • SQLite: LIKE-based pattern matching
  • Performance Optimized: Efficient querying with proper indexing support

🎯 User Experience

  • Visibility Levels: Control who can see different log entries
  • Dual Output: Logs are stored in database AND sent to standard output
    • Formatted console output with timestamps and metadata
    • Configurable logging levels and custom loggers
    • Per-model stdout logging control
  • Rails Integration: Easy setup with Rails generators
  • Polymorphic Associations: Create logs on any ActiveRecord model

⚡ Performance & Reliability

  • Batch Logging: Efficiently create multiple log entries at once
    • Uses insert_all for optimal performance (ActiveRecord 6.0+)
    • Automatic log chain caching
    • Memory-efficient bulk operations
  • Database Agnostic: Works with PostgreSQL, MySQL, and SQLite
  • Ruby Compatibility: Tested across multiple Ruby versions (3.1+)
    • Comprehensive test suite across Ruby 3.1.4, 3.2.4, 3.3.1, 3.3.6
    • ActiveRecord compatibility testing (7.1+, 8.0+)
  • Error Handling: Robust error handling with graceful degradation
  • Log Cleanup: Built-in log rotation and archival support

🛠️ Developer Experience

  • Comprehensive Testing: Full test suite with 37 tests and 183 assertions
  • Code Quality: RuboCop compliance and clean code standards
  • Documentation: Extensive documentation with examples and API reference
  • Demo Project: Complete Rails demo application showcasing all features
  • Interactive Dashboard: Real-time log viewing and management

Ruby Compatibility

ActiveModelLogger is tested and compatible with:

Ruby Version Status Notes Tested With
2.7.x ❌ Not Supported Compilation issues on Apple Silicon -
3.0.x ❌ Not Supported Compilation issues on Apple Silicon -
3.1.4 ✅ Supported Security maintenance
3.2.4 ✅ Supported Stable, recommended
3.3.1 ✅ Supported Current stable
3.3.6 ✅ Supported Latest patch version
3.4.0 ✅ Supported Latest features

ActiveRecord Compatibility

ActiveRecord Version Status Notes Tested With
7.1.x ✅ Supported LTS version, recommended
8.0.x ✅ Supported Latest stable
8.1.x ✅ Supported Latest features

Database Compatibility

Database Status JSON Support Notes
PostgreSQL ✅ Fully Supported Native JSONB Recommended for production
MySQL 5.7+ ✅ Supported JSON functions Requires MySQL 5.7+
SQLite 3.38+ ✅ Supported JSON1 extension Good for development

Testing Ruby Compatibility

We provide comprehensive testing scripts for Ruby compatibility:

# Quick compatibility check
./bin/check_ruby_versions

# Test commonly used Ruby versions
./bin/test_ruby_versions

# Test specific Ruby version
./bin/test_specific_ruby 3.2.4

# Test with specific ActiveRecord version
./bin/test_specific_ruby 3.3.1 "~> 8.0.0"

For detailed information, see Ruby Compatibility Testing.

Installation

Prerequisites

  • Ruby 3.1+ (tested with 3.1.4, 3.2.4, 3.3.1, 3.3.6)
  • Rails 7.1+ (tested with 7.1, 8.0)
  • ActiveRecord 7.1+ (tested with 7.1, 8.0)
  • PostgreSQL, MySQL, or SQLite database

Step 1: Add to Gemfile

Add this line to your application's Gemfile:

gem 'active_model_logger'

Step 2: Install Dependencies

bundle install

Step 3: Generate Migration

rails generate active_model_logger:install

This creates a migration for the active_model_logs table with the following features:

  • UUID primary key
  • Polymorphic associations to any ActiveRecord model
  • JSONB metadata field for flexible data storage
  • Proper indexing for performance

Step 4: Run Migration

rails db:migrate

Step 5: Include in Models

# Individual models
class User < ApplicationRecord
  include ActiveModelLogger::Loggable
end

class Order < ApplicationRecord
  include ActiveModelLogger::Loggable
end

# Or enable for all models
class ApplicationRecord < ActiveRecord::Base
  include ActiveModelLogger::Loggable
end

Alternative Installation

You can also install the gem directly:

gem install active_model_logger

Verification

Test your installation:

# In Rails console
user = User.create!(name: "Test User")
user.log("Installation test successful")
puts user.logs.count # Should output: 1

Usage

Basic Logging

The simplest way to start logging is with the log method:

user = User.find(1)

# Basic logging
user.log("User logged in")
user.log("Payment processed", log_level: "info")

# Logging with metadata
user.log("Order created",
         log_level: "info",
         metadata: {
           status: "success",
           category: "order",
           order_id: 123,
           amount: 99.99
         })

# Logging with custom visibility
user.log("Admin action performed",
         visible_to: "admin",
         metadata: { action: "user_suspended" })

Log Blocks (Atomic Operations)

Use log_block for grouping related logs together with automatic error handling:

# Basic log block
user.log_block("Order processing") do |log|
  log.info("Payment processed", amount: 99.99)
  log.info("Inventory updated", items: 3)
  log.info("Order completed", status: "success")
end

# Log block with custom chain
user.log_block("Order processing", log_chain: "order_123") do |log|
  log.info("Step 1: Payment processed", amount: 99.99)
  log.info("Step 2: Inventory updated", items: 3)
  log.info("Step 3: Order completed", status: "success")
end

# Log block with error handling
user.log_block("Risky operation") do |log|
  log.info("Starting risky operation")
  # If an exception occurs here, it will be logged and re-raised
  risky_operation!
  log.info("Risky operation completed successfully")
end

Log Chains

Log chains help you track related operations across multiple processes:

user = User.find(1)

# Automatic log chain caching
user.log("Process started", log_chain: "order_processing_123")
user.log("Step 1 completed")  # Uses cached log chain
user.log("Step 2 completed")  # Uses cached log chain

# Explicit log_chain breaks the cache and starts a new chain
user.log("New process", log_chain: "process_456")  # Breaks cache
user.log("Step A")  # Uses new cached log chain
user.log("Step B")  # Uses new cached log chain

# Cross-process log chains (for background jobs)
# Pass the log_chain between processes
log_chain = user.log_chain
SomeBackgroundJob.perform_later(user.id, log_chain)

# In the background job
class SomeBackgroundJob < ApplicationJob
  def perform(user_id, log_chain)
    user = User.find(user_id)
    user.log("Background job started", log_chain: log_chain)
    # ... do work ...
    user.log("Background job completed", log_chain: log_chain)
  end
end

Batch Logging

For efficient bulk operations, use log_batch:

# Basic batch logging
user.log_batch([
  { message: "Step 1 completed", status: "success" },
  { message: "Step 2 completed", status: "success" },
  { message: "Process finished", status: "complete" }
])

# Batch logging with custom log chain
user.log_batch([
  { message: "Step 1 completed", status: "success", log_chain: "process_123" },
  { message: "Step 2 completed", status: "success" },  # Uses cached log_chain
  { message: "Process finished", status: "complete" }  # Uses cached log_chain
])

# Batch logging with metadata
user.log_batch([
  {
    message: "Payment processed",
    status: "success",
    category: "payment",
    amount: 99.99,
    payment_method: "credit_card"
  },
  {
    message: "Inventory updated",
    status: "success",
    category: "inventory",
    items_updated: 3
  }
])

Advanced Logging Patterns

# Conditional logging
user.log("User action", metadata: { action: "login" }) if user.persisted?

# Logging with validation
user.log("Data validation failed",
         log_level: "error",
         metadata: {
           errors: user.errors.full_messages,
           field: "email"
         }) unless user.valid?

# Logging with callbacks
class User < ApplicationRecord
  include ActiveModelLogger::Loggable

  after_create :log_user_creation
  after_update :log_user_update

  private

  def log_user_creation
    log("User created", metadata: {
      email: email,
      created_at: created_at
    })
  end

  def log_user_update
    log("User updated", metadata: {
      changes: saved_changes,
      updated_at: updated_at
    })
  end
end

API Reference

Loggable Concern

The ActiveModelLogger::Loggable concern provides the following methods:

log(message, options = {})

Creates a new log entry for the model.

Parameters:

  • message (String): The log message
  • visible_to (String): Who can see this log (default: "admin")
  • log_level (String): Log level (default: "info")
  • log_chain (String): Custom log chain for grouping related logs (optional)
  • metadata (Hash): Additional structured data

Example:

user.log("Action completed",
         log_level: "info",
         visible_to: "account_admin",
         log_chain: "payment_123",
         metadata: { status: "success", category: "payment" })

logs(log_chain: nil)

Returns all logs for the model, optionally filtered by log chain.

log_chain

Returns the current log chain (UUID by default). Used to group related logs.

log_block(title, options = {}, &block)

Creates a log block for atomic operations with automatic error handling.

Parameters:

  • title (String): Title for the log block
  • log_chain (String): Custom log chain for the block (optional)
  • visible_to (String): Visibility level for logs in the block (optional)
  • log_level (String): Default log level for logs in the block (optional)
  • &block: Block containing log operations

Example:

user.log_block("Order processing") do |log|
  log.info("Payment processed", amount: 99.99)
  log.warn("Low inventory", items_remaining: 2)
  log.error("Payment failed", error: "insufficient_funds")
end

# With custom options
user.log_block("Admin operation",
               log_chain: "admin_123",
               visible_to: "admin") do |log|
  log.info("Admin action started")
  log.info("Admin action completed")
end

log_batch(entries)

Creates multiple log entries efficiently in a single database operation.

Parameters:

  • entries (Array): Array of log entry hashes

Example:

user.log_batch([
  { message: "Step 1 completed", status: "success", log_chain: "process_123" },
  { message: "Step 2 completed", status: "success" },  # Uses cached log_chain
  { message: "Process finished", status: "complete" }  # Uses cached log_chain
])

Log Model Scopes

The ActiveModelLogger::Log model provides several scopes for querying:

# Filter by log level
ActiveModelLogger::Log.by_level("error")
ActiveModelLogger::Log.info_logs
ActiveModelLogger::Log.error_logs

# Filter by visibility
ActiveModelLogger::Log.by_visibility("admin")

# Filter by log chain
ActiveModelLogger::Log.by_log_chain("process_123")

# Filter by status
ActiveModelLogger::Log.by_status("success")

# Filter by category
ActiveModelLogger::Log.by_category("payment")

# Filter by type
ActiveModelLogger::Log.by_type("user_action")

# Filter logs with data
ActiveModelLogger::Log.with_data                    # All logs with data
ActiveModelLogger::Log.with_data({ user_id: 123 })  # Logs with specific data

# Filter logs by keys (anywhere in metadata, including nested)
ActiveModelLogger::Log.with_keys("user_id")                    # Logs containing user_id key
ActiveModelLogger::Log.with_keys("user_id", "order_id")        # Logs containing both keys
ActiveModelLogger::Log.with_keys("email")                      # Finds email key anywhere (root, data, nested)
ActiveModelLogger::Log.with_keys("enabled")                    # Finds enabled key in deeply nested structures

# Examples of nested key searching:
# metadata: {"settings": {"notifications": {"email": true, "sms": false}}}
# metadata: {"user": {"profile": {"contact": {"email": "[email protected]"}}}}
# metadata: {"config": {"cache": {"redis": {"enabled": true}}}}
# All of these would be found by with_keys("email") or with_keys("enabled")

# Ordering
ActiveModelLogger::Log.oldest(5)  # 5 oldest logs
ActiveModelLogger::Log.newest(5)  # 5 newest logs

# Time-based filtering
ActiveModelLogger::Log.in_range(1.hour.ago, Time.current)
ActiveModelLogger::Log.newest(10)  # Last 10 logs

Log Model

The ActiveModelLogger::Log model represents individual log entries.

Attributes:

  • message: The log message
  • metadata: JSONB field for additional data including:
    • log_level: The log level (info, warn, error, etc.) (default: "info")
    • visible_to: Who can see this log (default: "admin")
    • log_chain: Groups related logs together
    • status, type, category, title, data: Additional metadata fields

Log Chains

Log chains are used to group related logs together, allowing you to track a series of events. The gem includes intelligent caching behavior for log chains:

Automatic Log Chain Caching

When you don't provide an explicit log_chain, the gem automatically uses the most recent log's chain or generates a new UUID:

# First log generates and caches a UUID
user.log("Process started")  # Uses: "abc-123-def"

# Subsequent logs use the cached chain
user.log("Step 1 completed")  # Uses: "abc-123-def" (cached)
user.log("Step 2 completed")  # Uses: "abc-123-def" (cached)

# Explicit log_chain breaks the cache
user.log("New process", log_chain: "process_456")  # Uses: "process_456"

# Next logs use the new cached chain
user.log("Step 1")  # Uses: "process_456" (cached)
user.log("Step 2")  # Uses: "process_456" (cached)

Manual Log Chain Management

You can also explicitly manage log chains:

# All logs will share the same log_chain
user.log("Process started", log_chain: "order_processing_123")
user.log("Step 1 completed")  # Uses cached "order_processing_123"
user.log("Step 2 completed")  # Uses cached "order_processing_123"
user.log("Process finished")  # Uses cached "order_processing_123"

# Get all logs in this chain
user.logs(log_chain: "order_processing_123")
# or
user.logs(log_chain: user.log_chain)

Use Cases

Log chains are perfect for:

  • Process Tracking: Track a multi-step process through its lifecycle
  • Error Investigation: Group related error logs together
  • Audit Trails: Maintain a complete audit trail for specific actions
  • Debugging: Follow a request through multiple services
  • Batch Operations: Track the progress of bulk operations

Standard Output Logging

By default, all logs are sent to both the database and standard output. This provides immediate visibility into your application's logging activity while maintaining a persistent record.

Output Format

Logs sent to stdout follow this format:

[2025-09-05 20:58:15] INFO User#123 [chain:abc-123-def] - User logged in (status=success, category=authentication)
[2025-09-05 20:58:16] WARN Order#456 [chain:order-789] - Payment processing started (amount=99.99)
[2025-09-05 20:58:17] ERROR User#123 [chain:abc-123-def] - Payment failed (error=insufficient_funds)

Configuration

You can customize stdout logging behavior:

class User < ApplicationRecord
  include ActiveModelLogger::Loggable

  # Disable stdout logging
  configure_loggable(stdout_logging: false)

  # Use custom logger
  configure_loggable(
    stdout_logger: Logger.new("log/application.log"),
    stdout_logging: true
  )
end

Disabling Stdout Logging

To disable stdout logging entirely:

# Global configuration
ActiveModelLogger::Loggable.configure_loggable(stdout_logging: false)

# Per-model configuration
class User < ApplicationRecord
  include ActiveModelLogger::Loggable
  configure_loggable(stdout_logging: false)
end

Configuration

You can configure the default behavior of the Loggable concern:

class User < ApplicationRecord
  include ActiveModelLogger::Loggable

  # Configure default values
  configure_loggable(
    default_visible_to: "user",
    default_log_level: "debug",
    auto_log_chain: true,
    log_chain_method: :log_chain,
    stdout_logging: true,
    stdout_logger: nil
  )
end

Configuration Options:

  • default_visible_to: Default visibility level for logs (default: "admin")
  • default_log_level: Default log level for logs (default: "info")
  • auto_log_chain: Automatically generate log chains (default: true)
  • log_chain_method: Method to call for log chain generation (default: :log_chain)
  • stdout_logging: Enable logging to standard output (default: true)
  • stdout_logger: Custom logger instance for stdout (default: Logger.new(STDOUT))

Use Cases

E-commerce Applications

class Order < ApplicationRecord
  include ActiveModelLogger::Loggable

  def process_payment!
    log_block("Payment Processing") do |log|
      log.info("Payment initiated", amount: total_amount, method: payment_method)

      result = PaymentGateway.charge(total_amount)

      if result.success?
        log.info("Payment successful", transaction_id: result.id)
        update!(status: 'paid')
        log.info("Order status updated", status: 'paid')
      else
        log.error("Payment failed", error: result.error_message)
        raise PaymentError, result.error_message
      end
    end
  end
end

User Authentication & Authorization

class User < ApplicationRecord
  include ActiveModelLogger::Loggable

  def log_login!(ip_address, user_agent)
    log("User logged in",
        metadata: {
          ip_address: ip_address,
          user_agent: user_agent,
          login_time: Time.current
        })
  end

  def log_failed_login!(ip_address, reason)
    log("Failed login attempt",
        log_level: "warn",
        metadata: {
          ip_address: ip_address,
          reason: reason,
          attempt_time: Time.current
        })
  end
end

Background Job Processing

class DataProcessingJob < ApplicationJob
  def perform(user_id, log_chain)
    user = User.find(user_id)

    user.log_block("Data Processing", log_chain: log_chain) do |log|
      log.info("Job started", job_id: job_id)

      # Process data
      processed_count = process_user_data(user)
      log.info("Data processed", count: processed_count)

      # Send notifications
      NotificationService.send_summary(user, processed_count)
      log.info("Notifications sent")

      log.info("Job completed successfully")
    end
  end
end

API Request Logging

class ApiController < ApplicationController
  before_action :log_request

  private

  def log_request
    current_user&.log("API Request",
                     metadata: {
                       endpoint: request.path,
                       method: request.method,
                       params: params.except(:controller, :action),
                       ip: request.remote_ip
                     })
  end
end

Audit Trails

class Document < ApplicationRecord
  include ActiveModelLogger::Loggable

  after_update :log_changes

  private

  def log_changes
    if saved_changes.any?
      log("Document updated",
          metadata: {
            changes: saved_changes,
            updated_by: Current.user&.id,
            updated_at: Time.current
          })
    end
  end
end

Advanced Usage

Custom Log Chain Generation

You can override the log_chain method to provide custom chain generation:

class Order < ApplicationRecord
  include ActiveModelLogger::Loggable

  private

  def log_chain
    "order_#{id}_#{Time.current.to_i}"
  end
end

Querying and Filtering

Use the built-in scopes for efficient querying:

# Find all error logs for a user
user.active_model_logs.error_logs

# Find logs in a specific time range
user.active_model_logs.in_range(1.day.ago, Time.current)

# Find logs with specific metadata
user.active_model_logs.by_status("success")
user.active_model_logs.by_category("payment")

# Find logs with specific data
user.active_model_logs.with_data({ user_id: 123 })
user.active_model_logs.with_data({ order_id: 456, status: "completed" })

# Find logs containing specific keys (anywhere in metadata)
user.active_model_logs.with_keys("user_id")
user.active_model_logs.with_keys("user_id", "order_id")
user.active_model_logs.with_keys("email")        # Finds email in any nested structure
user.active_model_logs.with_keys("enabled")      # Finds enabled in deeply nested config

# Find logs in a specific chain
user.active_model_logs.by_log_chain("order_processing_123")

# Order logs by creation time
user.active_model_logs.oldest(5)  # 5 oldest logs
user.active_model_logs.newest(5)  # 5 newest logs

# Complex queries
ActiveModelLogger::Log
  .by_level("error")
  .by_visibility("admin")
  .in_range(1.hour.ago, Time.current)
  .with_data

Performance Considerations

  • Batch Logging: Use log_batch for creating multiple logs efficiently
  • Indexing: Consider adding database indexes on frequently queried metadata fields
  • Cleanup: Implement log rotation or archival for high-volume applications
  • Scoping: Use scopes instead of loading all logs into memory

Database Schema

The active_model_logs table includes:

  • id (UUID, primary key)
  • loggable_type (string): The type of the associated model
  • loggable_id (UUID): The ID of the associated model
  • message (text): The log message
  • metadata (jsonb): Additional structured data including:
    • log_level (string): Log level (default: "info")
    • visible_to (string): Visibility level (default: "admin")
    • status, category, type, title, log_chain, data: Additional metadata fields
  • created_at, updated_at (datetime): Timestamps

Demo Project

A complete Rails demo application is included to showcase the gem's features:

# Navigate to the demo project
cd active_model_logger_demo

# Install dependencies
bundle install

# Run migrations
rails db:migrate

# Start the server
rails server

# Visit http://localhost:3000 to see the demo

The demo includes:

  • Interactive Dashboard: View logs in real-time
  • Code Examples: Live examples of all gem features
  • Log Chain Demo: See the caching behavior in action
  • Batch Logging: Examples of efficient bulk operations

Performance Tips

Database Optimization

# Add indexes for frequently queried fields
class AddIndexesToActiveModelLogs < ActiveRecord::Migration[7.1]
  def change
    add_index :active_model_logs, [:loggable_type, :loggable_id]
    add_index :active_model_logs, :created_at
    add_index :active_model_logs, :log_chain
    add_index :active_model_logs, :visible_to

    # For PostgreSQL JSONB queries
    add_index :active_model_logs, :metadata, using: :gin if connection.adapter_name == 'PostgreSQL'
  end
end

Efficient Querying

# Use scopes instead of loading all logs
user.logs.limit(10)  # Good
user.logs.to_a.first(10)  # Bad - loads all logs

# Use specific scopes for better performance
user.logs.error_logs.recent(5)  # Good
user.logs.select { |log| log.log_level == 'error' }.first(5)  # Bad

# Use batch operations for bulk inserts
user.log_batch(log_entries)  # Good - single DB operation
log_entries.each { |entry| user.log(entry) }  # Bad - multiple DB operations

Memory Management

# For large datasets, use pagination
user.logs.page(1).per(50)

# Use find_each for large result sets
ActiveModelLogger::Log.find_each(batch_size: 1000) do |log|
  # Process log
end

# Clean up old logs regularly
ActiveModelLogger::Log.where('created_at < ?', 1.year.ago).delete_all

Troubleshooting

Common Issues

1. Migration Errors

Problem: Migration fails with database-specific errors

Solution:

# Check your database adapter
Rails.application.config.database_configuration[Rails.env]['adapter']

# For SQLite, ensure you have the json1 extension
# For MySQL, ensure version 5.7+ for JSON support
# For PostgreSQL, JSONB is supported by default

2. Log Chain Not Working

Problem: Log chains aren't being shared between logs

Solution:

# Ensure you're not breaking the chain accidentally
user.log("First log", log_chain: "chain_123")
user.log("Second log")  # This will use the cached chain

# If you need a new chain, explicitly set it
user.log("New process", log_chain: "new_chain_456")

3. Stdout Logging Not Appearing

Problem: Logs aren't showing in console output

Solution:

# Check stdout logging configuration
class User < ApplicationRecord
  include ActiveModelLogger::Loggable
  configure_loggable(stdout_logging: true)  # Ensure it's enabled
end

# Check if you have a custom logger
configure_loggable(stdout_logger: Logger.new(STDOUT))

4. Performance Issues

Problem: Slow queries or high memory usage

Solution:

# Add database indexes
add_index :active_model_logs, [:loggable_type, :loggable_id, :created_at]

# Use batch operations
user.log_batch(entries)  # Instead of individual logs

# Implement log cleanup
ActiveModelLogger::Log.where('created_at < ?', 6.months.ago).delete_all

Debug Mode

Enable debug logging to troubleshoot issues:

# In development environment
Rails.logger.level = :debug

# Check log chain behavior
user.log("Debug test")
puts "Current log chain: #{user.log_chain}"
puts "Recent logs: #{user.logs.recent(3).pluck(:message)}"

FAQ

Q: Can I use ActiveModelLogger with non-ActiveRecord models?

A: No, ActiveModelLogger is specifically designed for ActiveRecord models. It relies on ActiveRecord's polymorphic associations and database features.

Q: How do I handle log chains across different servers/processes?

A: Pass the log chain explicitly between processes:

# In your main process
log_chain = user.log_chain
BackgroundJob.perform_later(user.id, log_chain)

# In your background job
class BackgroundJob < ApplicationJob
  def perform(user_id, log_chain)
    user = User.find(user_id)
    user.log("Job started", log_chain: log_chain)
    # ... do work ...
    user.log("Job completed", log_chain: log_chain)
  end
end

Q: Can I customize the log message format?

A: Yes, you can customize the stdout format by providing a custom logger:

class CustomLogger < Logger
  def format_message(severity, datetime, progname, msg)
    "[#{datetime}] #{severity} #{msg}\n"
  end
end

configure_loggable(stdout_logger: CustomLogger.new(STDOUT))

Q: How do I query logs by complex metadata?

A: Use the built-in scopes for common queries, or write custom scopes:

# Built-in scopes
user.logs.with_data({ user_id: 123 })
user.logs.with_keys("email", "phone")

# Custom scope
class ActiveModelLogger::Log
  scope :by_complex_metadata, ->(conditions) {
    where("metadata @> ?", conditions.to_json)
  }
end

# Usage
user.logs.({
  "user" => { "profile" => { "verified" => true } }
})

Q: Is there a limit to the number of logs I can store?

A: There's no hard limit in the gem itself, but consider:

  • Database storage capacity
  • Query performance (add indexes)
  • Memory usage (use pagination)
  • Implement log rotation for production

Q: Can I use ActiveModelLogger in a Rails API-only application?

A: Yes, but you'll need to ensure your models inherit from ApplicationRecord or include the concern directly:

class User < ApplicationRecord
  include ActiveModelLogger::Loggable
end

Q: How do I migrate from another logging solution?

A: Create a migration script to convert existing logs:

# Example migration from paper_trail
class MigrateFromPaperTrail
  def self.run
    PaperTrail::Version.find_each do |version|
      if version.item.respond_to?(:log)
        version.item.log(
          "Migrated from PaperTrail",
          metadata: {
            event: version.event,
            changes: version.changeset,
            created_at: version.created_at
          }
        )
      end
    end
  end
end

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake test to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

Running Tests

# Run all tests
bundle exec ruby -Itest test/test_active_model_logger.rb

# Run tests with verbose output
bundle exec ruby -Itest test/test_active_model_logger.rb -v

# Run tests with warnings
bundle exec ruby -Itest test/test_active_model_logger.rb -w

Code Quality

# Run RuboCop
bundle exec rubocop

# Auto-fix RuboCop issues
bundle exec rubocop --autocorrect

Building the Gem

# Build the gem
gem build active_model_logger.gemspec

# Install locally
bundle exec rake install

To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and the created tag, and push the .gem file to rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/EricSlick/active_model_logger. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.

License

The gem is available as open source under the terms of the MIT License.

Development Credits

While this was initially based on existing code, the conversion to a Gem and subsequent development was done using Cursor IDE with AI assistance from Claude Sonnet 4.

Summary

ActiveModelLogger is a comprehensive logging solution for Rails applications that provides:

  • 🚀 Powerful Logging: Structured logging with metadata, log chains, and atomic operations
  • 🔍 Advanced Querying: Database-agnostic JSON queries with powerful scopes
  • ⚡ High Performance: Batch operations, efficient indexing, and memory management
  • 🛠️ Developer Friendly: Easy setup, comprehensive documentation, and extensive testing
  • 🌐 Cross-Platform: Works with PostgreSQL, MySQL, and SQLite
  • 📊 Production Ready: Log rotation, cleanup, and monitoring capabilities

Key Benefits

  1. Structured Data: Store complex metadata as JSON for flexible querying
  2. Log Chains: Track related operations across multiple processes
  3. Atomic Operations: Use log blocks for consistent, error-safe logging
  4. Performance: Batch operations and optimized queries for high-volume applications
  5. Flexibility: Customizable visibility, log levels, and output formats
  6. Reliability: Comprehensive test suite and cross-version compatibility

Perfect For

  • E-commerce: Order processing, payment tracking, inventory management
  • SaaS Applications: User activity, feature usage, system monitoring
  • API Services: Request logging, error tracking, performance monitoring
  • Background Jobs: Process tracking, error handling, progress monitoring
  • Audit Trails: Compliance, security, change tracking
  • Debugging: Complex workflows, error investigation, system analysis

Code of Conduct

Everyone interacting in the ActiveModelLogger project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.