log_changes 0.1.0 → 1.0.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/README.md +39 -11
- data/lib/log_changes.rb +2 -1
- data/lib/log_changes/merge.rb +123 -0
- data/lib/log_changes/version.rb +1 -1
- data/lib/tasks/merge_logfiles.rake +9 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b1470104760459fde7eb28abce175dc5c94e2157
|
4
|
+
data.tar.gz: f1e02001e4ea961d395d8b3301767b807764dd4b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7bb15684947c874a4f972bc53b09fa81c91c2249fd3994020aad8eff94bcdaf26a69d180a1628048ae039bb3d28907dff529fbb3f23d2648159a279d259fd072
|
7
|
+
data.tar.gz: f47441a7a0d1e087ed50242c2b013fce2905d9f38cbf4a930409b2fbbcfe486f87729eefdc35dd352682ffe7dc162b85f165afce3ef952da7bbd4f0ae86d5f0d
|
data/README.md
CHANGED
@@ -1,8 +1,44 @@
|
|
1
1
|
# LogChanges
|
2
|
-
|
2
|
+
|
3
|
+
`log_changes` is a simple gem that writes `ActiveRecord#changes` contents to model-dedicated logfiles.
|
3
4
|
|
4
5
|
## Usage
|
5
|
-
|
6
|
+
To log attribute changes for a model simply add `include LogChanges::Base` to an `ActiveRecord` model:
|
7
|
+
|
8
|
+
```ruby
|
9
|
+
class User < ApplicationRecord
|
10
|
+
include LogChanges::Base
|
11
|
+
|
12
|
+
def to_s
|
13
|
+
"#{first_name} #{last_name}"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
```
|
17
|
+
|
18
|
+
In this example, if the `User` record with id = 1 had his name updated you may see an entry like this appear `logs/record_changes/2016.12_User.log`:
|
19
|
+
|
20
|
+
```
|
21
|
+
12/29/2016 at 3:48 PM (UTC)
|
22
|
+
Updated User {id: 1} John Smith
|
23
|
+
first_name:
|
24
|
+
FROM: Johnny
|
25
|
+
TO: John
|
26
|
+
last_name:
|
27
|
+
FROM: Smithers
|
28
|
+
TO: Smith
|
29
|
+
```
|
30
|
+
|
31
|
+
Logfiles are prefixed with a month stamp (to prevent them from getting too big over time).
|
32
|
+
|
33
|
+
### Aggregating logfiles
|
34
|
+
|
35
|
+
If your Rails app runs in a load-balanced distributed environment, you may wish to aggregate logs from multiple servers. `log_changes` comes with a rake task for this purpose:
|
36
|
+
|
37
|
+
```
|
38
|
+
rake log_changes:merge['/path/to/logs/directory']
|
39
|
+
```
|
40
|
+
|
41
|
+
Make sure the path you pass to the task has multiple subfolders (one per server), each with a `record_changes` directory containing the log files.
|
6
42
|
|
7
43
|
## Installation
|
8
44
|
Add this line to your application's Gemfile:
|
@@ -13,16 +49,8 @@ gem 'log_changes'
|
|
13
49
|
|
14
50
|
And then execute:
|
15
51
|
```bash
|
16
|
-
$ bundle
|
52
|
+
$ bundle install
|
17
53
|
```
|
18
54
|
|
19
|
-
Or install it yourself as:
|
20
|
-
```bash
|
21
|
-
$ gem install log_changes
|
22
|
-
```
|
23
|
-
|
24
|
-
## Contributing
|
25
|
-
Contribution directions go here.
|
26
|
-
|
27
55
|
## License
|
28
56
|
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/lib/log_changes.rb
CHANGED
@@ -0,0 +1,123 @@
|
|
1
|
+
module LogChanges
|
2
|
+
module Merge
|
3
|
+
def merge_logfiles( log_dir )
|
4
|
+
[true, false].each do |record_changes|
|
5
|
+
logfiles = associate_logfiles( log_dir, record_changes )
|
6
|
+
|
7
|
+
aggregate_log_dir = File.expand_path( File.join(log_dir, "../#{Time.now.in_time_zone.strftime('%Y-%m-%d_%H%M')}_aggregated_logs#{record_changes ? '/record_changes' : ''}") )
|
8
|
+
FileUtils.mkdir_p( aggregate_log_dir )
|
9
|
+
|
10
|
+
logfiles.each do |log_type, logfile_paths|
|
11
|
+
entries = []
|
12
|
+
logfile_paths.each do |lf|
|
13
|
+
lf_entries = logfile_entries(lf)
|
14
|
+
if lf_entries.nil?
|
15
|
+
puts "\nSKIPPING logfile (unable to parse entries): #{lf}\n\n"
|
16
|
+
else
|
17
|
+
entries += lf_entries
|
18
|
+
end
|
19
|
+
end
|
20
|
+
next if entries.empty?
|
21
|
+
entries.sort!{|e1, e2| e1[:time] <=> e2[:time]}
|
22
|
+
merged_log = File.join(aggregate_log_dir, "#{log_type}_#{entries.first[:time].in_time_zone.strftime('%Y-%m-%d_%H%M')}_-_#{entries.last[:time].in_time_zone.strftime('%Y-%m-%d_%H%M')}.log")
|
23
|
+
File.open( merged_log, 'w' ) do |file|
|
24
|
+
entries.each do |entry|
|
25
|
+
file.write "#{entry[:time].strftime('%-m/%-d/%Y at %-l:%M %p (%Z)')}\n"
|
26
|
+
file.write "#{entry[:text]}\n\n"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
puts "Merged log: #{merged_log}\n #{entries.length} #{'entry'.pluralize(entries.length)}"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Returns a hash whose keys are the common log names and whose values are arrays of file paths, e.g.:
|
35
|
+
# {
|
36
|
+
# "ajax_errors" => [
|
37
|
+
# [ 0] "/Users/seanhuber/Downloads/record_changes/2015.08_ajax_errors.log",
|
38
|
+
# [ 1] "/Users/seanhuber/Downloads/record_changes/2015.09_ajax_errors.log",
|
39
|
+
# [ 2] "/Users/seanhuber/Downloads/record_changes/2015.10_ajax_errors.log"
|
40
|
+
# ],
|
41
|
+
# "care_plan_updates" => [
|
42
|
+
# [ 0] "/Users/seanhuber/Downloads/record_changes/2015.08_care_plan_updates.log",
|
43
|
+
# [ 1] "/Users/seanhuber/Downloads/record_changes/2015.09_care_plan_updates.log",
|
44
|
+
# [ 2] "/Users/seanhuber/Downloads/record_changes/2015.10_care_plan_updates.log"
|
45
|
+
# ],
|
46
|
+
# "eval_updates" => [
|
47
|
+
# [ 0] "/Users/seanhuber/Downloads/record_changes/2015.08_eval_updates.log",
|
48
|
+
# [ 1] "/Users/seanhuber/Downloads/record_changes/2015.09_eval_updates.log",
|
49
|
+
# [ 2] "/Users/seanhuber/Downloads/record_changes/2015.10_eval_updates.log"
|
50
|
+
# ],
|
51
|
+
# }
|
52
|
+
def associate_logfiles( log_dir, record_changes = false )
|
53
|
+
raise "Directory does not exist: #{log_dir}" unless File.directory?( log_dir )
|
54
|
+
|
55
|
+
# scans for logfiles prefixed with a month stamp like "2016.03_"
|
56
|
+
ret_h = {}
|
57
|
+
search_path = record_changes ? File.join(log_dir, '**', 'record_changes', '*.log') : File.join(log_dir, '**', '20*_*.log')
|
58
|
+
Dir.glob(search_path) do |log_fp|
|
59
|
+
next if !record_changes && (log_fp.include?('record_changes') || log_fp.include?('import')) # TODO: include import
|
60
|
+
month_stamp = File.basename(log_fp).split('_').first
|
61
|
+
next unless month_stamp.length == 7 && month_stamp[4] == '.'
|
62
|
+
begin
|
63
|
+
DateTime.strptime(month_stamp, '%Y.%m')
|
64
|
+
rescue ArgumentError
|
65
|
+
next
|
66
|
+
end
|
67
|
+
log_class = File.basename(log_fp, File.extname(log_fp)).split('_')[1..-1].join('_')
|
68
|
+
ret_h[log_class] ||= []
|
69
|
+
ret_h[log_class] << log_fp
|
70
|
+
end
|
71
|
+
ret_h
|
72
|
+
end
|
73
|
+
|
74
|
+
# Returns an array of hashes containing time and text of each log entry, e.g.,
|
75
|
+
# [
|
76
|
+
# [0] {
|
77
|
+
# :time => Thu, 17 Sep 2015 12:20:00 -0500,
|
78
|
+
# :text => "Logged by user: (bm25671) John Doe\nSome message was logged"
|
79
|
+
# },
|
80
|
+
# [1] {
|
81
|
+
# :time => Thu, 17 Sep 2015 12:27:00 -0500,
|
82
|
+
# :text => "Logged by user: (bm25671) Jane Smith\nLorem ipsum..."
|
83
|
+
# },
|
84
|
+
# [2] {
|
85
|
+
# :time => Thu, 17 Sep 2015 13:24:00 -0500,
|
86
|
+
# :text => "Logged by user: (vr16208) Charlie Williams\nblah blah blah"
|
87
|
+
# }
|
88
|
+
# ]
|
89
|
+
#
|
90
|
+
# Returns nil if entries couldn't be parsed (unable to find lines structured DateTime)
|
91
|
+
def logfile_entries( logfile )
|
92
|
+
lines = File.open( logfile ).map{|l| l}
|
93
|
+
entry_indexes = [] # lines that are just a timestamp
|
94
|
+
lines.each_with_index do |line, idx|
|
95
|
+
next unless first_char_is_num?( line )
|
96
|
+
dt = begin
|
97
|
+
DateTime.strptime(line.strip, "%m/%d/%Y at %l:%M %p (%Z)")
|
98
|
+
rescue ArgumentError
|
99
|
+
nil
|
100
|
+
end
|
101
|
+
next if dt.nil?
|
102
|
+
next if idx > 0 && lines[idx-1].strip.present? && !lines[idx-1].strip.starts_with?('Logged by user:')
|
103
|
+
entry_indexes << idx
|
104
|
+
end
|
105
|
+
return nil if entry_indexes.empty?
|
106
|
+
|
107
|
+
# TODO: refactor (shouldn't need to loop over the logfile twice)
|
108
|
+
entries = []
|
109
|
+
entry_indexes.each_with_index do |entry_idx, entry_indexes_idx|
|
110
|
+
end_idx = entry_indexes_idx == (entry_indexes.length - 1) ? (lines.length-1) : (entry_indexes[entry_indexes_idx+1]-1)
|
111
|
+
end_idx -= 1 if lines[end_idx].starts_with?('Logged by user:')
|
112
|
+
entry_text = lines[entry_idx+1..end_idx].join
|
113
|
+
entry_text = lines[entry_idx-1] + entry_text if entry_idx > 0 && lines[entry_idx-1].starts_with?('Logged by user:')
|
114
|
+
entries << {:time => DateTime.strptime(lines[entry_idx].strip, "%m/%d/%Y at %l:%M %p (%Z)"), :text => entry_text.strip}
|
115
|
+
end
|
116
|
+
entries
|
117
|
+
end
|
118
|
+
|
119
|
+
def first_char_is_num?( str )
|
120
|
+
!(str[0] =~ /^\d/).nil?
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
data/lib/log_changes/version.rb
CHANGED
@@ -0,0 +1,9 @@
|
|
1
|
+
namespace :log_changes do
|
2
|
+
desc 'aggregates logfiles created with log_changes'
|
3
|
+
task :merge, [:logs_path] do |_, args|
|
4
|
+
raise ArgumentError, 'No logs directory specified' unless args[:logs_path].present? && File.directory?(args[:logs_path])
|
5
|
+
include LogChanges::Merge
|
6
|
+
Time.zone = 'Central Time (US & Canada)' # for timestamping log files (TODO: is this needed?)
|
7
|
+
merge_logfiles args[:logs_path]
|
8
|
+
end
|
9
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: log_changes
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sean Huber
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-12-
|
11
|
+
date: 2016-12-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -136,8 +136,10 @@ files:
|
|
136
136
|
- lib/log_changes.rb
|
137
137
|
- lib/log_changes/base.rb
|
138
138
|
- lib/log_changes/engine.rb
|
139
|
+
- lib/log_changes/merge.rb
|
139
140
|
- lib/log_changes/version.rb
|
140
141
|
- lib/tasks/log_changes_tasks.rake
|
142
|
+
- lib/tasks/merge_logfiles.rake
|
141
143
|
- log_changes.gemspec
|
142
144
|
- spec/dummy/Rakefile
|
143
145
|
- spec/dummy/app/assets/config/manifest.js
|