class U3d::LogAnalyzer

Analyzes log by filtering output along a set of rules rubocop:disable ClassLength, PerceivedComplexity, BlockNesting

Constants

MEMORY_SIZE
RULES_PATH

Public Class Methods

new() click to toggle source
# File lib/u3d/log_analyzer.rb, line 33
def initialize
  @lines_memory = Array.new(MEMORY_SIZE)
  @active_phase = nil
  @active_rule = nil
  @context = {}
  @rule_lines_buffer = []
  @generic_rules, @phases = load_rules
end

Public Instance Methods

load_rules() click to toggle source
# File lib/u3d/log_analyzer.rb, line 42
def load_rules
  generic_rules = {}
  phases = {}

  data = JSON.parse(File.read(rules_path))

  if data['GENERAL'] && data['GENERAL']['active']
    data['GENERAL']['rules'].each do |rn, r|
      generic_rules[rn] = r if parse_rule(r)
    end
  end
  data.delete('GENERAL')
  data.each do |name, phase|
    # Phase parsing
    next unless phase['active']
    next if phase['phase_start_pattern'].nil?
    phase['phase_start_pattern'] = Regexp.new phase['phase_start_pattern']
    phase['phase_end_pattern'] = Regexp.new phase['phase_end_pattern'] if phase['phase_end_pattern']
    phase.delete('comment')
    # Rules parsing
    temp_rules = {}
    unless phase['silent'] == true
      phase['rules'].each do |rn, r|
        temp_rules[rn] = r if parse_rule(r)
      end
    end
    phase['rules'] = temp_rules
    phases[name] = phase
  end
  return generic_rules, phases
end
parse_line(line) click to toggle source
# File lib/u3d/log_analyzer.rb, line 74
def parse_line(line)
  # Insert new line and remove last stored line
  @lines_memory.push(line).shift

  # Check if phase is changing
  @phases.each do |name, phase|
    next if name == @active_phase
    next unless line =~ phase['phase_start_pattern']
    finish_phase if @active_phase
    @active_phase = name
    UI.verbose("--- Beginning #{name} phase ---")
    break
  end

  apply_ruleset = lambda do |ruleset, header|
    # Apply the active rule
    if @active_rule && ruleset[@active_rule]
      rule = ruleset[@active_rule]
      pattern = rule['end_pattern']

      # Is it the end of the rule?
      if line =~ pattern
        unless @rule_lines_buffer.empty?
          @rule_lines_buffer.each do |l|
            UI.send(rule['type'], "[#{header}] " + l)
          end
        end
        if rule['end_message'] != false
          if rule['end_message']
            match = line.match(pattern)
            params = match.names.map(&:to_sym).zip(match.captures).to_h
            message = inject(rule['end_message'], params: params)
          else
            message = line.chomp
          end
          message = "[#{header}] " + message
          UI.send(rule['type'], message)
        end
        @active_rule = nil
        @context.clear
        @rule_lines_buffer.clear

      # It's not the end of the rules, should the line be stored?
      elsif rule['store_lines']
        match = false
        if rule['ignore_lines']
          rule['ignore_lines'].each do |pat|
            if line =~ pat
              match = true
              break
            end
          end
        end
        @rule_lines_buffer << line.chomp unless match
      end
    end

    # If there is no active rule, try to apply a new one
    if @active_rule.nil?
      ruleset.each do |rn, r|
        pattern = r['start_pattern']
        next unless line =~ pattern
        @active_rule = rn if r['end_pattern']
        match = line.match(pattern)
        @context = match.names.map(&:to_sym).zip(match.captures).to_h
        if r['fetch_line_at_index'] || r['fetch_first_line_not_matching']
          if r['fetch_line_at_index']
            fetched_line = @lines_memory.reverse[r['fetch_line_at_index']]
          else
            fetched_line = nil
            @lines_memory.reverse.each do |l|
              match = false
              r['fetch_first_line_not_matching'].each do |pat|
                next unless l =~ pat
                match = true
                break
              end
              next if match
              fetched_line = l
              break
            end
          end
          if fetched_line
            if r['fetched_line_pattern']
              match = fetched_line.match(r['fetched_line_pattern'])
              @context.merge!(match.names.map(&:to_sym).zip(match.captures).to_h)
            end
            if r['fetched_line_message'] != false
              message = if r['fetched_line_message']
                          inject(r['fetched_line_message'])
                        else
                          fetched_line.chomp
                        end
              message = "[#{header}] " + message
              UI.send(r['type'], message)
            end
          end
        end
        if r['start_message'] != false
          message = if r['start_message']
                      inject(r['start_message'])
                    else
                      line.chomp
                    end
          message = "[#{header}] " + message
          UI.send(r['type'], message)
        end
        break
      end
    end
  end

  if @active_phase
    apply_ruleset.call(@phases[@active_phase]['rules'], @active_phase)
    finish_phase if @phases[@active_phase]['phase_end_pattern'] && @phases[@active_phase]['phase_end_pattern'] =~ line
  end
  apply_ruleset.call(@generic_rules, 'GENERAL')
