Module: LogChanges::Merge

Defined in:
lib/log_changes/merge.rb

Instance Method Summary collapse

Instance Method Details

#associate_logfiles(log_dir, record_changes = false) ⇒ Object

Returns a hash whose keys are the common log names and whose values are arrays of file paths, e.g.:

                  "ajax_errors" => [
    [ 0] "/Users/seanhuber/Downloads/record_changes/2015.08_ajax_errors.log",
    [ 1] "/Users/seanhuber/Downloads/record_changes/2015.09_ajax_errors.log",
    [ 2] "/Users/seanhuber/Downloads/record_changes/2015.10_ajax_errors.log"
],
            "care_plan_updates" => [
    [ 0] "/Users/seanhuber/Downloads/record_changes/2015.08_care_plan_updates.log",
    [ 1] "/Users/seanhuber/Downloads/record_changes/2015.09_care_plan_updates.log",
    [ 2] "/Users/seanhuber/Downloads/record_changes/2015.10_care_plan_updates.log"
],
                 "eval_updates" => [
    [ 0] "/Users/seanhuber/Downloads/record_changes/2015.08_eval_updates.log",
    [ 1] "/Users/seanhuber/Downloads/record_changes/2015.09_eval_updates.log",
    [ 2] "/Users/seanhuber/Downloads/record_changes/2015.10_eval_updates.log"
],



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/log_changes/merge.rb', line 52

def associate_logfiles( log_dir, record_changes = false )
  raise "Directory does not exist: #{log_dir}" unless File.directory?( log_dir )

  # scans for logfiles prefixed with a month stamp like "2016.03_"
  ret_h = {}
  search_path = record_changes ? File.join(log_dir, '**', 'record_changes', '*.log') : File.join(log_dir, '**', '20*_*.log')
  Dir.glob(search_path) do |log_fp|
    next if !record_changes && (log_fp.include?('record_changes') || log_fp.include?('import')) # TODO: include import
    month_stamp = File.basename(log_fp).split('_').first
    next unless month_stamp.length == 7 && month_stamp[4] == '.'
    begin
      DateTime.strptime(month_stamp, '%Y.%m')
    rescue ArgumentError
      next
    end
    log_class = File.basename(log_fp, File.extname(log_fp)).split('_')[1..-1].join('_')
    ret_h[log_class] ||= []
    ret_h[log_class] << log_fp
  end
  ret_h
end

#first_char_is_num?(str) ⇒ Boolean

Returns:

  • (Boolean)


119
120
121
# File 'lib/log_changes/merge.rb', line 119

def first_char_is_num?( str )
  !(str[0] =~ /^\d/).nil?
end

#logfile_entries(logfile) ⇒ Object

Returns an array of hashes containing time and text of each log entry, e.g., [

[0] {
    :time => Thu, 17 Sep 2015 12:20:00 -0500,
    :text => "Logged by user: (bm25671) John Doe\nSome message was logged"
},
[1] {
    :time => Thu, 17 Sep 2015 12:27:00 -0500,
    :text => "Logged by user: (bm25671) Jane Smith\nLorem ipsum..."
},
[2] {
    :time => Thu, 17 Sep 2015 13:24:00 -0500,
    :text => "Logged by user: (vr16208) Charlie Williams\nblah blah blah"
}

]

Returns nil if entries couldn’t be parsed (unable to find lines structured DateTime)



91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/log_changes/merge.rb', line 91

def logfile_entries( logfile )
  lines = File.open( logfile ).map{|l| l}
  entry_indexes = [] # lines that are just a timestamp
  lines.each_with_index do |line, idx|
    next unless first_char_is_num?( line )
    dt = begin
      DateTime.strptime(line.strip, "%m/%d/%Y at %l:%M %p (%Z)")
    rescue ArgumentError
      nil
    end
    next if dt.nil?
    next if idx > 0 && lines[idx-1].strip.present? && !lines[idx-1].strip.starts_with?('Logged by user:')
    entry_indexes << idx
  end
  return nil if entry_indexes.empty?

  # TODO: refactor (shouldn't need to loop over the logfile twice)
  entries = []
  entry_indexes.each_with_index do |entry_idx, entry_indexes_idx|
    end_idx = entry_indexes_idx == (entry_indexes.length - 1) ? (lines.length-1) : (entry_indexes[entry_indexes_idx+1]-1)
    end_idx -= 1 if lines[end_idx].starts_with?('Logged by user:')
    entry_text = lines[entry_idx+1..end_idx].join
    entry_text = lines[entry_idx-1] + entry_text if entry_idx > 0 && lines[entry_idx-1].starts_with?('Logged by user:')
    entries << {:time => DateTime.strptime(lines[entry_idx].strip, "%m/%d/%Y at %l:%M %p (%Z)"), :text => entry_text.strip}
  end
  entries
end

#merge_logfiles(log_dir) ⇒ Object



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# File 'lib/log_changes/merge.rb', line 3

def merge_logfiles( log_dir )
  [true, false].each do |record_changes|
    logfiles = associate_logfiles( log_dir, record_changes )

    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' : ''}") )
    FileUtils.mkdir_p( aggregate_log_dir )

    logfiles.each do |log_type, logfile_paths|
      entries = []
      logfile_paths.each do |lf|
        lf_entries = logfile_entries(lf)
        if lf_entries.nil?
          puts "\nSKIPPING logfile (unable to parse entries): #{lf}\n\n"
        else
          entries += lf_entries
        end
      end
      next if entries.empty?
      entries.sort!{|e1, e2| e1[:time] <=> e2[:time]}
      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")
      File.open( merged_log, 'w' ) do |file|
        entries.each do |entry|
          file.write "#{entry[:time].strftime('%-m/%-d/%Y at %-l:%M %p (%Z)')}\n"
          file.write "#{entry[:text]}\n\n"
        end
      end
      puts "Merged log: #{merged_log}\n  #{entries.length} #{'entry'.pluralize(entries.length)}"
    end
  end
end