active_model_logger 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.md ADDED
@@ -0,0 +1,1215 @@
1
+ # ActiveModelLogger
2
+
3
+ [![Ruby Version](https://img.shields.io/badge/ruby-3.1%2B-red.svg)](https://www.ruby-lang.org/)
4
+ [![Rails Version](https://img.shields.io/badge/rails-7.1%2B-blue.svg)](https://rubyonrails.org/)
5
+ [![ActiveRecord Version](https://img.shields.io/badge/activerecord-7.1%2B-green.svg)](https://guides.rubyonrails.org/active_record_basics.html)
6
+ [![Build Status](https://img.shields.io/badge/build-passing-brightgreen.svg)](https://github.com/EricSlick/active_model_logger)
7
+ [![Gem Version](https://img.shields.io/badge/gem-0.2.1-purple.svg)](https://rubygems.org/gems/active_model_logger)
8
+
9
+ 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.
10
+
11
+ ## 🚀 Quick Start
12
+
13
+ ```ruby
14
+ # 1. Add to Gemfile
15
+ gem 'active_model_logger'
16
+
17
+ # 2. Install and generate migration
18
+ bundle install
19
+ rails generate active_model_logger:install
20
+ rails db:migrate
21
+
22
+ # 3. Include in your models
23
+ class User < ApplicationRecord
24
+ include ActiveModelLogger::Loggable
25
+ end
26
+
27
+ # 4. Start logging
28
+ user = User.find(1)
29
+ user.log("User logged in", metadata: { ip: "192.168.1.1" })
30
+
31
+ # 5. Use log blocks for atomic operations
32
+ user.log_block("Order processing") do |log|
33
+ log.info("Payment processed", amount: 99.99)
34
+ log.info("Inventory updated", items: 3)
35
+ log.info("Order completed", status: "success")
36
+ end
37
+ ```
38
+
39
+ ## 📋 Table of Contents
40
+
41
+ - [Features](#features)
42
+ - [Ruby Compatibility](#ruby-compatibility)
43
+ - [Installation](#installation)
44
+ - [Usage](#usage)
45
+ - [API Reference](#api-reference)
46
+ - [Log Chains](#log-chains)
47
+ - [Standard Output Logging](#standard-output-logging)
48
+ - [Configuration](#configuration)
49
+ - [Advanced Usage](#advanced-usage)
50
+ - [Database Schema](#database-schema)
51
+ - [Demo Project](#demo-project)
52
+ - [Performance Tips](#performance-tips)
53
+ - [Troubleshooting](#troubleshooting)
54
+ - [FAQ](#faq)
55
+ - [Development](#development)
56
+ - [Contributing](#contributing)
57
+ - [License](#license)
58
+
59
+ ## Features
60
+
61
+ ### 🚀 Core Logging
62
+ - **Structured Logging**: Store logs in a dedicated `active_model_logs` table with full metadata support
63
+ - **Log Chains**: Track related logs using shared log chains with intelligent caching
64
+ - Debug complex actions that take place over multiple processes
65
+ - Automatic chain management for synchronous operations
66
+ - Manual chain control for cross-process workflows
67
+ - **Log Blocks**: Atomic logging operations that group related logs together
68
+ - Automatic error handling and rollback
69
+ - Nested log block support
70
+ - Performance-optimized batch operations
71
+ - **Metadata Support**: Store additional structured data with each log entry
72
+ - JSONB storage for flexible data structures
73
+ - Nested key searching across complex metadata
74
+ - Type-safe data handling
75
+
76
+ ### 🔍 Advanced Querying
77
+ - **Powerful Scopes**: Built-in scopes for filtering and searching logs
78
+ - Query by log level, visibility, status, category, type
79
+ - Time-based range queries and ordering
80
+ - Deep metadata searching with nested key support
81
+ - **Database-Agnostic JSON Queries**: Works seamlessly across different databases
82
+ - PostgreSQL: Native JSON operators (`->`, `->>`)
83
+ - MySQL: JSON_EXTRACT and JSON_SEARCH functions
84
+ - SQLite: LIKE-based pattern matching
85
+ - **Performance Optimized**: Efficient querying with proper indexing support
86
+
87
+ ### 🎯 User Experience
88
+ - **Visibility Levels**: Control who can see different log entries
89
+ - **Dual Output**: Logs are stored in database AND sent to standard output
90
+ - Formatted console output with timestamps and metadata
91
+ - Configurable logging levels and custom loggers
92
+ - Per-model stdout logging control
93
+ - **Rails Integration**: Easy setup with Rails generators
94
+ - **Polymorphic Associations**: Create logs on any ActiveRecord model
95
+
96
+ ### ⚡ Performance & Reliability
97
+ - **Batch Logging**: Efficiently create multiple log entries at once
98
+ - Uses `insert_all` for optimal performance (ActiveRecord 6.0+)
99
+ - Automatic log chain caching
100
+ - Memory-efficient bulk operations
101
+ - **Database Agnostic**: Works with PostgreSQL, MySQL, and SQLite
102
+ - **Ruby Compatibility**: Tested across multiple Ruby versions (3.1+)
103
+ - Comprehensive test suite across Ruby 3.1.4, 3.2.4, 3.3.1, 3.3.6
104
+ - ActiveRecord compatibility testing (7.1+, 8.0+)
105
+ - **Error Handling**: Robust error handling with graceful degradation
106
+ - **Log Cleanup**: Built-in log rotation and archival support
107
+
108
+ ### 🛠️ Developer Experience
109
+ - **Comprehensive Testing**: Full test suite with 37 tests and 183 assertions
110
+ - **Code Quality**: RuboCop compliance and clean code standards
111
+ - **Documentation**: Extensive documentation with examples and API reference
112
+ - **Demo Project**: Complete Rails demo application showcasing all features
113
+ - **Interactive Dashboard**: Real-time log viewing and management
114
+
115
+ ## Ruby Compatibility
116
+
117
+ ActiveModelLogger is tested and compatible with:
118
+
119
+ | Ruby Version | Status | Notes | Tested With |
120
+ |--------------|--------|-------|-------------|
121
+ | 2.7.x | ❌ Not Supported | Compilation issues on Apple Silicon | - |
122
+ | 3.0.x | ❌ Not Supported | Compilation issues on Apple Silicon | - |
123
+ | 3.1.4 | ✅ Supported | Security maintenance | ✅ |
124
+ | 3.2.4 | ✅ Supported | Stable, recommended | ✅ |
125
+ | 3.3.1 | ✅ Supported | Current stable | ✅ |
126
+ | 3.3.6 | ✅ Supported | Latest patch version | ✅ |
127
+ | 3.4.0 | ✅ Supported | Latest features | ✅ |
128
+
129
+ ### ActiveRecord Compatibility
130
+
131
+ | ActiveRecord Version | Status | Notes | Tested With |
132
+ |---------------------|--------|-------|-------------|
133
+ | 7.1.x | ✅ Supported | LTS version, recommended | ✅ |
134
+ | 8.0.x | ✅ Supported | Latest stable | ✅ |
135
+ | 8.1.x | ✅ Supported | Latest features | ✅ |
136
+
137
+ ### Database Compatibility
138
+
139
+ | Database | Status | JSON Support | Notes |
140
+ |----------|--------|--------------|-------|
141
+ | PostgreSQL | ✅ Fully Supported | Native JSONB | Recommended for production |
142
+ | MySQL 5.7+ | ✅ Supported | JSON functions | Requires MySQL 5.7+ |
143
+ | SQLite 3.38+ | ✅ Supported | JSON1 extension | Good for development |
144
+
145
+ ### Testing Ruby Compatibility
146
+
147
+ We provide comprehensive testing scripts for Ruby compatibility:
148
+
149
+ ```bash
150
+ # Quick compatibility check
151
+ ./bin/check_ruby_versions
152
+
153
+ # Test commonly used Ruby versions
154
+ ./bin/test_ruby_versions
155
+
156
+ # Test specific Ruby version
157
+ ./bin/test_specific_ruby 3.2.4
158
+
159
+ # Test with specific ActiveRecord version
160
+ ./bin/test_specific_ruby 3.3.1 "~> 8.0.0"
161
+ ```
162
+
163
+ For detailed information, see [Ruby Compatibility Testing](bin/README_RUBY_COMPATIBILITY.md).
164
+
165
+ ## Installation
166
+
167
+ ### Prerequisites
168
+
169
+ - Ruby 3.1+ (tested with 3.1.4, 3.2.4, 3.3.1, 3.3.6)
170
+ - Rails 7.1+ (tested with 7.1, 8.0)
171
+ - ActiveRecord 7.1+ (tested with 7.1, 8.0)
172
+ - PostgreSQL, MySQL, or SQLite database
173
+
174
+ ### Step 1: Add to Gemfile
175
+
176
+ Add this line to your application's Gemfile:
177
+
178
+ ```ruby
179
+ gem 'active_model_logger'
180
+ ```
181
+
182
+ ### Step 2: Install Dependencies
183
+
184
+ ```bash
185
+ bundle install
186
+ ```
187
+
188
+ ### Step 3: Generate Migration
189
+
190
+ ```bash
191
+ rails generate active_model_logger:install
192
+ ```
193
+
194
+ This creates a migration for the `active_model_logs` table with the following features:
195
+ - UUID primary key
196
+ - Polymorphic associations to any ActiveRecord model
197
+ - JSONB metadata field for flexible data storage
198
+ - Proper indexing for performance
199
+
200
+ ### Step 4: Run Migration
201
+
202
+ ```bash
203
+ rails db:migrate
204
+ ```
205
+
206
+ ### Step 5: Include in Models
207
+
208
+ ```ruby
209
+ # Individual models
210
+ class User < ApplicationRecord
211
+ include ActiveModelLogger::Loggable
212
+ end
213
+
214
+ class Order < ApplicationRecord
215
+ include ActiveModelLogger::Loggable
216
+ end
217
+
218
+ # Or enable for all models
219
+ class ApplicationRecord < ActiveRecord::Base
220
+ include ActiveModelLogger::Loggable
221
+ end
222
+ ```
223
+
224
+ ### Alternative Installation
225
+
226
+ You can also install the gem directly:
227
+
228
+ ```bash
229
+ gem install active_model_logger
230
+ ```
231
+
232
+ ### Verification
233
+
234
+ Test your installation:
235
+
236
+ ```ruby
237
+ # In Rails console
238
+ user = User.create!(name: "Test User")
239
+ user.log("Installation test successful")
240
+ puts user.logs.count # Should output: 1
241
+ ```
242
+
243
+ ## Usage
244
+
245
+ ### Basic Logging
246
+
247
+ The simplest way to start logging is with the `log` method:
248
+
249
+ ```ruby
250
+ user = User.find(1)
251
+
252
+ # Basic logging
253
+ user.log("User logged in")
254
+ user.log("Payment processed", log_level: "info")
255
+
256
+ # Logging with metadata
257
+ user.log("Order created",
258
+ log_level: "info",
259
+ metadata: {
260
+ status: "success",
261
+ category: "order",
262
+ order_id: 123,
263
+ amount: 99.99
264
+ })
265
+
266
+ # Logging with custom visibility
267
+ user.log("Admin action performed",
268
+ visible_to: "admin",
269
+ metadata: { action: "user_suspended" })
270
+ ```
271
+
272
+ ### Log Blocks (Atomic Operations)
273
+
274
+ Use `log_block` for grouping related logs together with automatic error handling:
275
+
276
+ ```ruby
277
+ # Basic log block
278
+ user.log_block("Order processing") do |log|
279
+ log.info("Payment processed", amount: 99.99)
280
+ log.info("Inventory updated", items: 3)
281
+ log.info("Order completed", status: "success")
282
+ end
283
+
284
+ # Log block with custom chain
285
+ user.log_block("Order processing", log_chain: "order_123") do |log|
286
+ log.info("Step 1: Payment processed", amount: 99.99)
287
+ log.info("Step 2: Inventory updated", items: 3)
288
+ log.info("Step 3: Order completed", status: "success")
289
+ end
290
+
291
+ # Log block with error handling
292
+ user.log_block("Risky operation") do |log|
293
+ log.info("Starting risky operation")
294
+ # If an exception occurs here, it will be logged and re-raised
295
+ risky_operation!
296
+ log.info("Risky operation completed successfully")
297
+ end
298
+ ```
299
+
300
+ ### Log Chains
301
+
302
+ Log chains help you track related operations across multiple processes:
303
+
304
+ ```ruby
305
+ user = User.find(1)
306
+
307
+ # Automatic log chain caching
308
+ user.log("Process started", log_chain: "order_processing_123")
309
+ user.log("Step 1 completed") # Uses cached log chain
310
+ user.log("Step 2 completed") # Uses cached log chain
311
+
312
+ # Explicit log_chain breaks the cache and starts a new chain
313
+ user.log("New process", log_chain: "process_456") # Breaks cache
314
+ user.log("Step A") # Uses new cached log chain
315
+ user.log("Step B") # Uses new cached log chain
316
+
317
+ # Cross-process log chains (for background jobs)
318
+ # Pass the log_chain between processes
319
+ log_chain = user.log_chain
320
+ SomeBackgroundJob.perform_later(user.id, log_chain)
321
+
322
+ # In the background job
323
+ class SomeBackgroundJob < ApplicationJob
324
+ def perform(user_id, log_chain)
325
+ user = User.find(user_id)
326
+ user.log("Background job started", log_chain: log_chain)
327
+ # ... do work ...
328
+ user.log("Background job completed", log_chain: log_chain)
329
+ end
330
+ end
331
+ ```
332
+
333
+ ### Batch Logging
334
+
335
+ For efficient bulk operations, use `log_batch`:
336
+
337
+ ```ruby
338
+ # Basic batch logging
339
+ user.log_batch([
340
+ { message: "Step 1 completed", status: "success" },
341
+ { message: "Step 2 completed", status: "success" },
342
+ { message: "Process finished", status: "complete" }
343
+ ])
344
+
345
+ # Batch logging with custom log chain
346
+ user.log_batch([
347
+ { message: "Step 1 completed", status: "success", log_chain: "process_123" },
348
+ { message: "Step 2 completed", status: "success" }, # Uses cached log_chain
349
+ { message: "Process finished", status: "complete" } # Uses cached log_chain
350
+ ])
351
+
352
+ # Batch logging with metadata
353
+ user.log_batch([
354
+ {
355
+ message: "Payment processed",
356
+ status: "success",
357
+ category: "payment",
358
+ amount: 99.99,
359
+ payment_method: "credit_card"
360
+ },
361
+ {
362
+ message: "Inventory updated",
363
+ status: "success",
364
+ category: "inventory",
365
+ items_updated: 3
366
+ }
367
+ ])
368
+ ```
369
+
370
+ ### Advanced Logging Patterns
371
+
372
+ ```ruby
373
+ # Conditional logging
374
+ user.log("User action", metadata: { action: "login" }) if user.persisted?
375
+
376
+ # Logging with validation
377
+ user.log("Data validation failed",
378
+ log_level: "error",
379
+ metadata: {
380
+ errors: user.errors.full_messages,
381
+ field: "email"
382
+ }) unless user.valid?
383
+
384
+ # Logging with callbacks
385
+ class User < ApplicationRecord
386
+ include ActiveModelLogger::Loggable
387
+
388
+ after_create :log_user_creation
389
+ after_update :log_user_update
390
+
391
+ private
392
+
393
+ def log_user_creation
394
+ log("User created", metadata: {
395
+ email: email,
396
+ created_at: created_at
397
+ })
398
+ end
399
+
400
+ def log_user_update
401
+ log("User updated", metadata: {
402
+ changes: saved_changes,
403
+ updated_at: updated_at
404
+ })
405
+ end
406
+ end
407
+ ```
408
+
409
+ ## API Reference
410
+
411
+ ### Loggable Concern
412
+
413
+ The `ActiveModelLogger::Loggable` concern provides the following methods:
414
+
415
+ #### `log(message, options = {})`
416
+
417
+ Creates a new log entry for the model.
418
+
419
+ **Parameters:**
420
+ - `message` (String): The log message
421
+ - `visible_to` (String): Who can see this log (default: "admin")
422
+ - `log_level` (String): Log level (default: "info")
423
+ - `log_chain` (String): Custom log chain for grouping related logs (optional)
424
+ - `metadata` (Hash): Additional structured data
425
+
426
+ **Example:**
427
+ ```ruby
428
+ user.log("Action completed",
429
+ log_level: "info",
430
+ visible_to: "account_admin",
431
+ log_chain: "payment_123",
432
+ metadata: { status: "success", category: "payment" })
433
+ ```
434
+
435
+
436
+ #### `logs(log_chain: nil)`
437
+
438
+ Returns all logs for the model, optionally filtered by log chain.
439
+
440
+ #### `log_chain`
441
+
442
+ Returns the current log chain (UUID by default). Used to group related logs.
443
+
444
+ #### `log_block(title, options = {}, &block)`
445
+
446
+ Creates a log block for atomic operations with automatic error handling.
447
+
448
+ **Parameters:**
449
+ - `title` (String): Title for the log block
450
+ - `log_chain` (String): Custom log chain for the block (optional)
451
+ - `visible_to` (String): Visibility level for logs in the block (optional)
452
+ - `log_level` (String): Default log level for logs in the block (optional)
453
+ - `&block`: Block containing log operations
454
+
455
+ **Example:**
456
+ ```ruby
457
+ user.log_block("Order processing") do |log|
458
+ log.info("Payment processed", amount: 99.99)
459
+ log.warn("Low inventory", items_remaining: 2)
460
+ log.error("Payment failed", error: "insufficient_funds")
461
+ end
462
+
463
+ # With custom options
464
+ user.log_block("Admin operation",
465
+ log_chain: "admin_123",
466
+ visible_to: "admin") do |log|
467
+ log.info("Admin action started")
468
+ log.info("Admin action completed")
469
+ end
470
+ ```
471
+
472
+ #### `log_batch(entries)`
473
+
474
+ Creates multiple log entries efficiently in a single database operation.
475
+
476
+ **Parameters:**
477
+ - `entries` (Array): Array of log entry hashes
478
+
479
+ **Example:**
480
+ ```ruby
481
+ user.log_batch([
482
+ { message: "Step 1 completed", status: "success", log_chain: "process_123" },
483
+ { message: "Step 2 completed", status: "success" }, # Uses cached log_chain
484
+ { message: "Process finished", status: "complete" } # Uses cached log_chain
485
+ ])
486
+ ```
487
+
488
+ ### Log Model Scopes
489
+
490
+ The `ActiveModelLogger::Log` model provides several scopes for querying:
491
+
492
+ ```ruby
493
+ # Filter by log level
494
+ ActiveModelLogger::Log.by_level("error")
495
+ ActiveModelLogger::Log.info_logs
496
+ ActiveModelLogger::Log.error_logs
497
+
498
+ # Filter by visibility
499
+ ActiveModelLogger::Log.by_visibility("admin")
500
+
501
+ # Filter by log chain
502
+ ActiveModelLogger::Log.by_log_chain("process_123")
503
+
504
+ # Filter by status
505
+ ActiveModelLogger::Log.by_status("success")
506
+
507
+ # Filter by category
508
+ ActiveModelLogger::Log.by_category("payment")
509
+
510
+ # Filter by type
511
+ ActiveModelLogger::Log.by_type("user_action")
512
+
513
+ # Filter logs with data
514
+ ActiveModelLogger::Log.with_data # All logs with data
515
+ ActiveModelLogger::Log.with_data({ user_id: 123 }) # Logs with specific data
516
+
517
+ # Filter logs by keys (anywhere in metadata, including nested)
518
+ ActiveModelLogger::Log.with_keys("user_id") # Logs containing user_id key
519
+ ActiveModelLogger::Log.with_keys("user_id", "order_id") # Logs containing both keys
520
+ ActiveModelLogger::Log.with_keys("email") # Finds email key anywhere (root, data, nested)
521
+ ActiveModelLogger::Log.with_keys("enabled") # Finds enabled key in deeply nested structures
522
+
523
+ # Examples of nested key searching:
524
+ # metadata: {"settings": {"notifications": {"email": true, "sms": false}}}
525
+ # metadata: {"user": {"profile": {"contact": {"email": "user@example.com"}}}}
526
+ # metadata: {"config": {"cache": {"redis": {"enabled": true}}}}
527
+ # All of these would be found by with_keys("email") or with_keys("enabled")
528
+
529
+ # Ordering
530
+ ActiveModelLogger::Log.oldest(5) # 5 oldest logs
531
+ ActiveModelLogger::Log.newest(5) # 5 newest logs
532
+
533
+ # Time-based filtering
534
+ ActiveModelLogger::Log.in_range(1.hour.ago, Time.current)
535
+ ActiveModelLogger::Log.newest(10) # Last 10 logs
536
+ ```
537
+
538
+ ### Log Model
539
+
540
+ The `ActiveModelLogger::Log` model represents individual log entries.
541
+
542
+ **Attributes:**
543
+ - `message`: The log message
544
+ - `metadata`: JSONB field for additional data including:
545
+ - `log_level`: The log level (info, warn, error, etc.) (default: "info")
546
+ - `visible_to`: Who can see this log (default: "admin")
547
+ - `log_chain`: Groups related logs together
548
+ - `status`, `type`, `category`, `title`, `data`: Additional metadata fields
549
+
550
+ ## Log Chains
551
+
552
+ 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:
553
+
554
+ ### Automatic Log Chain Caching
555
+
556
+ When you don't provide an explicit `log_chain`, the gem automatically uses the most recent log's chain or generates a new UUID:
557
+
558
+ ```ruby
559
+ # First log generates and caches a UUID
560
+ user.log("Process started") # Uses: "abc-123-def"
561
+
562
+ # Subsequent logs use the cached chain
563
+ user.log("Step 1 completed") # Uses: "abc-123-def" (cached)
564
+ user.log("Step 2 completed") # Uses: "abc-123-def" (cached)
565
+
566
+ # Explicit log_chain breaks the cache
567
+ user.log("New process", log_chain: "process_456") # Uses: "process_456"
568
+
569
+ # Next logs use the new cached chain
570
+ user.log("Step 1") # Uses: "process_456" (cached)
571
+ user.log("Step 2") # Uses: "process_456" (cached)
572
+ ```
573
+
574
+ ### Manual Log Chain Management
575
+
576
+ You can also explicitly manage log chains:
577
+
578
+ ```ruby
579
+ # All logs will share the same log_chain
580
+ user.log("Process started", log_chain: "order_processing_123")
581
+ user.log("Step 1 completed") # Uses cached "order_processing_123"
582
+ user.log("Step 2 completed") # Uses cached "order_processing_123"
583
+ user.log("Process finished") # Uses cached "order_processing_123"
584
+
585
+ # Get all logs in this chain
586
+ user.logs(log_chain: "order_processing_123")
587
+ # or
588
+ user.logs(log_chain: user.log_chain)
589
+ ```
590
+
591
+ ### Use Cases
592
+
593
+ Log chains are perfect for:
594
+ - **Process Tracking**: Track a multi-step process through its lifecycle
595
+ - **Error Investigation**: Group related error logs together
596
+ - **Audit Trails**: Maintain a complete audit trail for specific actions
597
+ - **Debugging**: Follow a request through multiple services
598
+ - **Batch Operations**: Track the progress of bulk operations
599
+
600
+ ## Standard Output Logging
601
+
602
+ 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.
603
+
604
+ ### Output Format
605
+
606
+ Logs sent to stdout follow this format:
607
+ ```
608
+ [2025-09-05 20:58:15] INFO User#123 [chain:abc-123-def] - User logged in (status=success, category=authentication)
609
+ [2025-09-05 20:58:16] WARN Order#456 [chain:order-789] - Payment processing started (amount=99.99)
610
+ [2025-09-05 20:58:17] ERROR User#123 [chain:abc-123-def] - Payment failed (error=insufficient_funds)
611
+ ```
612
+
613
+ ### Configuration
614
+
615
+ You can customize stdout logging behavior:
616
+
617
+ ```ruby
618
+ class User < ApplicationRecord
619
+ include ActiveModelLogger::Loggable
620
+
621
+ # Disable stdout logging
622
+ configure_loggable(stdout_logging: false)
623
+
624
+ # Use custom logger
625
+ configure_loggable(
626
+ stdout_logger: Logger.new("log/application.log"),
627
+ stdout_logging: true
628
+ )
629
+ end
630
+ ```
631
+
632
+ ### Disabling Stdout Logging
633
+
634
+ To disable stdout logging entirely:
635
+
636
+ ```ruby
637
+ # Global configuration
638
+ ActiveModelLogger::Loggable.configure_loggable(stdout_logging: false)
639
+
640
+ # Per-model configuration
641
+ class User < ApplicationRecord
642
+ include ActiveModelLogger::Loggable
643
+ configure_loggable(stdout_logging: false)
644
+ end
645
+ ```
646
+
647
+ ## Configuration
648
+
649
+ You can configure the default behavior of the Loggable concern:
650
+
651
+ ```ruby
652
+ class User < ApplicationRecord
653
+ include ActiveModelLogger::Loggable
654
+
655
+ # Configure default values
656
+ configure_loggable(
657
+ default_visible_to: "user",
658
+ default_log_level: "debug",
659
+ auto_log_chain: true,
660
+ log_chain_method: :log_chain,
661
+ stdout_logging: true,
662
+ stdout_logger: nil
663
+ )
664
+ end
665
+ ```
666
+
667
+ **Configuration Options:**
668
+ - `default_visible_to`: Default visibility level for logs (default: "admin")
669
+ - `default_log_level`: Default log level for logs (default: "info")
670
+ - `auto_log_chain`: Automatically generate log chains (default: true)
671
+ - `log_chain_method`: Method to call for log chain generation (default: :log_chain)
672
+ - `stdout_logging`: Enable logging to standard output (default: true)
673
+ - `stdout_logger`: Custom logger instance for stdout (default: Logger.new(STDOUT))
674
+
675
+ ## Use Cases
676
+
677
+ ### E-commerce Applications
678
+
679
+ ```ruby
680
+ class Order < ApplicationRecord
681
+ include ActiveModelLogger::Loggable
682
+
683
+ def process_payment!
684
+ log_block("Payment Processing") do |log|
685
+ log.info("Payment initiated", amount: total_amount, method: payment_method)
686
+
687
+ result = PaymentGateway.charge(total_amount)
688
+
689
+ if result.success?
690
+ log.info("Payment successful", transaction_id: result.id)
691
+ update!(status: 'paid')
692
+ log.info("Order status updated", status: 'paid')
693
+ else
694
+ log.error("Payment failed", error: result.error_message)
695
+ raise PaymentError, result.error_message
696
+ end
697
+ end
698
+ end
699
+ end
700
+ ```
701
+
702
+ ### User Authentication & Authorization
703
+
704
+ ```ruby
705
+ class User < ApplicationRecord
706
+ include ActiveModelLogger::Loggable
707
+
708
+ def log_login!(ip_address, user_agent)
709
+ log("User logged in",
710
+ metadata: {
711
+ ip_address: ip_address,
712
+ user_agent: user_agent,
713
+ login_time: Time.current
714
+ })
715
+ end
716
+
717
+ def log_failed_login!(ip_address, reason)
718
+ log("Failed login attempt",
719
+ log_level: "warn",
720
+ metadata: {
721
+ ip_address: ip_address,
722
+ reason: reason,
723
+ attempt_time: Time.current
724
+ })
725
+ end
726
+ end
727
+ ```
728
+
729
+ ### Background Job Processing
730
+
731
+ ```ruby
732
+ class DataProcessingJob < ApplicationJob
733
+ def perform(user_id, log_chain)
734
+ user = User.find(user_id)
735
+
736
+ user.log_block("Data Processing", log_chain: log_chain) do |log|
737
+ log.info("Job started", job_id: job_id)
738
+
739
+ # Process data
740
+ processed_count = process_user_data(user)
741
+ log.info("Data processed", count: processed_count)
742
+
743
+ # Send notifications
744
+ NotificationService.send_summary(user, processed_count)
745
+ log.info("Notifications sent")
746
+
747
+ log.info("Job completed successfully")
748
+ end
749
+ end
750
+ end
751
+ ```
752
+
753
+ ### API Request Logging
754
+
755
+ ```ruby
756
+ class ApiController < ApplicationController
757
+ before_action :log_request
758
+
759
+ private
760
+
761
+ def log_request
762
+ current_user&.log("API Request",
763
+ metadata: {
764
+ endpoint: request.path,
765
+ method: request.method,
766
+ params: params.except(:controller, :action),
767
+ ip: request.remote_ip
768
+ })
769
+ end
770
+ end
771
+ ```
772
+
773
+ ### Audit Trails
774
+
775
+ ```ruby
776
+ class Document < ApplicationRecord
777
+ include ActiveModelLogger::Loggable
778
+
779
+ after_update :log_changes
780
+
781
+ private
782
+
783
+ def log_changes
784
+ if saved_changes.any?
785
+ log("Document updated",
786
+ metadata: {
787
+ changes: saved_changes,
788
+ updated_by: Current.user&.id,
789
+ updated_at: Time.current
790
+ })
791
+ end
792
+ end
793
+ end
794
+ ```
795
+
796
+ ## Advanced Usage
797
+
798
+ ### Custom Log Chain Generation
799
+
800
+ You can override the `log_chain` method to provide custom chain generation:
801
+
802
+ ```ruby
803
+ class Order < ApplicationRecord
804
+ include ActiveModelLogger::Loggable
805
+
806
+ private
807
+
808
+ def log_chain
809
+ "order_#{id}_#{Time.current.to_i}"
810
+ end
811
+ end
812
+ ```
813
+
814
+ ### Querying and Filtering
815
+
816
+ Use the built-in scopes for efficient querying:
817
+
818
+ ```ruby
819
+ # Find all error logs for a user
820
+ user.active_model_logs.error_logs
821
+
822
+ # Find logs in a specific time range
823
+ user.active_model_logs.in_range(1.day.ago, Time.current)
824
+
825
+ # Find logs with specific metadata
826
+ user.active_model_logs.by_status("success")
827
+ user.active_model_logs.by_category("payment")
828
+
829
+ # Find logs with specific data
830
+ user.active_model_logs.with_data({ user_id: 123 })
831
+ user.active_model_logs.with_data({ order_id: 456, status: "completed" })
832
+
833
+ # Find logs containing specific keys (anywhere in metadata)
834
+ user.active_model_logs.with_keys("user_id")
835
+ user.active_model_logs.with_keys("user_id", "order_id")
836
+ user.active_model_logs.with_keys("email") # Finds email in any nested structure
837
+ user.active_model_logs.with_keys("enabled") # Finds enabled in deeply nested config
838
+
839
+ # Find logs in a specific chain
840
+ user.active_model_logs.by_log_chain("order_processing_123")
841
+
842
+ # Order logs by creation time
843
+ user.active_model_logs.oldest(5) # 5 oldest logs
844
+ user.active_model_logs.newest(5) # 5 newest logs
845
+
846
+ # Complex queries
847
+ ActiveModelLogger::Log
848
+ .by_level("error")
849
+ .by_visibility("admin")
850
+ .in_range(1.hour.ago, Time.current)
851
+ .with_data
852
+ ```
853
+
854
+ ### Performance Considerations
855
+
856
+ - **Batch Logging**: Use `log_batch` for creating multiple logs efficiently
857
+ - **Indexing**: Consider adding database indexes on frequently queried metadata fields
858
+ - **Cleanup**: Implement log rotation or archival for high-volume applications
859
+ - **Scoping**: Use scopes instead of loading all logs into memory
860
+
861
+ ## Database Schema
862
+
863
+ The `active_model_logs` table includes:
864
+
865
+ - `id` (UUID, primary key)
866
+ - `loggable_type` (string): The type of the associated model
867
+ - `loggable_id` (UUID): The ID of the associated model
868
+ - `message` (text): The log message
869
+ - `metadata` (jsonb): Additional structured data including:
870
+ - `log_level` (string): Log level (default: "info")
871
+ - `visible_to` (string): Visibility level (default: "admin")
872
+ - `status`, `category`, `type`, `title`, `log_chain`, `data`: Additional metadata fields
873
+ - `created_at`, `updated_at` (datetime): Timestamps
874
+
875
+ ## Demo Project
876
+
877
+ A complete Rails demo application is included to showcase the gem's features:
878
+
879
+ ```bash
880
+ # Navigate to the demo project
881
+ cd active_model_logger_demo
882
+
883
+ # Install dependencies
884
+ bundle install
885
+
886
+ # Run migrations
887
+ rails db:migrate
888
+
889
+ # Start the server
890
+ rails server
891
+
892
+ # Visit http://localhost:3000 to see the demo
893
+ ```
894
+
895
+ The demo includes:
896
+ - **Interactive Dashboard**: View logs in real-time
897
+ - **Code Examples**: Live examples of all gem features
898
+ - **Log Chain Demo**: See the caching behavior in action
899
+ - **Batch Logging**: Examples of efficient bulk operations
900
+
901
+ ## Performance Tips
902
+
903
+ ### Database Optimization
904
+
905
+ ```ruby
906
+ # Add indexes for frequently queried fields
907
+ class AddIndexesToActiveModelLogs < ActiveRecord::Migration[7.1]
908
+ def change
909
+ add_index :active_model_logs, [:loggable_type, :loggable_id]
910
+ add_index :active_model_logs, :created_at
911
+ add_index :active_model_logs, :log_chain
912
+ add_index :active_model_logs, :visible_to
913
+
914
+ # For PostgreSQL JSONB queries
915
+ add_index :active_model_logs, :metadata, using: :gin if connection.adapter_name == 'PostgreSQL'
916
+ end
917
+ end
918
+ ```
919
+
920
+ ### Efficient Querying
921
+
922
+ ```ruby
923
+ # Use scopes instead of loading all logs
924
+ user.logs.limit(10) # Good
925
+ user.logs.to_a.first(10) # Bad - loads all logs
926
+
927
+ # Use specific scopes for better performance
928
+ user.logs.error_logs.recent(5) # Good
929
+ user.logs.select { |log| log.log_level == 'error' }.first(5) # Bad
930
+
931
+ # Use batch operations for bulk inserts
932
+ user.log_batch(log_entries) # Good - single DB operation
933
+ log_entries.each { |entry| user.log(entry) } # Bad - multiple DB operations
934
+ ```
935
+
936
+ ### Memory Management
937
+
938
+ ```ruby
939
+ # For large datasets, use pagination
940
+ user.logs.page(1).per(50)
941
+
942
+ # Use find_each for large result sets
943
+ ActiveModelLogger::Log.find_each(batch_size: 1000) do |log|
944
+ # Process log
945
+ end
946
+
947
+ # Clean up old logs regularly
948
+ ActiveModelLogger::Log.where('created_at < ?', 1.year.ago).delete_all
949
+ ```
950
+
951
+ ## Troubleshooting
952
+
953
+ ### Common Issues
954
+
955
+ #### 1. Migration Errors
956
+
957
+ **Problem**: Migration fails with database-specific errors
958
+
959
+ **Solution**:
960
+ ```ruby
961
+ # Check your database adapter
962
+ Rails.application.config.database_configuration[Rails.env]['adapter']
963
+
964
+ # For SQLite, ensure you have the json1 extension
965
+ # For MySQL, ensure version 5.7+ for JSON support
966
+ # For PostgreSQL, JSONB is supported by default
967
+ ```
968
+
969
+ #### 2. Log Chain Not Working
970
+
971
+ **Problem**: Log chains aren't being shared between logs
972
+
973
+ **Solution**:
974
+ ```ruby
975
+ # Ensure you're not breaking the chain accidentally
976
+ user.log("First log", log_chain: "chain_123")
977
+ user.log("Second log") # This will use the cached chain
978
+
979
+ # If you need a new chain, explicitly set it
980
+ user.log("New process", log_chain: "new_chain_456")
981
+ ```
982
+
983
+ #### 3. Stdout Logging Not Appearing
984
+
985
+ **Problem**: Logs aren't showing in console output
986
+
987
+ **Solution**:
988
+ ```ruby
989
+ # Check stdout logging configuration
990
+ class User < ApplicationRecord
991
+ include ActiveModelLogger::Loggable
992
+ configure_loggable(stdout_logging: true) # Ensure it's enabled
993
+ end
994
+
995
+ # Check if you have a custom logger
996
+ configure_loggable(stdout_logger: Logger.new(STDOUT))
997
+ ```
998
+
999
+ #### 4. Performance Issues
1000
+
1001
+ **Problem**: Slow queries or high memory usage
1002
+
1003
+ **Solution**:
1004
+ ```ruby
1005
+ # Add database indexes
1006
+ add_index :active_model_logs, [:loggable_type, :loggable_id, :created_at]
1007
+
1008
+ # Use batch operations
1009
+ user.log_batch(entries) # Instead of individual logs
1010
+
1011
+ # Implement log cleanup
1012
+ ActiveModelLogger::Log.where('created_at < ?', 6.months.ago).delete_all
1013
+ ```
1014
+
1015
+ ### Debug Mode
1016
+
1017
+ Enable debug logging to troubleshoot issues:
1018
+
1019
+ ```ruby
1020
+ # In development environment
1021
+ Rails.logger.level = :debug
1022
+
1023
+ # Check log chain behavior
1024
+ user.log("Debug test")
1025
+ puts "Current log chain: #{user.log_chain}"
1026
+ puts "Recent logs: #{user.logs.recent(3).pluck(:message)}"
1027
+ ```
1028
+
1029
+ ## FAQ
1030
+
1031
+ ### Q: Can I use ActiveModelLogger with non-ActiveRecord models?
1032
+
1033
+ **A**: No, ActiveModelLogger is specifically designed for ActiveRecord models. It relies on ActiveRecord's polymorphic associations and database features.
1034
+
1035
+ ### Q: How do I handle log chains across different servers/processes?
1036
+
1037
+ **A**: Pass the log chain explicitly between processes:
1038
+
1039
+ ```ruby
1040
+ # In your main process
1041
+ log_chain = user.log_chain
1042
+ BackgroundJob.perform_later(user.id, log_chain)
1043
+
1044
+ # In your background job
1045
+ class BackgroundJob < ApplicationJob
1046
+ def perform(user_id, log_chain)
1047
+ user = User.find(user_id)
1048
+ user.log("Job started", log_chain: log_chain)
1049
+ # ... do work ...
1050
+ user.log("Job completed", log_chain: log_chain)
1051
+ end
1052
+ end
1053
+ ```
1054
+
1055
+ ### Q: Can I customize the log message format?
1056
+
1057
+ **A**: Yes, you can customize the stdout format by providing a custom logger:
1058
+
1059
+ ```ruby
1060
+ class CustomLogger < Logger
1061
+ def format_message(severity, datetime, progname, msg)
1062
+ "[#{datetime}] #{severity} #{msg}\n"
1063
+ end
1064
+ end
1065
+
1066
+ configure_loggable(stdout_logger: CustomLogger.new(STDOUT))
1067
+ ```
1068
+
1069
+ ### Q: How do I query logs by complex metadata?
1070
+
1071
+ **A**: Use the built-in scopes for common queries, or write custom scopes:
1072
+
1073
+ ```ruby
1074
+ # Built-in scopes
1075
+ user.logs.with_data({ user_id: 123 })
1076
+ user.logs.with_keys("email", "phone")
1077
+
1078
+ # Custom scope
1079
+ class ActiveModelLogger::Log
1080
+ scope :by_complex_metadata, ->(conditions) {
1081
+ where("metadata @> ?", conditions.to_json)
1082
+ }
1083
+ end
1084
+
1085
+ # Usage
1086
+ user.logs.by_complex_metadata({
1087
+ "user" => { "profile" => { "verified" => true } }
1088
+ })
1089
+ ```
1090
+
1091
+ ### Q: Is there a limit to the number of logs I can store?
1092
+
1093
+ **A**: There's no hard limit in the gem itself, but consider:
1094
+ - Database storage capacity
1095
+ - Query performance (add indexes)
1096
+ - Memory usage (use pagination)
1097
+ - Implement log rotation for production
1098
+
1099
+ ### Q: Can I use ActiveModelLogger in a Rails API-only application?
1100
+
1101
+ **A**: Yes, but you'll need to ensure your models inherit from `ApplicationRecord` or include the concern directly:
1102
+
1103
+ ```ruby
1104
+ class User < ApplicationRecord
1105
+ include ActiveModelLogger::Loggable
1106
+ end
1107
+ ```
1108
+
1109
+ ### Q: How do I migrate from another logging solution?
1110
+
1111
+ **A**: Create a migration script to convert existing logs:
1112
+
1113
+ ```ruby
1114
+ # Example migration from paper_trail
1115
+ class MigrateFromPaperTrail
1116
+ def self.run
1117
+ PaperTrail::Version.find_each do |version|
1118
+ if version.item.respond_to?(:log)
1119
+ version.item.log(
1120
+ "Migrated from PaperTrail",
1121
+ metadata: {
1122
+ event: version.event,
1123
+ changes: version.changeset,
1124
+ created_at: version.created_at
1125
+ }
1126
+ )
1127
+ end
1128
+ end
1129
+ end
1130
+ end
1131
+ ```
1132
+
1133
+ ## Development
1134
+
1135
+ 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.
1136
+
1137
+ ### Running Tests
1138
+
1139
+ ```bash
1140
+ # Run all tests
1141
+ bundle exec ruby -Itest test/test_active_model_logger.rb
1142
+
1143
+ # Run tests with verbose output
1144
+ bundle exec ruby -Itest test/test_active_model_logger.rb -v
1145
+
1146
+ # Run tests with warnings
1147
+ bundle exec ruby -Itest test/test_active_model_logger.rb -w
1148
+ ```
1149
+
1150
+ ### Code Quality
1151
+
1152
+ ```bash
1153
+ # Run RuboCop
1154
+ bundle exec rubocop
1155
+
1156
+ # Auto-fix RuboCop issues
1157
+ bundle exec rubocop --autocorrect
1158
+ ```
1159
+
1160
+ ### Building the Gem
1161
+
1162
+ ```bash
1163
+ # Build the gem
1164
+ gem build active_model_logger.gemspec
1165
+
1166
+ # Install locally
1167
+ bundle exec rake install
1168
+ ```
1169
+
1170
+ 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](https://rubygems.org).
1171
+
1172
+ ## Contributing
1173
+
1174
+ 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](https://github.com/EricSlick/active_model_logger/blob/main/CODE_OF_CONDUCT.md).
1175
+
1176
+ ## License
1177
+
1178
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
1179
+
1180
+ ## Development Credits
1181
+
1182
+ While this was initially based on existing code, the conversion to a Gem and subsequent development was done using [Cursor](https://cursor.sh/) IDE with AI assistance from Claude Sonnet 4.
1183
+
1184
+ ## Summary
1185
+
1186
+ ActiveModelLogger is a comprehensive logging solution for Rails applications that provides:
1187
+
1188
+ - **🚀 Powerful Logging**: Structured logging with metadata, log chains, and atomic operations
1189
+ - **🔍 Advanced Querying**: Database-agnostic JSON queries with powerful scopes
1190
+ - **⚡ High Performance**: Batch operations, efficient indexing, and memory management
1191
+ - **🛠️ Developer Friendly**: Easy setup, comprehensive documentation, and extensive testing
1192
+ - **🌐 Cross-Platform**: Works with PostgreSQL, MySQL, and SQLite
1193
+ - **📊 Production Ready**: Log rotation, cleanup, and monitoring capabilities
1194
+
1195
+ ### Key Benefits
1196
+
1197
+ 1. **Structured Data**: Store complex metadata as JSON for flexible querying
1198
+ 2. **Log Chains**: Track related operations across multiple processes
1199
+ 3. **Atomic Operations**: Use log blocks for consistent, error-safe logging
1200
+ 4. **Performance**: Batch operations and optimized queries for high-volume applications
1201
+ 5. **Flexibility**: Customizable visibility, log levels, and output formats
1202
+ 6. **Reliability**: Comprehensive test suite and cross-version compatibility
1203
+
1204
+ ### Perfect For
1205
+
1206
+ - **E-commerce**: Order processing, payment tracking, inventory management
1207
+ - **SaaS Applications**: User activity, feature usage, system monitoring
1208
+ - **API Services**: Request logging, error tracking, performance monitoring
1209
+ - **Background Jobs**: Process tracking, error handling, progress monitoring
1210
+ - **Audit Trails**: Compliance, security, change tracking
1211
+ - **Debugging**: Complex workflows, error investigation, system analysis
1212
+
1213
+ ## Code of Conduct
1214
+
1215
+ Everyone interacting in the ActiveModelLogger project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/EricSlick/active_model_logger/blob/main/CODE_OF_CONDUCT.md).