Repository
Munin (contrib)
Last change
2020-08-25
Graph Categories
Keywords
Language
Ruby
Authors

ejabberd_scanlog

Example graph: day

Sadly there is no documentation for this plugin.

#!/usr/bin/env ruby
require 'yaml'

# ejabberd_scanlog revision 2 (Mar 2012)
#
# Scans ejabberd 2.1.x log for known error signatures and counts them
#
# Required privileges: read ejabberd log (user ejabberd or, in some cases, root)
#
# OS: Unix
#
# Configuration:
# 	env.log: ejabberd log file (defaults to /var...)
#
# Author: Artem Sheremet <dot.doom@gmail.com>

#
# Run with 'debug' argument to initiate full log rescan.
# This will also print out unparsed log entries to stderr.
# Cache file will be untouched.
#

LOG_FILE = ENV['log'] || '/var/log/ejabberd/ejabberd.log'
CACHE_FILE = '/tmp/ejabberd_scanlog_cache'.freeze # cache file position

DEFAULT_CACHE = { start: 0 }.freeze

$debug_mode = ARGV.first == 'debug'

if $debug_mode
  log_info = DEFAULT_CACHE
else
  begin
    log_info = YAML.load IO.read(CACHE_FILE)
  rescue StandardError
    log_info = DEFAULT_CACHE
  end

  if File.size(LOG_FILE) < log_info[:start]
    # logrotate?
    log_info = DEFAULT_CACHE
  end
end

if ARGV.first == 'reset'
  log_info = { start: File.size(LOG_FILE) - 1 }
  puts 'Log reset'
end

new_data = ''
File.open(LOG_FILE, 'rb') do |flog|
  flog.seek(log_info[:start])
  new_data = flog.read
end

KNOWN_LOG_TYPES = [
  # each element is an instance of Array. 1st item: error description, others: text to search log for
  ['EJAB-1482 Crash when waiting for item',
   ['wait_for_']],
  ['EJAB-1483 ODBC sup failure (wrong PID?)',
   ['ejabberd_odbc_sup']],
  ['EJAB-1483 ODBC sup wrong PID failure echo',
   ["mod_pubsub_odbc,'-unsubscribe"]],
  ['DNS failure',
   ['You should check your DNS configuration']],
  ['Database unavailable/too slow',
   ['Database was not available or too slow']],
  ['State machine terminated: timeout',
   ['State machine',
    'terminating',
    'Reason for',
    'timeout']],
  ['The auth module returned an error',
   ['The authentication module',
    'returned an error']],
  ['MySQL disconnected',
   ['mysql',
    'Received unknown signal, exiting']],
  ['Connecting to MySQL: failed',
   ['mysql',
    'Failed connecting to']],
  ['Timeout while running a hook',
   %w[ejabberd_hooks
      timeout]],
  ['SQL transaction restarts exceeded',
   ['SQL transaction restarts exceeded']],
  ['Unexpected info',
   ['nexpected info']],
  ['Other sql_cmd timeout',
   ['sql_cmd']],
  ['System limit hit: ports', # check with length(erlang:ports())., set in ejabberdctl config file
   %w[system_limit
      open_port]],
  ['Other system limit hit', # processes? check with erlang:system_info(process_count)., erlang:system_info(process_limit)., set in ejabberdctl cfg
   ['system_limit']],
  ['Generic server terminating',
   ['Generic server',
    'terminating']],
  ['Mnesia table shrinked',
   ['shrinking table']],
  ['Admin access failed',
   ['Access of',
    'failed with error']],
  ['MySQL sock timedout',
   ['mysql_',
    ': Socket',
    'timedout']],
  ['Configuration error',
   ['{badrecord,config}']],
  ['Strange vCard error (vhost)',
   ['error found when trying to get the vCard']],
  ['Mnesia is overloaded',
   ['Mnesia is overloaded']],
  ['MySQL: init failed recv data',
   ['mysql_conn: init failed receiving data']],
  ['TCP Error',
   ['Failed TCP']]
].freeze

def log_type(text)
  KNOWN_LOG_TYPES.find_index do |entry|
    entry[1].all? { |substr| text.include? substr }
  end
end

new_data.split("\n=").each do |report|
  next if report.empty?

  report =~ /\A(\w+) REPORT==== (.*) ===\n(.*)\z/m
  type = Regexp.last_match(1)
  time = Regexp.last_match(2)
  text = Regexp.last_match(3)
  next unless type && time && text

  log_info[type] = (log_info[type] || 0) + 1
  if sub_type = log_type(text)
    log_info[sub_type] = (log_info[sub_type] || 0) + 1
  elsif $debug_mode
    warn "Unparsed log entry #{type}: #{text} at #{time}"
  end
end

log_info[:start] += new_data.size
File.open(CACHE_FILE, 'w') { |f| f.write log_info.to_yaml } unless $debug_mode

if ARGV.first == 'config'
  puts <<~CONFIG
    graph_title Ejabberd Log
    graph_vlabel total
    graph_category chat
    graph_args -l 0
  CONFIG
end

(KNOWN_LOG_TYPES + %w[ERROR WARNING INFO DEBUG]).each.with_index do |log_type, index|
  label, index = if log_type.is_a? Array
                   [log_type.first, index]
                 else
                   [log_type, log_type]
           end
  if ARGV.first == 'config'
    puts "T#{index}.label #{label}"
    puts "T#{index}.draw LINE"
  else
    puts "T#{index}.value #{log_info[index] or 0}"
  end
end