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.
- checksums.yaml +7 -0
- data/.rubocop.yml +44 -0
- data/CHANGELOG.md +7 -0
- data/CODE_OF_CONDUCT.md +55 -0
- data/LICENSE.txt +21 -0
- data/README.md +1215 -0
- data/Rakefile +12 -0
- data/examples/basic_usage.rb +142 -0
- data/examples/log_block_demo.rb +487 -0
- data/examples/stdout_logging_demo.rb +35 -0
- data/identifier.sqlite +0 -0
- data/lib/active_model_logger/block_logger.rb +133 -0
- data/lib/active_model_logger/generators/active_model_logger/install_generator.rb +30 -0
- data/lib/active_model_logger/generators/active_model_logger/templates/README +33 -0
- data/lib/active_model_logger/generators/active_model_logger/templates/create_active_model_logs.rb +34 -0
- data/lib/active_model_logger/json_query_helpers.rb +103 -0
- data/lib/active_model_logger/log.rb +86 -0
- data/lib/active_model_logger/loggable.rb +380 -0
- data/lib/active_model_logger/loggable_helpers.rb +78 -0
- data/lib/active_model_logger/railtie.rb +14 -0
- data/lib/active_model_logger/version.rb +5 -0
- data/lib/active_model_logger.rb +17 -0
- data/sig/ActiveModelLogger.rbs +4 -0
- metadata +211 -0
data/README.md
ADDED
@@ -0,0 +1,1215 @@
|
|
1
|
+
# ActiveModelLogger
|
2
|
+
|
3
|
+
[](https://www.ruby-lang.org/)
|
4
|
+
[](https://rubyonrails.org/)
|
5
|
+
[](https://guides.rubyonrails.org/active_record_basics.html)
|
6
|
+
[](https://github.com/EricSlick/active_model_logger)
|
7
|
+
[](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).
|