rails_action_tracker 0.1.0 → 0.2.0
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 +4 -4
- data/Appraisals +13 -1
- data/README.md +247 -9
- data/RELEASE_NOTES.md +210 -0
- data/lib/generators/rails_action_tracker/templates/initializer.rb +139 -1
- data/lib/rails_action_tracker/tracker.rb +432 -15
- data/lib/rails_action_tracker/version.rb +1 -1
- metadata +7 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 474a7b13c3c2e1d8b7dc1a0830021eb6523b72c78ed952190d7d70cffe3be0e7
|
4
|
+
data.tar.gz: c0deab51762820a5d6042171af3bfa75d4705432480ff85dd469356814d86f9c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9a7d029e5ae2ac1bb4bbab60f9338ad25106c51b6ce5227bf626ddd058aed42ae2b56e5c1a61a0714a3677b05d7b1eb913af1543c4ab2d6d317840f4ffeb2314
|
7
|
+
data.tar.gz: 0cf1f7781f4ee60776e846402ab1059009f9db9cb4eb2e071c7642891a79039cfaaccdf6ce52e245ad6785153aaac4ebf57972de48cbe98c800b4f97cdf17444
|
data/Appraisals
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Appraisals for testing against Rails versions
|
4
|
-
#
|
4
|
+
# Supporting Rails 6.0+ through 7.1
|
5
5
|
|
6
6
|
appraise 'rails-6.0' do
|
7
7
|
gem 'activesupport', '~> 6.0.0'
|
@@ -14,3 +14,15 @@ appraise 'rails-6.1' do
|
|
14
14
|
gem 'actionpack', '~> 6.1.0'
|
15
15
|
gem 'railties', '~> 6.1.0'
|
16
16
|
end
|
17
|
+
|
18
|
+
appraise 'rails-7.0' do
|
19
|
+
gem 'activesupport', '~> 7.0.0'
|
20
|
+
gem 'actionpack', '~> 7.0.0'
|
21
|
+
gem 'railties', '~> 7.0.0'
|
22
|
+
end
|
23
|
+
|
24
|
+
appraise 'rails-7.1' do
|
25
|
+
gem 'activesupport', '~> 7.1.0'
|
26
|
+
gem 'actionpack', '~> 7.1.0'
|
27
|
+
gem 'railties', '~> 7.1.0'
|
28
|
+
end
|
data/README.md
CHANGED
@@ -21,13 +21,13 @@ Start your Rails server and see the output:
|
|
21
21
|
|
22
22
|
```
|
23
23
|
UsersController#show - Models and Services accessed during request:
|
24
|
-
|
25
|
-
| Models Read
|
26
|
-
|
27
|
-
| users
|
28
|
-
| posts
|
29
|
-
| comments
|
30
|
-
|
24
|
+
+-------------------+-------------------+-------------------+
|
25
|
+
| Models Read | Models Written | Services Accessed |
|
26
|
+
+-------------------+-------------------+-------------------+
|
27
|
+
| users | user_sessions | Redis |
|
28
|
+
| posts | audit_logs | Sidekiq |
|
29
|
+
| comments | | ActionMailer |
|
30
|
+
+-------------------+-------------------+-------------------+
|
31
31
|
```
|
32
32
|
|
33
33
|
## Configuration
|
@@ -41,6 +41,10 @@ RailsActionTracker::Tracker.configure(
|
|
41
41
|
write_to_file: false, # Write to separate file (default: false)
|
42
42
|
log_file_path: Rails.root.join('log', 'action_tracker.log'),
|
43
43
|
|
44
|
+
# Output format controls (new in v0.2.0+)
|
45
|
+
print_format: :table, # Format for console/Rails log: :table, :csv, :json
|
46
|
+
log_format: :table, # Format for log file: :table, :csv, :json
|
47
|
+
|
44
48
|
# Custom services to track (optional)
|
45
49
|
services: [
|
46
50
|
{ name: 'Redis', pattern: /redis/i },
|
@@ -53,6 +57,96 @@ RailsActionTracker::Tracker.configure(
|
|
53
57
|
)
|
54
58
|
```
|
55
59
|
|
60
|
+
### Output Formats
|
61
|
+
|
62
|
+
**🎨 New in v0.2.0+**: The gem now supports different output formats and allows you to configure separate formats for console output and file logging.
|
63
|
+
|
64
|
+
#### Available Formats
|
65
|
+
|
66
|
+
1. **`:table`** - Clean tabular format (default)
|
67
|
+
2. **`:csv`** - CSV format with dynamic headers
|
68
|
+
3. **`:json`** - JSON format with different behaviors for print vs log
|
69
|
+
|
70
|
+
#### Format Configuration
|
71
|
+
|
72
|
+
```ruby
|
73
|
+
RailsActionTracker::Tracker.configure(
|
74
|
+
print_format: :table, # Format for console/Rails log output
|
75
|
+
log_format: :json, # Format for log file output (can be different!)
|
76
|
+
|
77
|
+
print_to_rails_log: true, # Enable console output
|
78
|
+
write_to_file: true, # Enable file logging
|
79
|
+
log_file_path: Rails.root.join('log', 'action_tracker.json')
|
80
|
+
)
|
81
|
+
```
|
82
|
+
|
83
|
+
#### Format Examples
|
84
|
+
|
85
|
+
**Table Format (:table)**
|
86
|
+
```
|
87
|
+
UsersController#show - Models and Services accessed during request:
|
88
|
+
+-------------------+-------------------+-------------------+
|
89
|
+
| Models Read | Models Written | Services Accessed |
|
90
|
+
+-------------------+-------------------+-------------------+
|
91
|
+
| users | user_sessions | Redis |
|
92
|
+
| posts | audit_logs | Sidekiq |
|
93
|
+
+-------------------+-------------------+-------------------+
|
94
|
+
```
|
95
|
+
|
96
|
+
**CSV Print Format (:csv for console)**
|
97
|
+
```
|
98
|
+
Action,users,posts,user_sessions,Redis
|
99
|
+
UsersController#show,R,R,W,Y
|
100
|
+
```
|
101
|
+
|
102
|
+
**CSV Log Format (:csv for file - accumulative)**
|
103
|
+
```csv
|
104
|
+
Action,Elasticsearch,Redis,Sidekiq,posts,profiles,sessions,users
|
105
|
+
UsersController#show,Y,Y,-,R,R,W,RW
|
106
|
+
PostsController#create,-,-,Y,RW,-,-,R
|
107
|
+
```
|
108
|
+
|
109
|
+
**JSON Print Format (:json for console)**
|
110
|
+
```
|
111
|
+
UsersController#show: {
|
112
|
+
"read": ["users", "posts"],
|
113
|
+
"write": ["user_sessions", "audit_logs"],
|
114
|
+
"services": ["Redis", "Sidekiq"]
|
115
|
+
}
|
116
|
+
```
|
117
|
+
|
118
|
+
**JSON Log Format (:json for file - accumulative)**
|
119
|
+
```json
|
120
|
+
{
|
121
|
+
"UsersController#show": {
|
122
|
+
"read": ["users", "posts"],
|
123
|
+
"write": ["user_sessions"],
|
124
|
+
"services": ["Redis"]
|
125
|
+
},
|
126
|
+
"UsersController#update": {
|
127
|
+
"read": ["users", "posts"],
|
128
|
+
"write": ["users", "audit_logs"],
|
129
|
+
"services": ["Redis", "Sidekiq"]
|
130
|
+
}
|
131
|
+
}
|
132
|
+
```
|
133
|
+
|
134
|
+
#### Format Behavior Differences
|
135
|
+
|
136
|
+
Both JSON and CSV formats behave differently for print vs log:
|
137
|
+
|
138
|
+
**JSON Format:**
|
139
|
+
- **JSON Print** (console): Shows only current action data in clean format
|
140
|
+
- **JSON Log** (file): Accumulates all actions in a persistent structure, merging new data when the same action is visited again
|
141
|
+
|
142
|
+
**CSV Format:**
|
143
|
+
- **CSV Print** (console): Shows only current action data with compact headers
|
144
|
+
- **CSV Log** (file): Accumulates all actions in a single CSV file with dynamic headers that expand as new tables/services are encountered. When the same action is visited again, access patterns are merged intelligently (e.g., R + W = RW)
|
145
|
+
|
146
|
+
**Table Format:**
|
147
|
+
- **Table Print** (console): Shows current action data in formatted table
|
148
|
+
- **Table Log** (file): Each action logged separately in table format (no accumulation)
|
149
|
+
|
56
150
|
### Configuration Options
|
57
151
|
|
58
152
|
**Basic Configuration**
|
@@ -62,6 +156,10 @@ RailsActionTracker::Tracker.configure(
|
|
62
156
|
print_to_rails_log: true, # Print to Rails logger (default: true)
|
63
157
|
write_to_file: false, # Write to separate file (default: false)
|
64
158
|
log_file_path: nil, # Path to separate log file (required if write_to_file: true)
|
159
|
+
|
160
|
+
print_format: :table, # Format for console output: :table, :csv, :json
|
161
|
+
log_format: :table, # Format for file output (defaults to print_format)
|
162
|
+
|
65
163
|
ignored_tables: [], # Tables to ignore from tracking (optional)
|
66
164
|
ignored_controllers: [], # Controllers to completely ignore (optional)
|
67
165
|
ignored_actions: {} # Specific controller#action combinations to ignore (optional)
|
@@ -96,6 +194,86 @@ RailsActionTracker::Tracker.configure(
|
|
96
194
|
)
|
97
195
|
```
|
98
196
|
|
197
|
+
### Format Configuration Examples
|
198
|
+
|
199
|
+
**Option 4: Different print and log formats**
|
200
|
+
```ruby
|
201
|
+
RailsActionTracker::Tracker.configure(
|
202
|
+
print_format: :json, # Console shows clean JSON for current action
|
203
|
+
log_format: :csv, # File saves in CSV format for analysis
|
204
|
+
print_to_rails_log: true,
|
205
|
+
write_to_file: true,
|
206
|
+
log_file_path: Rails.root.join('log', 'action_tracker.csv')
|
207
|
+
)
|
208
|
+
```
|
209
|
+
|
210
|
+
**Option 5: JSON accumulation for analysis**
|
211
|
+
```ruby
|
212
|
+
RailsActionTracker::Tracker.configure(
|
213
|
+
print_format: :table, # Console shows familiar table format
|
214
|
+
log_format: :json, # File accumulates JSON data across requests
|
215
|
+
print_to_rails_log: true,
|
216
|
+
write_to_file: true,
|
217
|
+
log_file_path: Rails.root.join('log', 'action_tracker.json')
|
218
|
+
)
|
219
|
+
```
|
220
|
+
|
221
|
+
**Option 6: CSV accumulation for spreadsheet analysis**
|
222
|
+
```ruby
|
223
|
+
RailsActionTracker::Tracker.configure(
|
224
|
+
print_format: :table, # Console shows table
|
225
|
+
log_format: :csv, # File accumulates CSV with dynamic columns
|
226
|
+
print_to_rails_log: true,
|
227
|
+
write_to_file: true,
|
228
|
+
log_file_path: Rails.root.join('log', 'action_tracker.csv')
|
229
|
+
)
|
230
|
+
```
|
231
|
+
# Results in accumulated CSV like:
|
232
|
+
# Action,Redis,Sidekiq,posts,profiles,users
|
233
|
+
# UsersController#show,Y,-,R,R,RW
|
234
|
+
# PostsController#create,-,Y,RW,-,R
|
235
|
+
|
236
|
+
**Option 7: CSV print and CSV log (different behaviors)**
|
237
|
+
```ruby
|
238
|
+
RailsActionTracker::Tracker.configure(
|
239
|
+
print_format: :csv, # Console shows current action CSV
|
240
|
+
log_format: :csv, # File accumulates all actions with smart merging
|
241
|
+
print_to_rails_log: true,
|
242
|
+
write_to_file: true,
|
243
|
+
log_file_path: Rails.root.join('log', 'action_tracker.csv')
|
244
|
+
)
|
245
|
+
```
|
246
|
+
# Print: Shows only current action's CSV data with minimal headers
|
247
|
+
# Log: Accumulates all actions with expanding headers and intelligent merging
|
248
|
+
|
249
|
+
**Option 8: JSON everywhere with different behaviors**
|
250
|
+
```ruby
|
251
|
+
RailsActionTracker::Tracker.configure(
|
252
|
+
print_format: :json, # Console: current action JSON only
|
253
|
+
log_format: :json, # File: accumulative JSON structure
|
254
|
+
print_to_rails_log: true,
|
255
|
+
write_to_file: true,
|
256
|
+
log_file_path: Rails.root.join('log', 'action_tracker.json')
|
257
|
+
)
|
258
|
+
```
|
259
|
+
|
260
|
+
### Migration from v1.x
|
261
|
+
|
262
|
+
If you're upgrading from v1.x and using `output_format`, the gem maintains backward compatibility:
|
263
|
+
|
264
|
+
```ruby
|
265
|
+
# Old configuration (still works)
|
266
|
+
RailsActionTracker::Tracker.configure(
|
267
|
+
output_format: :json # Sets both print_format and log_format to :json
|
268
|
+
)
|
269
|
+
|
270
|
+
# New configuration (recommended)
|
271
|
+
RailsActionTracker::Tracker.configure(
|
272
|
+
print_format: :table, # Different formats for different outputs
|
273
|
+
log_format: :json
|
274
|
+
)
|
275
|
+
```
|
276
|
+
|
99
277
|
### Custom Service Detection
|
100
278
|
|
101
279
|
You can customize which services are detected by providing custom patterns:
|
@@ -259,6 +437,59 @@ RailsActionTracker::Tracker.print_summary
|
|
259
437
|
RailsActionTracker::Tracker.stop_tracking
|
260
438
|
```
|
261
439
|
|
440
|
+
## Format Use Cases
|
441
|
+
|
442
|
+
### Development & Debugging
|
443
|
+
```ruby
|
444
|
+
# Clean console output, detailed JSON logs for analysis
|
445
|
+
RailsActionTracker::Tracker.configure(
|
446
|
+
print_format: :table, # Easy to read during development
|
447
|
+
log_format: :json, # Detailed logs for debugging
|
448
|
+
print_to_rails_log: true,
|
449
|
+
write_to_file: true,
|
450
|
+
log_file_path: Rails.root.join('log', 'action_tracker.json')
|
451
|
+
)
|
452
|
+
```
|
453
|
+
|
454
|
+
### Performance Analysis
|
455
|
+
```ruby
|
456
|
+
# CSV accumulation for importing into spreadsheet tools
|
457
|
+
RailsActionTracker::Tracker.configure(
|
458
|
+
print_format: :table, # Console stays readable
|
459
|
+
log_format: :csv, # Perfect for Excel/Google Sheets with accumulated data
|
460
|
+
print_to_rails_log: true,
|
461
|
+
write_to_file: true,
|
462
|
+
log_file_path: Rails.root.join('log', 'performance_analysis.csv')
|
463
|
+
)
|
464
|
+
```
|
465
|
+
# Results in comprehensive CSV with all actions and merged access patterns
|
466
|
+
# Headers expand automatically as new tables/services are discovered
|
467
|
+
# Perfect for pivot tables and data analysis
|
468
|
+
|
469
|
+
### API Documentation Generation
|
470
|
+
```ruby
|
471
|
+
# JSON logs for automated API documentation
|
472
|
+
RailsActionTracker::Tracker.configure(
|
473
|
+
print_format: :json, # Immediate JSON feedback
|
474
|
+
log_format: :json, # Accumulated endpoint data
|
475
|
+
print_to_rails_log: true,
|
476
|
+
write_to_file: true,
|
477
|
+
log_file_path: Rails.root.join('log', 'api_endpoints.json')
|
478
|
+
)
|
479
|
+
```
|
480
|
+
|
481
|
+
### Monitoring & Alerting
|
482
|
+
```ruby
|
483
|
+
# CSV for log aggregation systems
|
484
|
+
RailsActionTracker::Tracker.configure(
|
485
|
+
print_format: :table, # Human-readable console
|
486
|
+
log_format: :csv, # Machine-readable logs
|
487
|
+
print_to_rails_log: false, # Reduce console noise
|
488
|
+
write_to_file: true,
|
489
|
+
log_file_path: Rails.root.join('log', 'monitoring.csv')
|
490
|
+
)
|
491
|
+
```
|
492
|
+
|
262
493
|
## How It Works
|
263
494
|
|
264
495
|
The gem integrates seamlessly with Rails:
|
@@ -274,10 +505,13 @@ The gem integrates seamlessly with Rails:
|
|
274
505
|
- 🔍 **Model tracking** - See which ActiveRecord models are read/written
|
275
506
|
- 🏢 **Service detection** - Monitor Redis, Sidekiq, HTTP calls, and more
|
276
507
|
- 📝 **Flexible logging** - Rails logger, separate files, or both
|
277
|
-
- 🎨 **
|
508
|
+
- 🎨 **Multiple output formats** - Table, CSV, and JSON formats with separate print/log controls
|
509
|
+
- 📊 **JSON accumulation** - Persistent JSON logs that merge data across requests
|
510
|
+
- 🔄 **Format flexibility** - Different formats for console vs file output
|
278
511
|
- ⚡ **Zero configuration** - Works immediately after installation
|
279
512
|
- 🧵 **Thread-safe** - Handles concurrent requests properly
|
280
513
|
- 🚀 **Production ready** - Minimal performance impact
|
514
|
+
- 🔧 **Backward compatible** - Seamless upgrade from v1.x configurations
|
281
515
|
|
282
516
|
## Thread Safety
|
283
517
|
|
@@ -297,7 +531,11 @@ This gem is tested and compatible with:
|
|
297
531
|
|
298
532
|
**Ruby Versions:** 2.7.x, 3.0.x, 3.1.x, 3.4.x
|
299
533
|
|
300
|
-
**Rails Versions:** 5.0+ through
|
534
|
+
**Rails Versions:** 5.0+ through 7.1+ (see our CI for the full compatibility matrix)
|
535
|
+
|
536
|
+
**Currently Tested Combinations:**
|
537
|
+
- Ruby 2.7.x with Rails 6.0, 6.1, 7.0, 7.1
|
538
|
+
- Ruby 3.0.x with Rails 6.0, 6.1, 7.0, 7.1
|
301
539
|
|
302
540
|
## Contributing
|
303
541
|
|
data/RELEASE_NOTES.md
ADDED
@@ -0,0 +1,210 @@
|
|
1
|
+
# Rails Action Tracker - Release Notes
|
2
|
+
|
3
|
+
## Version 0.2.0 - Major Format Enhancement Release
|
4
|
+
|
5
|
+
**Release Date:** September 3, 2025
|
6
|
+
|
7
|
+
### 🚀 Major New Features
|
8
|
+
|
9
|
+
#### Separate Print and Log Format Support
|
10
|
+
- **Independent Format Control**: Configure different output formats for console display (`print_format`) and file logging (`log_format`)
|
11
|
+
- **Mix & Match Flexibility**: Use table format for console readability while saving JSON/CSV for analysis
|
12
|
+
- **Backward Compatible**: Existing `output_format` configuration continues to work seamlessly
|
13
|
+
|
14
|
+
#### JSON Accumulation with Intelligent Merging
|
15
|
+
- **Persistent JSON Logs**: JSON format now accumulates all actions in a single valid JSON file
|
16
|
+
- **Smart Data Merging**: When the same action is visited multiple times, new tables/services are added to existing data
|
17
|
+
- **Thread-Safe Operations**: File locking prevents corruption during concurrent writes
|
18
|
+
- **Separate Behaviors**:
|
19
|
+
- JSON Print: Shows only current action data in clean format
|
20
|
+
- JSON Log: Accumulates comprehensive historical data across requests
|
21
|
+
|
22
|
+
#### CSV Accumulation with Dynamic Headers
|
23
|
+
- **Intelligent CSV Accumulation**: CSV format accumulates all actions in a single file with expanding headers
|
24
|
+
- **Dynamic Schema Evolution**: Headers automatically expand as new tables/services are discovered
|
25
|
+
- **Smart Access Pattern Merging**: Access patterns merge intelligently (R + W = RW) when same action visited again
|
26
|
+
- **Separate Behaviors**:
|
27
|
+
- CSV Print: Shows only current action data with compact headers
|
28
|
+
- CSV Log: Comprehensive accumulated data perfect for spreadsheet analysis
|
29
|
+
|
30
|
+
### 🎨 Format Examples
|
31
|
+
|
32
|
+
#### JSON Format Behaviors
|
33
|
+
**Print Output (Console):**
|
34
|
+
```
|
35
|
+
UsersController#show: {
|
36
|
+
"read": ["users", "posts"],
|
37
|
+
"write": ["sessions"],
|
38
|
+
"services": ["Redis"]
|
39
|
+
}
|
40
|
+
```
|
41
|
+
|
42
|
+
**Log Output (Accumulated File):**
|
43
|
+
```json
|
44
|
+
{
|
45
|
+
"UsersController#show": {
|
46
|
+
"read": ["users", "posts", "profiles"],
|
47
|
+
"write": ["sessions", "users"],
|
48
|
+
"services": ["Redis", "Elasticsearch"]
|
49
|
+
},
|
50
|
+
"PostsController#create": {
|
51
|
+
"read": ["posts", "users"],
|
52
|
+
"write": ["posts"],
|
53
|
+
"services": ["Sidekiq"]
|
54
|
+
}
|
55
|
+
}
|
56
|
+
```
|
57
|
+
|
58
|
+
#### CSV Format Behaviors
|
59
|
+
**Print Output (Console):**
|
60
|
+
```csv
|
61
|
+
Action,users,posts,sessions,Redis
|
62
|
+
UsersController#show,R,R,W,Y
|
63
|
+
```
|
64
|
+
|
65
|
+
**Log Output (Accumulated File):**
|
66
|
+
```csv
|
67
|
+
Action,Elasticsearch,Redis,Sidekiq,posts,profiles,sessions,users
|
68
|
+
UsersController#show,Y,Y,-,R,R,W,RW
|
69
|
+
PostsController#create,-,-,Y,RW,-,-,R
|
70
|
+
```
|
71
|
+
|
72
|
+
### ⚙️ Configuration Enhancements
|
73
|
+
|
74
|
+
#### New Configuration Options
|
75
|
+
```ruby
|
76
|
+
RailsActionTracker::Tracker.configure(
|
77
|
+
print_format: :table, # Format for console/Rails log: :table, :csv, :json
|
78
|
+
log_format: :json, # Format for log file: :table, :csv, :json
|
79
|
+
print_to_rails_log: true,
|
80
|
+
write_to_file: true,
|
81
|
+
log_file_path: Rails.root.join('log', 'action_tracker.json')
|
82
|
+
)
|
83
|
+
```
|
84
|
+
|
85
|
+
#### Popular Configuration Patterns
|
86
|
+
|
87
|
+
**Development & Debugging:**
|
88
|
+
```ruby
|
89
|
+
print_format: :table, # Easy to read during development
|
90
|
+
log_format: :json # Detailed logs for debugging
|
91
|
+
```
|
92
|
+
|
93
|
+
**Performance Analysis:**
|
94
|
+
```ruby
|
95
|
+
print_format: :table, # Console stays readable
|
96
|
+
log_format: :csv # Perfect for Excel/Google Sheets
|
97
|
+
```
|
98
|
+
|
99
|
+
**API Documentation Generation:**
|
100
|
+
```ruby
|
101
|
+
print_format: :json, # Immediate JSON feedback
|
102
|
+
log_format: :json # Accumulated endpoint data
|
103
|
+
```
|
104
|
+
|
105
|
+
### 🔧 Technical Improvements
|
106
|
+
|
107
|
+
#### Enhanced File Handling
|
108
|
+
- **Automatic Directory Creation**: Log directories are created automatically if they don't exist
|
109
|
+
- **Malformed File Recovery**: Gracefully handles corrupted JSON/CSV files by starting fresh
|
110
|
+
- **Atomic File Operations**: All file writes are atomic to prevent partial data corruption
|
111
|
+
- **Memory Efficient**: Large files are processed efficiently without loading entire contents into memory
|
112
|
+
|
113
|
+
#### Logger Optimization
|
114
|
+
- **Format-Specific Logger Setup**: Only creates custom loggers for formats that need them
|
115
|
+
- **Reduced File I/O**: JSON and CSV formats write directly to files, avoiding logger overhead
|
116
|
+
- **No Logger Interference**: Prevents logger headers from corrupting structured data files
|
117
|
+
|
118
|
+
### 📊 Use Cases & Benefits
|
119
|
+
|
120
|
+
#### Data Analysis
|
121
|
+
- **Spreadsheet Ready**: CSV accumulation creates files perfect for Excel/Google Sheets analysis
|
122
|
+
- **Pivot Table Friendly**: Dynamic headers and consistent data structure ideal for pivot tables
|
123
|
+
- **Historical Trends**: Track how application data access patterns evolve over time
|
124
|
+
|
125
|
+
#### Performance Monitoring
|
126
|
+
- **Access Pattern Analysis**: Identify which actions access the most tables/services
|
127
|
+
- **Service Usage Tracking**: Monitor service adoption and usage patterns across actions
|
128
|
+
- **Data Growth Tracking**: Watch as application complexity grows through expanding CSV headers
|
129
|
+
|
130
|
+
#### API Documentation
|
131
|
+
- **Automatic Endpoint Discovery**: JSON accumulation reveals all API endpoints and their data dependencies
|
132
|
+
- **Real Usage Patterns**: See actual data access patterns instead of theoretical documentation
|
133
|
+
- **Integration Testing**: Verify that endpoints access expected tables and services
|
134
|
+
|
135
|
+
### 🧪 Quality Assurance
|
136
|
+
|
137
|
+
#### Comprehensive Testing
|
138
|
+
- **73 Test Cases**: All features covered with comprehensive test suite
|
139
|
+
- **403 Assertions**: Thorough validation of all functionality
|
140
|
+
- **File Operation Testing**: Extensive testing of concurrent file access and merging logic
|
141
|
+
- **Format Validation**: All output formats validated for correctness and consistency
|
142
|
+
|
143
|
+
#### Code Quality
|
144
|
+
- **RuboCop Compliant**: All code meets Ruby style guidelines
|
145
|
+
- **Thread Safety**: All operations are thread-safe for production use
|
146
|
+
- **Error Handling**: Graceful degradation when file operations fail
|
147
|
+
- **Memory Efficient**: Optimized for minimal memory usage even with large data sets
|
148
|
+
|
149
|
+
### 📖 Documentation Updates
|
150
|
+
|
151
|
+
#### Comprehensive Examples
|
152
|
+
- **11 Configuration Examples**: From basic setups to advanced use cases
|
153
|
+
- **Format Comparison Guide**: Clear explanations of when to use each format
|
154
|
+
- **Migration Guide**: Smooth upgrade path from v1.x configurations
|
155
|
+
- **Use Case Documentation**: Real-world scenarios with recommended configurations
|
156
|
+
|
157
|
+
#### Updated README
|
158
|
+
- **Format Behavior Differences**: Clear explanation of print vs log behaviors
|
159
|
+
- **Configuration Matrix**: All possible format combinations documented
|
160
|
+
- **Performance Guidelines**: Recommendations for production use
|
161
|
+
- **Troubleshooting Section**: Common issues and solutions
|
162
|
+
|
163
|
+
### 🔄 Backward Compatibility
|
164
|
+
|
165
|
+
#### Seamless Upgrades
|
166
|
+
- **No Breaking Changes**: All v1.x configurations continue to work
|
167
|
+
- **Automatic Migration**: `output_format` automatically sets both `print_format` and `log_format`
|
168
|
+
- **Deprecation Warnings**: Clear guidance on migrating to new configuration options
|
169
|
+
- **Feature Parity**: All v1.x functionality preserved and enhanced
|
170
|
+
|
171
|
+
### 🏃♂️ Getting Started
|
172
|
+
|
173
|
+
#### Quick Setup
|
174
|
+
```ruby
|
175
|
+
# Add to your Gemfile
|
176
|
+
gem 'rails_action_tracker'
|
177
|
+
|
178
|
+
# Generate initializer
|
179
|
+
rails generate rails_action_tracker:install
|
180
|
+
|
181
|
+
# Configure for your needs
|
182
|
+
RailsActionTracker::Tracker.configure(
|
183
|
+
print_format: :table, # Console output
|
184
|
+
log_format: :csv, # File accumulation
|
185
|
+
write_to_file: true,
|
186
|
+
log_file_path: Rails.root.join('log', 'action_tracker.csv')
|
187
|
+
)
|
188
|
+
```
|
189
|
+
|
190
|
+
#### Immediate Benefits
|
191
|
+
- **Zero Learning Curve**: Works immediately with sensible defaults
|
192
|
+
- **Flexible Configuration**: Easily adjust formats as needs change
|
193
|
+
- **Production Ready**: Thread-safe and performance optimized
|
194
|
+
- **Rich Data**: Comprehensive insights into application behavior
|
195
|
+
|
196
|
+
### 🔮 Future Enhancements
|
197
|
+
|
198
|
+
#### Planned Features
|
199
|
+
- **Real-time Dashboard**: Web interface for monitoring live data access patterns
|
200
|
+
- **Alert System**: Notifications when unusual access patterns are detected
|
201
|
+
- **Export Integrations**: Direct integration with analytics platforms
|
202
|
+
- **Custom Format Support**: Plugin system for custom output formats
|
203
|
+
|
204
|
+
---
|
205
|
+
|
206
|
+
**Full Changelog**: [View on GitHub](https://github.com/your-repo/rails_action_tracker/compare/v0.1.0...v0.2.0)
|
207
|
+
|
208
|
+
**Upgrade Guide**: See README.md for detailed migration instructions from v1.x
|
209
|
+
|
210
|
+
**Support**: Report issues on [GitHub Issues](https://github.com/your-repo/rails_action_tracker/issues)
|
@@ -14,6 +14,15 @@ RailsActionTracker::Tracker.configure(
|
|
14
14
|
# Path to separate log file (required if write_to_file is true)
|
15
15
|
log_file_path: Rails.root.join('log', 'action_tracker.log'),
|
16
16
|
|
17
|
+
# Format for console/Rails log output: :table (default), :csv, or :json
|
18
|
+
print_format: :table,
|
19
|
+
|
20
|
+
# Format for log file output: :table, :csv, or :json (defaults to print_format if not specified)
|
21
|
+
log_format: :table,
|
22
|
+
|
23
|
+
# Deprecated: Use print_format and log_format instead
|
24
|
+
# output_format: :table,
|
25
|
+
|
17
26
|
# Custom service detection patterns (optional)
|
18
27
|
# You can define custom services to track beyond the defaults
|
19
28
|
services: [
|
@@ -41,7 +50,31 @@ RailsActionTracker::Tracker.configure(
|
|
41
50
|
# Add your custom ignored tables here
|
42
51
|
# 'audit_logs',
|
43
52
|
# 'session_data'
|
44
|
-
]
|
53
|
+
],
|
54
|
+
|
55
|
+
# Controllers to ignore completely (optional)
|
56
|
+
# All actions from these controllers will be ignored
|
57
|
+
ignored_controllers: [
|
58
|
+
# 'Rails::PwaController', # Ignore PWA controller completely
|
59
|
+
# 'HealthCheckController', # Ignore health check controller
|
60
|
+
# 'Assets::ServingController'
|
61
|
+
],
|
62
|
+
|
63
|
+
# Specific controller#action combinations to ignore (optional)
|
64
|
+
# Flexible patterns for fine-grained control
|
65
|
+
ignored_actions: {
|
66
|
+
# Ignore specific actions for specific controllers
|
67
|
+
# 'ApplicationController' => ['ping', 'status', 'health'],
|
68
|
+
# 'ApiController' => ['heartbeat', 'version'],
|
69
|
+
# 'AdminController' => ['dashboard_stats'],
|
70
|
+
|
71
|
+
# Ignore entire controllers using empty arrays or nil
|
72
|
+
# 'Rails::PwaController' => [], # Empty array = ignore entire controller
|
73
|
+
# 'HealthController' => nil, # nil = ignore entire controller
|
74
|
+
|
75
|
+
# Global action ignoring (ignore actions across ALL controllers)
|
76
|
+
# '' => ['ping', 'health', 'status'] # Empty string key = applies to all controllers
|
77
|
+
}
|
45
78
|
)
|
46
79
|
|
47
80
|
# Example configurations:
|
@@ -65,3 +98,108 @@ RailsActionTracker::Tracker.configure(
|
|
65
98
|
# write_to_file: true,
|
66
99
|
# log_file_path: Rails.root.join('log', 'action_tracker.log')
|
67
100
|
# )
|
101
|
+
|
102
|
+
# Configuration 4: With controller/action filtering
|
103
|
+
# RailsActionTracker::Tracker.configure(
|
104
|
+
# print_to_rails_log: true,
|
105
|
+
# ignored_controllers: ['Rails::PwaController', 'HealthCheckController'],
|
106
|
+
# ignored_actions: {
|
107
|
+
# '' => ['ping', 'health'], # Global actions to ignore
|
108
|
+
# 'ApplicationController' => ['status'], # Controller-specific actions
|
109
|
+
# 'MonitoringController' => [], # Ignore entire controller
|
110
|
+
# 'ApiController' => ['heartbeat', 'version'] # Multiple specific actions
|
111
|
+
# }
|
112
|
+
# )
|
113
|
+
|
114
|
+
# Configuration 5: CSV print format (current action only)
|
115
|
+
# RailsActionTracker::Tracker.configure(
|
116
|
+
# print_format: :csv,
|
117
|
+
# print_to_rails_log: true
|
118
|
+
# )
|
119
|
+
# Example CSV print output (shows only current action):
|
120
|
+
# Action,table1,table2,Redis
|
121
|
+
# JobsController#show,R,R,Y
|
122
|
+
|
123
|
+
# Configuration 6: Different print and log formats
|
124
|
+
# RailsActionTracker::Tracker.configure(
|
125
|
+
# print_format: :json, # Console shows JSON for current action only
|
126
|
+
# log_format: :csv, # Log file saves in CSV format
|
127
|
+
# print_to_rails_log: true,
|
128
|
+
# write_to_file: true,
|
129
|
+
# log_file_path: Rails.root.join('log', 'action_tracker.csv')
|
130
|
+
# )
|
131
|
+
# Print output (Rails log):
|
132
|
+
# JobsController#show: {
|
133
|
+
# "read": ["table1", "table2", "table3"],
|
134
|
+
# "write": [],
|
135
|
+
# "services": ["Redis"]
|
136
|
+
# }
|
137
|
+
# Log file output (CSV format):
|
138
|
+
# Action,table1,table2,table3,Redis
|
139
|
+
# JobsController#show,R,R,R,Y
|
140
|
+
|
141
|
+
# Configuration 7: JSON accumulation in log file, table in console
|
142
|
+
# RailsActionTracker::Tracker.configure(
|
143
|
+
# print_format: :table, # Console shows table format
|
144
|
+
# log_format: :json, # Log file accumulates JSON data
|
145
|
+
# print_to_rails_log: true,
|
146
|
+
# write_to_file: true,
|
147
|
+
# log_file_path: Rails.root.join('log', 'action_tracker.json')
|
148
|
+
# )
|
149
|
+
# Print output: Standard table format in Rails log
|
150
|
+
# Log file output: Accumulated JSON structure like:
|
151
|
+
# {
|
152
|
+
# "JobsController#show": {
|
153
|
+
# "read": ["table1", "table2", "table3"],
|
154
|
+
# "write": [],
|
155
|
+
# "services": ["Redis"]
|
156
|
+
# },
|
157
|
+
# "JobsController#update": {
|
158
|
+
# "read": ["table1", "table2"],
|
159
|
+
# "write": ["table1"],
|
160
|
+
# "services": ["Redis", "Sidekiq"]
|
161
|
+
# }
|
162
|
+
# }
|
163
|
+
|
164
|
+
# Configuration 8: JSON print and JSON log (different behaviors)
|
165
|
+
# RailsActionTracker::Tracker.configure(
|
166
|
+
# print_format: :json, # Console shows current action JSON
|
167
|
+
# log_format: :json, # Log file accumulates all actions
|
168
|
+
# print_to_rails_log: true,
|
169
|
+
# write_to_file: true,
|
170
|
+
# log_file_path: Rails.root.join('log', 'action_tracker.json')
|
171
|
+
# )
|
172
|
+
# Print: Shows only current action's JSON data
|
173
|
+
# Log: Accumulates all actions in a single JSON structure
|
174
|
+
|
175
|
+
# Configuration 9: CSV accumulation with intelligent merging
|
176
|
+
# RailsActionTracker::Tracker.configure(
|
177
|
+
# print_format: :table, # Console shows readable table
|
178
|
+
# log_format: :csv, # File accumulates CSV data with smart merging
|
179
|
+
# print_to_rails_log: true,
|
180
|
+
# write_to_file: true,
|
181
|
+
# log_file_path: Rails.root.join('log', 'accumulated_tracker.csv')
|
182
|
+
# )
|
183
|
+
# Example accumulated CSV output:
|
184
|
+
# Action,Elasticsearch,Redis,Sidekiq,posts,profiles,sessions,users
|
185
|
+
# JobsController#show,Y,Y,-,R,R,W,RW
|
186
|
+
# JobsController#update,-,Y,Y,RW,-,-,RW
|
187
|
+
# Note: Headers expand as new tables/services are discovered
|
188
|
+
# Note: Access patterns merge intelligently (R + W = RW)
|
189
|
+
|
190
|
+
# Configuration 10: CSV print and CSV log (different behaviors)
|
191
|
+
# RailsActionTracker::Tracker.configure(
|
192
|
+
# print_format: :csv, # Console shows current action CSV
|
193
|
+
# log_format: :csv, # File accumulates all actions
|
194
|
+
# print_to_rails_log: true,
|
195
|
+
# write_to_file: true,
|
196
|
+
# log_file_path: Rails.root.join('log', 'action_tracker.csv')
|
197
|
+
# )
|
198
|
+
# Print: Compact CSV with only current action's tables/services
|
199
|
+
# Log: Full CSV with all actions and comprehensive column headers
|
200
|
+
|
201
|
+
# Configuration 11: Backward compatibility (deprecated but still supported)
|
202
|
+
# RailsActionTracker::Tracker.configure(
|
203
|
+
# output_format: :json # Will set both print_format and log_format to :json
|
204
|
+
# )
|
205
|
+
# Note: output_format is deprecated, use print_format and log_format for better control
|
@@ -16,6 +16,9 @@ module RailsActionTracker
|
|
16
16
|
print_to_rails_log: true,
|
17
17
|
write_to_file: false,
|
18
18
|
log_file_path: nil,
|
19
|
+
print_format: :table,
|
20
|
+
log_format: nil,
|
21
|
+
output_format: nil, # Deprecated: kept for backward compatibility
|
19
22
|
services: [],
|
20
23
|
ignored_tables: %w[pg_attribute pg_index pg_class pg_namespace pg_type ar_internal_metadata
|
21
24
|
schema_migrations],
|
@@ -23,7 +26,19 @@ module RailsActionTracker
|
|
23
26
|
ignored_actions: {}
|
24
27
|
}.merge(options)
|
25
28
|
|
26
|
-
|
29
|
+
# Handle backward compatibility with output_format
|
30
|
+
if @config[:output_format] && !options.key?(:print_format) && !options.key?(:log_format)
|
31
|
+
@config[:print_format] = @config[:output_format]
|
32
|
+
@config[:log_format] = @config[:output_format]
|
33
|
+
end
|
34
|
+
|
35
|
+
# Default log_format to print_format if not specified
|
36
|
+
@config[:log_format] ||= @config[:print_format]
|
37
|
+
|
38
|
+
# Only setup custom logger for formats that don't write directly to file
|
39
|
+
should_setup_logger = @config[:write_to_file] && @config[:log_file_path] &&
|
40
|
+
!%i[json csv].include?(@config[:log_format])
|
41
|
+
setup_custom_logger if should_setup_logger
|
27
42
|
end
|
28
43
|
|
29
44
|
def start_tracking
|
@@ -50,21 +65,11 @@ module RailsActionTracker
|
|
50
65
|
def print_summary
|
51
66
|
logs = Thread.current[THREAD_KEY]
|
52
67
|
return unless logs
|
53
|
-
|
54
|
-
# Check if this controller/action should be ignored
|
55
68
|
return if should_ignore_controller_action?(logs[:controller], logs[:action])
|
56
69
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
controller_action = "#{logs[:controller]}##{logs[:action]}" if logs[:controller] && logs[:action]
|
62
|
-
|
63
|
-
# Generate outputs with and without colors
|
64
|
-
colored_output = format_summary(read_models, write_models, services_accessed, controller_action, true)
|
65
|
-
plain_output = format_summary(read_models, write_models, services_accessed, controller_action, false)
|
66
|
-
|
67
|
-
log_output(colored_output, plain_output)
|
70
|
+
summary_data = prepare_summary_data(logs)
|
71
|
+
handle_print_output(summary_data)
|
72
|
+
handle_log_file_output(summary_data)
|
68
73
|
end
|
69
74
|
|
70
75
|
private
|
@@ -159,8 +164,61 @@ module RailsActionTracker
|
|
159
164
|
services_accessed.uniq
|
160
165
|
end
|
161
166
|
|
167
|
+
def prepare_summary_data(logs)
|
168
|
+
services_accessed = detect_services(logs[:captured_logs])
|
169
|
+
read_models = logs[:read].to_a.uniq.sort
|
170
|
+
write_models = logs[:write].to_a.uniq.sort
|
171
|
+
controller_action = "#{logs[:controller]}##{logs[:action]}" if logs[:controller] && logs[:action]
|
172
|
+
|
173
|
+
{
|
174
|
+
read_models: read_models,
|
175
|
+
write_models: write_models,
|
176
|
+
services_accessed: services_accessed,
|
177
|
+
controller_action: controller_action
|
178
|
+
}
|
179
|
+
end
|
180
|
+
|
181
|
+
def handle_print_output(summary_data)
|
182
|
+
return unless config&.dig(:print_to_rails_log)
|
183
|
+
|
184
|
+
print_format = config&.dig(:print_format) || :table
|
185
|
+
print_colored_output, print_plain_output = generate_format_output(
|
186
|
+
print_format, summary_data, true
|
187
|
+
)
|
188
|
+
log_output(print_colored_output, print_plain_output)
|
189
|
+
end
|
190
|
+
|
191
|
+
def handle_log_file_output(summary_data)
|
192
|
+
return unless config&.dig(:write_to_file) && config[:log_file_path]
|
193
|
+
|
194
|
+
log_format = config&.dig(:log_format) || config&.dig(:print_format) || :table
|
195
|
+
process_log_file_format(log_format, summary_data)
|
196
|
+
end
|
197
|
+
|
198
|
+
def process_log_file_format(log_format, summary_data)
|
199
|
+
case log_format
|
200
|
+
when :json
|
201
|
+
accumulate_json_data(
|
202
|
+
summary_data[:read_models],
|
203
|
+
summary_data[:write_models],
|
204
|
+
summary_data[:services_accessed],
|
205
|
+
summary_data[:controller_action]
|
206
|
+
)
|
207
|
+
when :csv
|
208
|
+
accumulate_csv_data(
|
209
|
+
summary_data[:read_models],
|
210
|
+
summary_data[:write_models],
|
211
|
+
summary_data[:services_accessed],
|
212
|
+
summary_data[:controller_action]
|
213
|
+
)
|
214
|
+
else
|
215
|
+
_, log_plain_output = generate_format_output(log_format, summary_data, false)
|
216
|
+
custom_logger&.info(log_plain_output)
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
162
220
|
# rubocop:disable Style/OptionalBooleanParameter
|
163
|
-
def
|
221
|
+
def format_table_summary(read_models, write_models, services, controller_action = nil, colorize = true)
|
164
222
|
colors = setup_colors(colorize)
|
165
223
|
max_rows = [read_models.size, write_models.size, services.size].max
|
166
224
|
|
@@ -173,6 +231,160 @@ module RailsActionTracker
|
|
173
231
|
end
|
174
232
|
# rubocop:enable Style/OptionalBooleanParameter
|
175
233
|
|
234
|
+
def format_csv_summary(read_models, write_models, services, controller_action = nil)
|
235
|
+
if read_models.empty? && write_models.empty? && services.empty?
|
236
|
+
return "Action\nNo models or services accessed during this request.\n"
|
237
|
+
end
|
238
|
+
|
239
|
+
# Get all unique table names and service names
|
240
|
+
all_tables = (read_models + write_models).uniq.sort
|
241
|
+
all_services = services.uniq.sort
|
242
|
+
|
243
|
+
# Create header
|
244
|
+
header = ['Action'] + all_tables + all_services
|
245
|
+
csv_output = "#{header.join(',')}\n"
|
246
|
+
|
247
|
+
# Create data row
|
248
|
+
action_name = controller_action || 'Unknown'
|
249
|
+
row = [action_name]
|
250
|
+
|
251
|
+
# Add table columns (R for read, W for write, RW for both, - for none)
|
252
|
+
all_tables.each do |table|
|
253
|
+
row << if read_models.include?(table) && write_models.include?(table)
|
254
|
+
'RW'
|
255
|
+
elsif read_models.include?(table)
|
256
|
+
'R'
|
257
|
+
elsif write_models.include?(table)
|
258
|
+
'W'
|
259
|
+
else
|
260
|
+
'-'
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
# Add service columns (Y for accessed, - for not accessed)
|
265
|
+
all_services.each do |service|
|
266
|
+
row << (services.include?(service) ? 'Y' : '-')
|
267
|
+
end
|
268
|
+
|
269
|
+
csv_output += "#{row.join(',')}\n"
|
270
|
+
csv_output
|
271
|
+
end
|
272
|
+
|
273
|
+
def format_json_summary(read_models, write_models, services, controller_action = nil)
|
274
|
+
require 'json'
|
275
|
+
|
276
|
+
action_name = controller_action || 'Unknown'
|
277
|
+
|
278
|
+
result = {
|
279
|
+
action_name => {
|
280
|
+
'read' => read_models.uniq.sort,
|
281
|
+
'write' => write_models.uniq.sort,
|
282
|
+
'services' => services.uniq.sort
|
283
|
+
}
|
284
|
+
}
|
285
|
+
|
286
|
+
JSON.pretty_generate(result)
|
287
|
+
end
|
288
|
+
|
289
|
+
def format_json_print_summary(read_models, write_models, services, controller_action = nil)
|
290
|
+
require 'json'
|
291
|
+
|
292
|
+
action_name = controller_action || 'Unknown'
|
293
|
+
|
294
|
+
# For printing, show only current action's data in a clean format
|
295
|
+
result = {
|
296
|
+
'read' => read_models.uniq.sort,
|
297
|
+
'write' => write_models.uniq.sort,
|
298
|
+
'services' => services.uniq.sort
|
299
|
+
}
|
300
|
+
|
301
|
+
"#{action_name}: #{JSON.pretty_generate(result)}"
|
302
|
+
end
|
303
|
+
|
304
|
+
def format_csv_print_summary(read_models, write_models, services, controller_action = nil)
|
305
|
+
if read_models.empty? && write_models.empty? && services.empty?
|
306
|
+
action_name = controller_action || 'Unknown'
|
307
|
+
return "#{action_name}: No models or services accessed during this request."
|
308
|
+
end
|
309
|
+
|
310
|
+
# Get all unique table names and service names for this action only
|
311
|
+
all_tables = (read_models + write_models).uniq.sort
|
312
|
+
all_services = services.uniq.sort
|
313
|
+
|
314
|
+
# Create header
|
315
|
+
header = ['Action'] + all_tables + all_services
|
316
|
+
csv_output = "#{header.join(',')}\n"
|
317
|
+
|
318
|
+
# Create data row
|
319
|
+
action_name = controller_action || 'Unknown'
|
320
|
+
row = [action_name]
|
321
|
+
|
322
|
+
# Add table columns (R for read, W for write, RW for both, - for none)
|
323
|
+
all_tables.each do |table|
|
324
|
+
row << if read_models.include?(table) && write_models.include?(table)
|
325
|
+
'RW'
|
326
|
+
elsif read_models.include?(table)
|
327
|
+
'R'
|
328
|
+
elsif write_models.include?(table)
|
329
|
+
'W'
|
330
|
+
else
|
331
|
+
'-'
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
# Add service columns (Y for accessed, - for not accessed)
|
336
|
+
all_services.each do |service|
|
337
|
+
row << (services.include?(service) ? 'Y' : '-')
|
338
|
+
end
|
339
|
+
|
340
|
+
csv_output += "#{row.join(',')}\n"
|
341
|
+
csv_output
|
342
|
+
end
|
343
|
+
|
344
|
+
def generate_format_output(format, summary_data, colorize)
|
345
|
+
read_models = summary_data[:read_models]
|
346
|
+
write_models = summary_data[:write_models]
|
347
|
+
services = summary_data[:services_accessed]
|
348
|
+
controller_action = summary_data[:controller_action]
|
349
|
+
|
350
|
+
case format
|
351
|
+
when :csv
|
352
|
+
output = format_csv_print_summary(read_models, write_models, services, controller_action)
|
353
|
+
[output, output]
|
354
|
+
when :json
|
355
|
+
output = format_json_print_summary(read_models, write_models, services, controller_action)
|
356
|
+
[output, output]
|
357
|
+
else
|
358
|
+
colored_output = format_table_summary(read_models, write_models, services, controller_action, colorize)
|
359
|
+
plain_output = format_table_summary(read_models, write_models, services, controller_action, false)
|
360
|
+
[colored_output, plain_output]
|
361
|
+
end
|
362
|
+
end
|
363
|
+
|
364
|
+
def accumulate_json_data(read_models, write_models, services, controller_action = nil)
|
365
|
+
return unless config&.dig(:write_to_file) && config[:log_file_path]
|
366
|
+
|
367
|
+
action_name = controller_action || 'Unknown'
|
368
|
+
json_file_path = config[:log_file_path]
|
369
|
+
|
370
|
+
ensure_log_directory_exists(json_file_path)
|
371
|
+
update_json_file(json_file_path, action_name, read_models, write_models, services)
|
372
|
+
rescue StandardError => e
|
373
|
+
Rails.logger.error "Failed to accumulate JSON data: #{e.message}" if defined?(Rails)
|
374
|
+
end
|
375
|
+
|
376
|
+
def accumulate_csv_data(read_models, write_models, services, controller_action = nil)
|
377
|
+
return unless config&.dig(:write_to_file) && config[:log_file_path]
|
378
|
+
|
379
|
+
action_name = controller_action || 'Unknown'
|
380
|
+
csv_file_path = config[:log_file_path]
|
381
|
+
|
382
|
+
ensure_log_directory_exists(csv_file_path)
|
383
|
+
update_csv_file(csv_file_path, action_name, read_models, write_models, services)
|
384
|
+
rescue StandardError => e
|
385
|
+
Rails.logger.error "Failed to accumulate CSV data: #{e.message}" if defined?(Rails)
|
386
|
+
end
|
387
|
+
|
176
388
|
def setup_colors(colorize)
|
177
389
|
return { green: '', red: '', blue: '', yellow: '', reset: '' } unless colorize
|
178
390
|
return default_colors unless rails_colorized?
|
@@ -313,6 +525,211 @@ module RailsActionTracker
|
|
313
525
|
ActiveSupport::Notifications.unsubscribe(@logger_subscriber) if @logger_subscriber
|
314
526
|
@logger_subscriber = nil
|
315
527
|
end
|
528
|
+
|
529
|
+
def ensure_log_directory_exists(json_file_path)
|
530
|
+
log_dir = File.dirname(json_file_path)
|
531
|
+
FileUtils.mkdir_p(log_dir) unless Dir.exist?(log_dir)
|
532
|
+
end
|
533
|
+
|
534
|
+
def update_json_file(json_file_path, action_name, read_models, write_models, services)
|
535
|
+
File.open(json_file_path, File::RDWR | File::CREAT, 0o644) do |file|
|
536
|
+
file.flock(File::LOCK_EX)
|
537
|
+
existing_data = read_existing_json_data(file)
|
538
|
+
updated_data = merge_action_data(existing_data, action_name, read_models, write_models, services)
|
539
|
+
write_json_data(file, updated_data)
|
540
|
+
end
|
541
|
+
end
|
542
|
+
|
543
|
+
def read_existing_json_data(file)
|
544
|
+
require 'json'
|
545
|
+
file.rewind # Ensure we're at the beginning of the file
|
546
|
+
file_content = file.read.strip
|
547
|
+
return {} if file_content.empty?
|
548
|
+
|
549
|
+
JSON.parse(file_content)
|
550
|
+
rescue JSON::ParserError
|
551
|
+
{}
|
552
|
+
end
|
553
|
+
|
554
|
+
def merge_action_data(existing_data, action_name, read_models, write_models, services)
|
555
|
+
new_read_models = (read_models || []).uniq.sort
|
556
|
+
new_write_models = (write_models || []).uniq.sort
|
557
|
+
new_services = (services || []).uniq.sort
|
558
|
+
|
559
|
+
if existing_data[action_name]
|
560
|
+
merge_with_existing_action(existing_data, action_name, new_read_models, new_write_models, new_services)
|
561
|
+
else
|
562
|
+
add_new_action(existing_data, action_name, new_read_models, new_write_models, new_services)
|
563
|
+
end
|
564
|
+
|
565
|
+
existing_data
|
566
|
+
end
|
567
|
+
|
568
|
+
def merge_with_existing_action(existing_data, action_name, new_read, new_write, new_services)
|
569
|
+
existing_read = existing_data[action_name]['read'] || []
|
570
|
+
existing_write = existing_data[action_name]['write'] || []
|
571
|
+
existing_services = existing_data[action_name]['services'] || []
|
572
|
+
|
573
|
+
existing_data[action_name] = {
|
574
|
+
'read' => (existing_read + new_read).uniq.sort,
|
575
|
+
'write' => (existing_write + new_write).uniq.sort,
|
576
|
+
'services' => (existing_services + new_services).uniq.sort
|
577
|
+
}
|
578
|
+
end
|
579
|
+
|
580
|
+
def add_new_action(existing_data, action_name, new_read, new_write, new_services)
|
581
|
+
existing_data[action_name] = {
|
582
|
+
'read' => new_read,
|
583
|
+
'write' => new_write,
|
584
|
+
'services' => new_services
|
585
|
+
}
|
586
|
+
end
|
587
|
+
|
588
|
+
def write_json_data(file, data)
|
589
|
+
require 'json'
|
590
|
+
file.rewind
|
591
|
+
file.write(JSON.pretty_generate(data))
|
592
|
+
file.truncate(file.pos)
|
593
|
+
end
|
594
|
+
|
595
|
+
def update_csv_file(csv_file_path, action_name, read_models, write_models, services)
|
596
|
+
File.open(csv_file_path, File::RDWR | File::CREAT, 0o644) do |file|
|
597
|
+
file.flock(File::LOCK_EX)
|
598
|
+
existing_data = read_existing_csv_data(file)
|
599
|
+
updated_data = merge_csv_action_data(existing_data, action_name, read_models, write_models, services)
|
600
|
+
write_csv_data(file, updated_data)
|
601
|
+
end
|
602
|
+
end
|
603
|
+
|
604
|
+
def read_existing_csv_data(file)
|
605
|
+
require 'csv'
|
606
|
+
file.rewind
|
607
|
+
file_content = file.read.strip
|
608
|
+
return { headers: ['Action'], rows: {} } if file_content.empty?
|
609
|
+
|
610
|
+
begin
|
611
|
+
csv_data = CSV.parse(file_content, headers: true)
|
612
|
+
headers = csv_data.headers
|
613
|
+
rows = {}
|
614
|
+
|
615
|
+
csv_data.each do |row|
|
616
|
+
action = row['Action']
|
617
|
+
rows[action] = row.to_h if action
|
618
|
+
end
|
619
|
+
|
620
|
+
{ headers: headers, rows: rows }
|
621
|
+
rescue CSV::MalformedCSVError
|
622
|
+
{ headers: ['Action'], rows: {} }
|
623
|
+
end
|
624
|
+
end
|
625
|
+
|
626
|
+
def merge_csv_action_data(existing_data, action_name, read_models, write_models, services)
|
627
|
+
cleaned_data = sanitize_csv_inputs(read_models, write_models, services)
|
628
|
+
all_combined = build_combined_headers(existing_data, cleaned_data)
|
629
|
+
new_headers = ['Action'] + all_combined
|
630
|
+
|
631
|
+
if existing_data[:rows][action_name]
|
632
|
+
merge_existing_csv_row(existing_data[:rows][action_name], all_combined, cleaned_data)
|
633
|
+
else
|
634
|
+
existing_data[:rows][action_name] = create_new_csv_row(action_name, all_combined, cleaned_data)
|
635
|
+
end
|
636
|
+
|
637
|
+
existing_data[:headers] = new_headers
|
638
|
+
existing_data
|
639
|
+
end
|
640
|
+
|
641
|
+
def sanitize_csv_inputs(read_models, write_models, services)
|
642
|
+
{
|
643
|
+
read_models: (read_models || []).uniq.sort,
|
644
|
+
write_models: (write_models || []).uniq.sort,
|
645
|
+
services: (services || []).uniq.sort,
|
646
|
+
all_tables: ((read_models || []) + (write_models || [])).uniq.sort
|
647
|
+
}
|
648
|
+
end
|
649
|
+
|
650
|
+
def build_combined_headers(existing_data, cleaned_data)
|
651
|
+
existing_headers = existing_data[:headers] || ['Action']
|
652
|
+
existing_names = existing_headers[1..] || []
|
653
|
+
(cleaned_data[:all_tables] + cleaned_data[:services] + existing_names).uniq.sort
|
654
|
+
end
|
655
|
+
|
656
|
+
def merge_existing_csv_row(existing_row, all_combined, cleaned_data)
|
657
|
+
all_combined.each do |name|
|
658
|
+
if cleaned_data[:all_tables].include?(name)
|
659
|
+
current_value = existing_row[name] || '-'
|
660
|
+
new_value = determine_table_access(name, cleaned_data[:read_models], cleaned_data[:write_models])
|
661
|
+
existing_row[name] = merge_table_access(current_value, new_value)
|
662
|
+
elsif cleaned_data[:services].include?(name)
|
663
|
+
existing_row[name] = 'Y'
|
664
|
+
end
|
665
|
+
end
|
666
|
+
end
|
667
|
+
|
668
|
+
def create_new_csv_row(action_name, all_combined, cleaned_data)
|
669
|
+
new_row = { 'Action' => action_name }
|
670
|
+
all_combined.each do |name|
|
671
|
+
new_row[name] = if cleaned_data[:all_tables].include?(name)
|
672
|
+
determine_table_access(name, cleaned_data[:read_models], cleaned_data[:write_models])
|
673
|
+
elsif cleaned_data[:services].include?(name)
|
674
|
+
'Y'
|
675
|
+
else
|
676
|
+
'-'
|
677
|
+
end
|
678
|
+
end
|
679
|
+
new_row
|
680
|
+
end
|
681
|
+
|
682
|
+
def determine_table_access(table_name, read_models, write_models)
|
683
|
+
read_access = read_models.include?(table_name)
|
684
|
+
write_access = write_models.include?(table_name)
|
685
|
+
|
686
|
+
if read_access && write_access
|
687
|
+
'RW'
|
688
|
+
elsif read_access
|
689
|
+
'R'
|
690
|
+
elsif write_access
|
691
|
+
'W'
|
692
|
+
else
|
693
|
+
'-'
|
694
|
+
end
|
695
|
+
end
|
696
|
+
|
697
|
+
def merge_table_access(current, new_access)
|
698
|
+
return new_access if current == '-'
|
699
|
+
return current if new_access == '-'
|
700
|
+
|
701
|
+
# Convert to sets for easier merging
|
702
|
+
current_ops = current == 'RW' ? %w[R W] : [current]
|
703
|
+
new_ops = new_access == 'RW' ? %w[R W] : [new_access]
|
704
|
+
|
705
|
+
merged_ops = (current_ops + new_ops).uniq.sort
|
706
|
+
|
707
|
+
case merged_ops
|
708
|
+
when %w[R W]
|
709
|
+
'RW'
|
710
|
+
when ['R']
|
711
|
+
'R'
|
712
|
+
when ['W']
|
713
|
+
'W'
|
714
|
+
else
|
715
|
+
current
|
716
|
+
end
|
717
|
+
end
|
718
|
+
|
719
|
+
def write_csv_data(file, data)
|
720
|
+
require 'csv'
|
721
|
+
file.rewind
|
722
|
+
|
723
|
+
output = CSV.generate do |csv|
|
724
|
+
csv << data[:headers]
|
725
|
+
data[:rows].each_value do |row|
|
726
|
+
csv << data[:headers].map { |header| row[header] || '-' }
|
727
|
+
end
|
728
|
+
end
|
729
|
+
|
730
|
+
file.write(output)
|
731
|
+
file.truncate(file.pos)
|
732
|
+
end
|
316
733
|
end
|
317
734
|
end
|
318
735
|
end
|
metadata
CHANGED
@@ -1,13 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rails_action_tracker
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Deepak Mahakale
|
8
|
+
autorequire:
|
8
9
|
bindir: exe
|
9
10
|
cert_chain: []
|
10
|
-
date:
|
11
|
+
date: 2025-09-03 00:00:00.000000000 Z
|
11
12
|
dependencies:
|
12
13
|
- !ruby/object:Gem::Dependency
|
13
14
|
name: actionpack
|
@@ -67,6 +68,7 @@ files:
|
|
67
68
|
- DEVELOPMENT.md
|
68
69
|
- LICENSE.txt
|
69
70
|
- README.md
|
71
|
+
- RELEASE_NOTES.md
|
70
72
|
- Rakefile
|
71
73
|
- lib/generators/rails_action_tracker/install_generator.rb
|
72
74
|
- lib/generators/rails_action_tracker/templates/initializer.rb
|
@@ -84,6 +86,7 @@ metadata:
|
|
84
86
|
homepage_uri: https://github.com/deepakmahakale/rails_action_tracker
|
85
87
|
source_code_uri: https://github.com/deepakmahakale/rails_action_tracker
|
86
88
|
changelog_uri: https://github.com/deepakmahakale/rails_action_tracker/blob/master/CHANGELOG.md
|
89
|
+
post_install_message:
|
87
90
|
rdoc_options: []
|
88
91
|
require_paths:
|
89
92
|
- lib
|
@@ -98,7 +101,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
98
101
|
- !ruby/object:Gem::Version
|
99
102
|
version: '0'
|
100
103
|
requirements: []
|
101
|
-
rubygems_version: 3.
|
104
|
+
rubygems_version: 3.5.22
|
105
|
+
signing_key:
|
102
106
|
specification_version: 4
|
103
107
|
summary: Track ActiveRecord model operations and service usage during Rails action
|
104
108
|
calls
|