end

Private Instance Methods

finish_phase() click to toggle source
# File lib/u3d/log_analyzer.rb, line 195
def finish_phase
  if @active_rule
    # Active rule should be finished
    # If it is still active during phase change, it means that something went wrong
    context = @lines_memory.map { |l| "> #{l}" }.join('')
    UI.error("[#{@active_phase}] Could not finish active rule '#{@active_rule}'. Aborting it. Context:\n#{context}")

    U3d::FailureReporter.report(
      failure_type: "PRETTIFIER",
      failure_message: "Could not finish rule",
      data: {
        phase: @active_phase,
        rule: @active_rule,
        context: context.split("\n")
      }
    )

    @active_rule = nil
  end
  UI.verbose("--- Ending #{@active_phase} phase ---")
  @active_phase = nil
  @context.clear
  @rule_lines_buffer.clear
end
inject(string, params: {}) click to toggle source
# File lib/u3d/log_analyzer.rb, line 230
def inject(string, params: {})
  message = "This is a default message."
  begin
    message = string % params.merge(@context)
  rescue KeyError => e
    UI.error("[U3D] Rule '#{@active_rule}' captures were incomplete: #{e.message}")
  end
  message
end
parse_rule(r) click to toggle source
# File lib/u3d/log_analyzer.rb, line 240
def parse_rule(r)
  return false unless r['active']
  return false if r['start_pattern'].nil?
  r['start_pattern'] = Regexp.new r['start_pattern']
  r['end_pattern'] = Regexp.new r['end_pattern'] if r['end_pattern']
  if r['fetch_line_at_index']
    r.delete('fetch_line_at_index') if r['fetch_line_at_index'] >= MEMORY_SIZE
    r.delete('fetch_line_at_index') if r['fetch_line_at_index'] <= 0
  elsif r['fetch_first_line_not_matching']
    r['fetch_first_line_not_matching'].map! { |pat| Regexp.new pat }
  end
  if r['fetch_line_at_index'] || r['fetch_first_line_not_matching']
    r['fetched_line_pattern'] = Regexp.new r['fetched_line_pattern'] if r['fetched_line_pattern']
  end
  r['type'] = 'important' if r['type'] == 'warning'
  r['type'] = 'message' if r['type'] && r['type'] != 'error' && r['type'] != 'important' && r['type'] != 'success'
  r['type'] ||= 'message'
  r['ignore_lines'].map! { |pat| Regexp.new pat } if r['ignore_lines']
  true
end
rules_path() click to toggle source
# File lib/u3d/log_analyzer.rb, line 220
def rules_path
  path = ENV["U3D_RULES_PATH"]
  unless path.nil?
    UI.user_error!("Specified rules path '#{path}' isn't a file") unless File.exist? path
    UI.message("Using #{path} for prettify rules path")
  end
  path = RULES_PATH if path.nil?
  path
end