#! /usr/bin/ruby1.8 # NAME # mauvesend - send alert(s) to a given alert station # # SYNOPSIS # mauvesend [<destination>] # [ --help | -h ] [ --manual | -m ] [ --version | -V ] # [--source | -o <source>] [--replace | -p] [--verbose | -v] # [--id <alertid> ... ] # # OPTIONS # <destination> Where the alert should go. This can be either a hostname or # an IP address, and optionally a port, separated by a colon. # The default port is 32741. # # If no destination is supplied, the value from the file # /etc/mauvealert/mauvesend.destination is used. If no # destination can be determined, an error is raised. # # If a hostname is given and no port is specified, SRV records # are used to determine where the alerts should go to. The SRV # prefix is _mauvealert._udp. If no SRV records are found, A # records are used instead. # # IPv6 addresses can be used, but must be enclosed in square # brackets, e.g. [2001:41c8::12]. # # --source, -o <source> identify the source of the alert (defaults to # hostname, but you might want to name your monitoring # systems more explicitly). # # --replace, -p Send an update replacing all other alerts for this # source -- any previous alerts not specified in this # update are assumed to be cleared. If you specify this # option, you don't have to supply *any* alerts to raise # or clear (in which case all alerts from that source # will be cleared). # # --verbose, -v If you specify this option once, it will print the # transmission ID of the packet for debugging. If you # specify it twice, it will print the entire data # structure. # # --help, -h Display a short help message, and exit. # # --manual, -m Display this manual, and exit. # # --version, -V Display the version number for Mauve and exit. # # You can specify any number of alerts in an update - every time you specify # --id starts a new alert. # # --id, -i <alertid> Unique specified for each alert raised. This should # be unique on a per-source basis, i.e. for an # individual application or host. # # --summary, -s <summary> Text for humans describing the nature of the alert, # first 100 characters are only ones guaranteed to make # it to pagers, twitter, SMS etc. # # --detail, -d <detail> HTML fragment describing the alert in more detail, # no limit on length. # # --subject, -u <subject> Set the subject of the alert (i.e. the server/entity # that this alert concerns). If no subject is # specified, it is assumed to be the same as <source>, # detailed above. # # --raise, -r <time> Mark the alert to be (re)raised at the given time. # If no time is supplied, "now" is assumed. See # SPECIFYING TIMES below for the format of <time>. # # --clear, -c <time> Mark the alert to be cleared at the given time. If # no time is specified, "now" is assumed. See # SPECIFYING TIMES below for the format of <time>. # # SPECIFYING TIMES # # Times can be specified for an alert to be raised or cleared. This can be # specified as any time in the past or future. The format is + or -, followed # by a number, followed by a letter determining the units, one of s, m, h, d, # representing seconds, minutes, hours, and days, respectively. If no units are # specified, seconds is assumed. If no sign or unit is specified, an absolute # number of seconds since midnight UTC, 1st Jan 1970 is expected. # # Some example times are: # # now Immediately # +10m In 10 minutes time # -10h 10 Hours ago # # SENISBLE USAGE # # Mauve uses UDP to transmit data, which means that there is no guarrantee a # single packet will reach the server. Therefore: # # * The host/application should send "raise" notification regularly until the # alert clears, whereupon it should regularly send "cleared" notifications. # # * When setting a heartbeat-type alert, make sure that the raise time is more # than double the period of the "clear" notifications. For example, if the # host is sending a clear every 120 seconds, the raise time should be # greater than 240 seconds, preferably greater than 360 seconds to allow for # packets going missing, reducing the likelihood of false alerts. # # Try to convey salient details about the alerts in the relevant fields. A # typical short alert from Mauve might read # # RAISED: <subject>: <summary> -- <source> # # Make sure that the alert will be understood with just those three fields # displayed. # # * Keep the summary brief and salient. # # * Keep the summary constant, unless there has been a material change to the # nature of the alert. Mauve may re-send any messages when the subject # changes. If something is changing quickly, like load averages, best not # to put them in the summary. # # * Make sure that the subject is set correctly. Remember if no subject is # set, then the source of the alert is used instead. # # * Make sure that the source is correct too -- nothing worse than an alert # that comes in with an ambiguous origin. # # * The alert ID is used internally by Mauve to keep alerts consistent. This # must be unique on a per-source basis. It is OK to have many alerts with the # ID "heartbeat" as long as the source of the alert is different in each case. # # The raise and clear times can be specified, if needed, but generally leaving # them empty, i.e. setting them to "now" is sufficient. Mauve remembers when # an alert is first raised. # # EXAMPLES # # To raise an alert: # # mauvesend -s smtp-out-1.example.com -i mailqueue \\ # -d "Mail queue has <b>54232</b> messages in it. That's <em>LOADS</em>" \\ # -u "Mail queue too big on outgoing SMTP server" -r # # To clear an alert: # # mauvesend -s smtp-out-1.example.com -i mailqueue -c # # To create a "heartbeat" alert, i.e. one that says "Currently OK, but raise in the future if nothing more is heard": # # mauvesend -i heartbeat -d "No heartbeat received for 1.2.3.4. Could be down!" -s "heartbeat failed" -c -r +10m # # SEE ALSO # # mauveconsole(1), mauveserver(1) # # AUTHOR # # Patrick J Cherry <patrick@bytemark.co.uk> # require 'getoptlong' %w(sender mauve_time version proto).each do |r| begin require "mauve/#{r}" rescue LoadError => ex STDERR.puts "*** "+ex.to_s end end NOW = Time.now def error(msg) STDERR.print "*** Error: #{msg}\n" STDERR.print "*** For help, type: #{$0} -h\n" exit 1 end def parse_time_spec(spec = "now") # # Default to now # spec = "now" if spec.empty? case spec when "now" NOW when /^\d+$/ spec.to_i when /^(\+|-)?(\d+)([smhd])?$/ if $1 == "-" multiplier = -1 else multiplier = 1 end multiplier *= case $3 when "m" then 60 when "h" then 3600 when "d" then 86400 else 1 end NOW + $2.to_i * multiplier else raise ArgumentError, "Unrecognised time format #{spec.inspect}" end end begin begin update = Mauve::Proto::AlertUpdate.new update.replace = false update.alert = [] rescue NameError # # Do nothing .. When generating manpages in the build process we don't need # to have Protobuf available. # update = nil end message = nil verbose = 0 help = false manual = false version = false opts = GetoptLong.new( ['-h', '--help', GetoptLong::NO_ARGUMENT], ['-m', '--manual', GetoptLong::NO_ARGUMENT], ['-V', '--version', GetoptLong::NO_ARGUMENT], ['-o', '--source', GetoptLong::OPTIONAL_ARGUMENT], ['-p', '--replace', GetoptLong::NO_ARGUMENT], ['-i', '--id', GetoptLong::OPTIONAL_ARGUMENT], ['-s', '--summary', GetoptLong::OPTIONAL_ARGUMENT], ['-u', '--subject', GetoptLong::OPTIONAL_ARGUMENT], ['-c', '--clear', GetoptLong::OPTIONAL_ARGUMENT], ['-r', '--raise', GetoptLong::OPTIONAL_ARGUMENT], ['-d', '--detail', GetoptLong::OPTIONAL_ARGUMENT], ['-v', '--verbose', GetoptLong::NO_ARGUMENT] ).each do |opt,arg| # # Can catch empty arguments better if we set the GetoptLong things to # "optional" rather than "required" and catch the empty arg here. error "#{opt} cannot be empty" if arg.empty? and not %w(-h -m -V -p -v -c -r).include?(opt) case opt when '-h' help = true when '-m' manual = true when '-V' version = true when '-p' error "Cannot send update -- not all libraries are available" if update.nil? update.replace = true when '-i' error "Cannot send update -- not all libraries are available" if update.nil? error "Cannot specify the same ID twice in one update -- ID #{arg}" if update.alert.any?{|a| a.id == arg} message = Mauve::Proto::Alert.new message.id = arg update.alert << message when '-o' error "Cannot send update -- not all libraries are available" if update.nil? error "Can only specify one source" if update.source update.source = arg when '-v' verbose += 1 else error "Cannot send update -- not all libraries are available" if update.nil? error "Must specify --id before message" unless message case opt when '-s' then message.summary = arg when '-u' then message.subject = arg when '-d' then message.detail = arg when '-c' then message.clear_time = parse_time_spec(arg).to_i when '-r' then message.raise_time = parse_time_spec(arg).to_i else error "Unknown option #{opt}" end end end # CAUTION! Kwality kode. # if manual or help # Open the file, stripping the shebang line lines = File.open(__FILE__){|fh| fh.readlines}[1..-1] found_synopsis = false lines.each do |line| line.chomp! break if line.empty? if help and !found_synopsis found_synopsis = (line =~ /^#\s+SYNOPSIS\s*$/) next end puts line[2..-1].to_s break if help and found_synopsis and line =~ /^#\s*$/ end end puts "#{$0}: version "+Mauve::VERSION if version exit 0 if help or version or manual error "Cannot send update -- not all libraries are available" if update.nil? error "No alerts specified" unless !update.alert.empty? || update.replace update.transmission_id = rand(2**63) Mauve::Sender.new(ARGV).send(update, verbose) rescue ArgumentError => ae error ae.message rescue StandardError => ae error ae.message end