postfix_exporter/lib/collector/dovecot.rb

162 lines
8.8 KiB
Ruby

class Collector::Dovecot
class Sieve
def initialize store, prometheus
@store = store
@stored_into_mailbox = prometheus.counter :stored_into_mailbox_total, docstring: 'A counter of mails stored in mailbox by sieve', labels: %i[process]
@forwards = prometheus.counter :forwared_mails_total, docstring: 'A counter of mails forwareded to other address', labels: %i[process]
@discarded_duplicate_forward = prometheus.counter :discarded_duplicate_forward_total, docstring: 'A counter of discarded duplicates, which will not be forwarded.', labels: %i[process]
%w[lmtp deliver].each do |p|
@stored_into_mailbox.increment by: 0, labels: {process: p}
@forwards.increment by: 0, labels: {process: p}
@discarded_duplicate_forward.increment by: 0, labels: {process: p}
end
end
def collect entry, process, msg
case msg
when / stored mail into mailbox /
# dovecot.service dovecot lmtp sieve: lmtp(dillo@nfotex.com)<935639><hhtGGv2ZVmLXRg4AQx2OcA>: sieve: msgid=<1649842684455734173.18148473471766045120@vlmpaymp001.at.inside>: stored mail into mailbox
@stored_into_mailbox.increment labels: {process: process}
when / forwarded to /
@forwards.increment labels: {process: process}
when / discarded duplicate forward to /
@discarded_duplicate_forward.increment labels: {process: process}
else
STDERR.puts "# #{entry._systemd_unit} #{entry.syslog_identifier} sieve| #{entry.message}"
end
end
end
class Delivery
def initialize store, prometheus, sieve, saved_mailbox, process
@store, @sieve, @process = store, sieve, process
@connect = prometheus.counter "#{process}_connect_total", docstring: "A counter of connection via #{process}"
@disconnect = prometheus.counter "#{process}_disconnect_total", docstring: "A counter of disconnect at #{process}"
@saved_mail_to_mailbox = saved_mailbox
end
def collect entry, msg
case msg
when /\AConnect from / then @connect.increment
when /\ADisconnect from / then @disconnect.increment
when /saved mail to / then @saved_mail_to_mailbox.increment labels: {process: @process}
when /\Asieve: (.*)/ then @sieve.collect entry, @process, $1
else STDERR.puts "# #{entry._systemd_unit} #{entry.syslog_identifier} delivery| #{entry.message}"
end
end
end
class Imap
def initialize store, prometheus
@connection_closed = prometheus.counter :connection_closed_total, docstring: 'A counter of closed connection on dovecot'
@inactivity = prometheus.counter :disconnected_inactivity_total, docstring: 'A counter for disconnect for inactivity.'
@connection_stats = prometheus.counter :connection_stats_total, docstring: 'A counter for observed statistics after disconnected.', labels: %i[disconnect_reason]
@logged_out = prometheus.counter :logged_out_total, docstring: 'A counter of logouts on dovecot'
@maildir_scanning_took_long = prometheus.summary :maildir_scanning_took_long_total, docstring: 'A summary of long taken maildir scanning by reason (why).', labels: %i[why]
@maildir_scanning_took_long_rename = prometheus.counter :maildir_scanning_took_long_renames_total, docstring: 'A counter of rename()-calls while long taken mail dir scanning'
@maildir_scanning_took_long_readdir = prometheus.counter :maildir_scanning_took_long_readdirs_total, docstring: 'A counter of readdir()-calls while long taken mail dir scanning'
disconnect_reasons = ['logged out', 'connection closed', 'inactivity']
disconnect_reasons.each {|r| @connection_stats.increment by: 0, labels: {disconnect_reason: r} }
@connection_stats =
Hash[ *%i[in out deleted expunged trashed hdr_count hdr_bytes body_count body_bytes].flat_map {|t|
[t, prometheus.counter( :"connection_stats_#{t}_total", docstring: "Counter for #{t} statistics observed after disconnected")]
}]
end
def collect entry, msg
case msg
when /\ALogged out (.*)/
# imap(srv_rt0@nfotex.com)<936759><IXxbn4bc4roqAQGQAGoGAWAUP//++Cw+>: Logged out in=38 out=804 deleted=0 expunged=0 trashed=0 hdr_count=0 hdr_bytes=0 body_count=0 body_bytes=0
@logged_out.increment
$1.split( ' ').each {|x| t, v = x.split('='); @connection_stats[t.to_sym]&.increment by: v.to_f }
when /\AConnection closed \([^)]+\) (.*)/
# imap(johannes@nfotex.com)<936668><R4OTgIbcCKRQbb9l>: Connection closed (EXAMINE finished 0.041 secs ago) in=5253 out=390971 deleted=0 expunged=0 trashed=0 hdr_count=14 hdr_bytes=6569 body_count=14 body_bytes=336589
@connection_closed.increment
$1.split( ' ').each {|x| t, v = x.split('='); @connection_stats[t.to_sym]&.increment by: v.to_f }
when /\AConnection closed: .* failed: \([^)]+\) (.*)/
# imap(wiz@nfotex.com)<1447340><0OOGu+XeasMqAoOIC8CCAHA59y+2iMag>: Connection closed: read(size=6100) failed: Connection reset by peer (UID FETCH finished 0.159 secs ago) in=2982 out=17044659 deleted=0 expunged=0 trashed=0 hdr_count=1 hdr_bytes=3724 body_count=169 body_bytes=16970415
@connection_closed.increment
$1.split( ' ').each {|x| t, v = x.split('='); @connection_stats[t.to_sym]&.increment by: v.to_f }
when /\ADisconnected for inactivity (.*)/
@inactivity.increment
$1.split( ' ').each {|x| t, v = x.split('='); @connection_stats[t.to_sym]&.increment by: v.to_f }
when /\AWarning: Maildir: Scanning .+? took (?<took>\d+) seconds \((?<readdir>\d+) readdir\(\)s, (?<rename>\d+) rename\(\)s to cur\/, why=0x(?<why>[0-9a-fA-F]+)\)/
# Warning: Maildir: Scanning /var/mail/nfotex.com/wiz/mails/.Updates/cur took 49 seconds (86044 readdir()s, 0 rename()s to cur/, why=0x80)
m = $~
@maildir_scanning_took_long.observe m[:took].to_i, labels: m[:why].to_i(16)
@maildir_scanning_took_long_rename.increment by: m[:rename].to_i
@maildir_scanning_took_long_readdir.increment by: m[:readdir].to_i
else
STDERR.puts "# #{entry._systemd_unit} #{entry.syslog_identifier} imap| #{entry.message}"
end
end
end
class ImapLogin
def initialize store, prometheus
@store = store
@logged_in = prometheus.counter :logged_in_total, docstring: 'A counter of successfull logins to dovecot'
@aborted = prometheus.counter :login_aborted_total, docstring: 'A counter of aborted logins'
@disconnected = prometheus.counter :login_disconnected_total, docstring: 'A counter of disconnections before successfully logged in', labels: %i[reason]
end
def collect entry, msg
case msg
when /\ALogin: user=/
@logged_in.increment
when /\ADisconnected:? \((.*?)\): user=</
case $1
when /\Ano auth attempts/
@disconnected.increment labels: {reason: 'no auth attempts'}
when /\Aauth failed/
@disconnected.increment labels: {reason: 'auth failed'}
when /\AToo many invalid commands /
@disconnected.increment labels: {reason: 'too many invalid commands'}
when /\AInactivity /
@disconnected.increment labels: {reason: 'inactivity'}
when /\ADisconnected /
@disconnected.increment labels: {reason: 'disconnected'}
else
@disconnected.increment labels: {reason: '<any>'}
STDERR.puts "# #{entry._systemd_unit} #{entry.syslog_identifier} Disconnected before login| #{entry.message}"
end
when /\AAborted login/
@aborted.increment
else
STDERR.puts "# #{entry._systemd_unit} #{entry.syslog_identifier} imap-login| #{entry.message}"
end
end
end
def initialize store, prometheus
@store = store
@sieve = Sieve.new store, Collector::PrefixProxy.new( prometheus, :sieve)
@lmtp = Delivery.new store, prometheus, @sieve, @saved_mail_to_mailbox, :lmtp
@deliver = Delivery.new store, prometheus, @sieve, @saved_mail_to_mailbox, :deliver
@imap_login = ImapLogin.new store, prometheus
@imap = Imap.new store, prometheus
@saved_mail_to_mailbox = prometheus.counter :saved_mail_to_mailbox_total, docstring: "A counter of saved mails to mailbox directly", labels: %i[process]
@auth_errors = prometheus.counter :auth_errors_total, docstring: "A counter of dovecot auth errors by type.", labels: %i[error]
end
def collect entry
# STDERR.puts "dovecot| #{entry.message}"
case entry.message
when /\Aimap-login: (.*)/ then @imap_login.collect entry, $1
when /\Aimap\([^)]+\)(?:<[^ ]+>)?: (.*)/ then @imap.collect entry, $1
when /\Almtp\([^ ]+\)<[^ ]+>: (.*)/ then @lmtp.collect entry, $1
when /\Almtp\([^ ]+\): (.*)/ then @lmtp.collect entry, $1
when /\Adeliver(?:[^:]+): (.*)/ then @deliver.collect entry, $1
when /\Aauth: Error: (.*)/
case $1
when /\ALDAP: Connection lost to LDAP server, /
@auth_errors.increment labels: {error: "ldap connection lost"}
else
STDERR.puts "# #{entry._systemd_unit} #{entry.syslog_identifier}| #{entry.message}"
@auth_errors.increment labels: {error: "<any>"}
end
else STDERR.puts "# #{entry._systemd_unit} #{entry.syslog_identifier}| #{entry.message}"
end
end
end