#!/usr/bin/env ruby require 'rack' require 'prometheus/middleware/exporter' require 'socket' require 'rack/handler/webrick' require 'logger' prometheus = Prometheus::Client.registry mailq = prometheus.gauge(:postfix_queue_size, "Number of messages in the mail queue") Thread.abort_on_exception = true Thread.new do loop do begin %w{incoming active corrupt hold}.each do |q| mailq.set({ queue: q }, Dir["/var/spool/postfix/#{q}/*"].size) end # deferred is special, because it's often hueg it gets sharded into # multiple subdirectories mailq.set({ queue: 'deferred' }, Dir["/var/spool/postfix/deferred/*/*"].size) sleep 5 rescue StandardError => ex $stderr.puts "Error while monitoring queue sizes: #{ex.message} (#{ex.class})" $stderr.puts ex.backtrace.map { |l| " #{l}" }.join("\n") sleep 1 end end end if ENV["SYSLOG_SOCKET"] delays = prometheus.summary(:postfix_delivery_delays, "Distribution of time taken to deliver (or bounce) messages") Thread.new do begin s = Socket.new(Socket::AF_UNIX, Socket::SOCK_DGRAM, 0) s.bind(Socket.pack_sockaddr_un(ENV["SYSLOG_SOCKET"])) rescue Errno::EEXIST, Errno::EADDRINUSE File.unlink ENV["SYSLOG_SOCKET"] retry end loop do begin msg = s.recvmsg.first if msg =~ %r{postfix/.* delay=(\d+(\.\d+)?), .* dsn=(\d+\.\d+\.\d+), status=(\w+)} delay = $1.to_f dsn = $3 status = $4 if status == "bounced" or status == "sent" delays.observe({dsn: dsn, status: status}, delay) end end rescue StandardError => ex $stderr.puts "Error while receiving postfix logs: #{ex.message} (#{ex.class})" $stderr.puts ex.backtrace.map { |l| " #{l}" }.join("\n") sleep 1 end end end end app = Rack::Builder.new app.use Rack::Deflater, if: ->(_, _, _, body) { body.any? && body[0].length > 512 } app.use Prometheus::Middleware::Exporter app.run ->(env) { [404, {'Content-Type' => 'text/plain'}, ['NOPE NOPE NOPE NOPE']] } logger = Logger.new($stderr) logger.level = Logger::INFO logger.formatter = proc { |s, t, p, m| "WEBrick: #{m}\n" } # This is the only way to get the Rack-mediated webrick to listen on both # INADDR_ANY and IN6ADDR_ANY on libcs that don't support getaddrinfo("*") # (ie musl-libc). Setting `Host: '*'` barfs on the above-mentioned buggy(?) # libcs, `Host: '::'` fails on newer rubies (because they use # setsockopt(V6ONLY) by default), and with RACK_ENV at its default of # "development", it only listens on localhost. And even *this* only works # on Rack 2, because before that the non-development default listen address # was "0.0.0.0"! ENV['RACK_ENV'] = "none" Rack::Handler::WEBrick.run app, Port: 9154, Logger: logger, AccessLog: []