ActiveModelLogger
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
- Ruby Compatibility
- Installation
- Usage
- API Reference
- Log Chains
- Standard Output Logging
- Configuration
- Advanced Usage
- Database Schema
- Demo Project
- Performance Tips
- Troubleshooting
- FAQ
- Development
- Contributing
- License
Features
🚀 Core Logging
- Structured Logging: Store logs in a dedicated
active_model_logstable 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
- PostgreSQL: Native JSON operators (
- 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_allfor optimal performance (ActiveRecord 6.0+) - Automatic log chain caching
- Memory-efficient bulk operations
- Uses
- 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.,
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 messagevisible_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 blocklog_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 messagemetadata: 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 togetherstatus,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.)
raise PaymentError, result.
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_batchfor 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 modelloggable_id(UUID): The ID of the associated modelmessage(text): The log messagemetadata(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 (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
- Structured Data: Store complex metadata as JSON for flexible querying
- Log Chains: Track related operations across multiple processes
- Atomic Operations: Use log blocks for consistent, error-safe logging
- Performance: Batch operations and optimized queries for high-volume applications
- Flexibility: Customizable visibility, log levels, and output formats
- 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.