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>: 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>: 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>: 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 (?\d+) seconds \((?\d+) readdir\(\)s, (?\d+) rename\(\)s to cur\/, why=0x(?[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='} 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: ""} end else STDERR.puts "# #{entry._systemd_unit} #{entry.syslog_identifier}| #{entry.message}" end end end