Package Products :: Package ZenEvents :: Module zensyslog |
[hide private]
[frames] | no frames]

# Source Code for Module Products.ZenEvents.zensyslog

      1  #! /usr/bin/env python
      2  # -*- coding: utf-8 -*-
      3  # ##########################################################################
      4  #
      5  # This program is part of Zenoss Core, an open source monitoring platform.
      6  # Copyright (C) 2008, Zenoss Inc.
      7  #
      8  # This program is free software; you can redistribute it and/or modify it
      9  # under the terms of the GNU General Public License version 2 as published by
     10  # the Free Software Foundation.
     11  #
     12  # For complete information please visit: http://www.zenoss.com/oss/
     13  #
     14  # ##########################################################################
     15
     16  __doc__ = """zensyslog
     17
     18  Turn syslog messages into events.
     19
     20  """
     21
     22  import time
     23  import socket
     24
     25  from twisted.internet.protocol import DatagramProtocol
     26  from twisted.internet import reactor
     27  from twisted.python import failure
     28  from twisted.internet import defer
     29
     30  import Globals
     31  from Products.ZenEvents.EventServer import EventServer
     32  from Products.ZenEvents.SyslogProcessing import SyslogProcessor
     33
     34  from Products.ZenUtils.Utils import zenPath
     35  from Products.ZenUtils.IpUtil import asyncNameLookup
     36  from Products.ZenUtils.Driver import drive
     37
     38  SYSLOG_PORT = 514
     39  try:
     40      SYSLOG_PORT = socket.getservbyname('syslog', 'udp')
     41  except socket.error:
     42      pass
     43
     44
     45 -class ZenSyslog(DatagramProtocol, EventServer):
     46      """
     47      ZenSyslog
     48      """
     49
     50      name = 'zensyslog'
     51      SYSLOG_DATE_FORMAT = '%b %d %H:%M:%S'
     52      SAMPLE_DATE = 'Apr 10 15:19:22'
     53
     54 -    def __init__(self):
     55          EventServer.__init__(self)
     56          if not self.options.useFileDescriptor\
     57               and self.options.syslogport < 1024:
     58              self.openPrivilegedPort('--listen', '--proto=udp',
     59                                      '--port=%s:%d'
     60                                       % (self.options.listenip,
     61                                      self.options.syslogport))
     62          self.changeUser()
     63          self.minpriority = self.options.minpriority
     64          self.processor = None
     65
     66          if self.options.logorig:
     67              import logging
     68              self.olog = logging.getLogger('origsyslog')
     69              self.olog.setLevel(20)
     70              self.olog.propagate = False
     71              lname = zenPath('log/origsyslog.log')
     72              hdlr = logging.FileHandler(lname)
     73              hdlr.setFormatter(logging.Formatter('%(message)s'))
     74              self.olog.addHandler(hdlr)
     75          if self.options.useFileDescriptor is not None:
     76              self.useUdpFileDescriptor(int(self.options.useFileDescriptor))
     77          else:
     78              reactor.listenUDP(self.options.syslogport, self,
     79                                interface=self.options.listenip)
     80
     81
     82 -    def configure(self):
     83          """
     84          Initialize the daemon
     85
     86          @return: Twisted deferred object
     87          @rtype: Twisted deferred object
     88          """
     89          def inner(driver):
     90              """
     91              Generator function to gather zProperites and then initialize.
     92
     93              @param driver: driver
     94              @type driver: string
     95              @return: Twisted deferred object
     96              @rtype: Twisted deferred object
     97              """
     98              yield EventServer.configure(self)
     99              driver.next()
    100              self.log.info('Fetching the default syslog priority')
    101              yield self.model().callRemote('getDefaultPriority')
    102              self.processor = SyslogProcessor(self.sendEvent,
    103                      self.options.minpriority, self.options.parsehost,
    104                      self.options.monitor, driver.next())
    105              self.log.info('Configuration finished')
    106
    107          return drive(inner)
    108
    109
    110 -    def expand(self, msg, client_address):
    111          """
    112          Expands a syslog message into a string format suitable for writing
    113          to the filesystem such that it appears the same as it would
    114          had the message been logged by the syslog daemon.
    115
    116          @param msg: syslog message
    117          @type msg: string
    118          @param client_address: IP info of the remote device (ipaddr, port)
    119          @type client_address: tuple of (string, number)
    120          @return: message
    121          @rtype: string
    122          """
    123          # pri := facility * severity
    124          stop = msg.find('>')
    125
    126          # check for a datestamp.  default to right now if date not present
    127          start = stop + 1
    128          stop = start + len(ZenSyslog.SAMPLE_DATE)
    129          dateField = msg[start:stop]
    130          try:
    131              date = time.strptime(dateField,
    132                                   ZenSyslog.SYSLOG_DATE_FORMAT)
    133              year = time.localtime()[0]
    134              date = (year, ) + date[1:]
    135              start = stop + 1
    136          except ValueError:
    137
    138          # date not present, so use today's date
    139              date = time.localtime()
    140
    141          # check for a hostname.  default to localhost if not present
    142          stop = msg.find(' ', start)
    143          if msg[stop - 1] == ':':
    144              hostname = client_address[0]
    145          else:
    146              hostname = msg[start:stop]
    147              start = stop + 1
    148
    149          # the message content
    150          body = msg[start:]
    151
    152          # assemble the message
    153          prettyTime = time.strftime(ZenSyslog.SYSLOG_DATE_FORMAT, date)
    154          message = '%s %s %s' % (prettyTime, hostname, body)
    155          return message
    156
    157
    158 -    def datagramReceived(self, msg, client_address):
    159          """
    160          Consume the network packet
    161
    162          @param msg: syslog message
    163          @type msg: string
    164          @param client_address: IP info of the remote device (ipaddr, port)
    165          @type client_address: tuple of (string, number)
    166          """
    167          (ipaddr, port) = client_address
    168          if self.options.logorig:
    169              if self.options.logformat == 'human':
    170                  message = self.expand(msg, client_address)
    171              else:
    172                  message = msg
    173              self.olog.info(message)
    174
    175          if self.options.noreverseLookup:
    176              d = defer.succeed(ipaddr)
    177          else:
    178              d = asyncNameLookup(ipaddr)
    179          d.addBoth(self.gotHostname, (msg, ipaddr, time.time()))
    180
    181
    182 -    def gotHostname(self, response, data):
    183          """
    184          Send the resolved address, if possible, and the event via the thread
    185
    186          @param response: Twisted response
    187          @type response: Twisted response
    188          @param data: (msg, ipaddr, rtime)
    189          @type data: tuple of (string, string, datetime object)
    190          """
    191          (msg, ipaddr, rtime) = data
    192          if isinstance(response, failure.Failure):
    193              host = ipaddr
    194          else:
    195              host = response
    196          if self.processor:
    197              self.processor.process(msg, ipaddr, host, rtime)
    198
    199
    200 -    def buildOptions(self):
    201          """
    202          Command-line options
    203          """
    204          EventServer.buildOptions(self)
    205          self.parser.add_option('--dmdpath', dest='dmdpath',
    206                                 default='/zport/dmd',
    207                                 help='Zope path to our DMD /zport/dmd')
    208          self.parser.add_option('--parsehost', dest='parsehost',
    209                                 action='store_true', default=False,
    210                                 help='Try to parse the hostname part of a syslog HEADER'
    211                                 )
    212          self.parser.add_option('--stats', dest='stats',
    213                                 action='store_true', default=False,
    214                                 help='Print statistics to log every 2 secs')
    215          self.parser.add_option('--logorig', dest='logorig',
    216                                 action='store_true', default=False,
    217                                 help='Log the original message')
    218          self.parser.add_option('--logformat', dest='logformat',
    219                                 default='human',
    220                                 help='Human-readable (/var/log/messages) or raw (wire)'
    221                                 )
    222          self.parser.add_option('--minpriority', dest='minpriority',
    223                                 default=6, type='int',
    224                                 help='Minimum priority message that zensyslog will accept'
    225                                 )
    226          self.parser.add_option('--heartbeat', dest='heartbeat',
    227                                 default=60,
    228                                 help='Number of seconds between heartbeats'
    229                                 )
    230          self.parser.add_option('--syslogport', dest='syslogport',
    231                                 default=SYSLOG_PORT, type='int',
    232                                 help='Port number to use for syslog events'
    233                                 )
    234          self.parser.add_option('--listenip', dest='listenip',
    235                                 default='0.0.0.0',
    236                                 help='IP address to listen on. Default is 0.0.0.0'
    237                                 )
    238          self.parser.add_option('--useFileDescriptor',
    239                                 dest='useFileDescriptor', type='int',
    240                                 help='Read from an existing connection rather opening a new port.'
    241                                 , default=None)
    242          self.parser.add_option('--noreverseLookup', dest='noreverseLookup',
    243                                 action='store_true', default=False,
    244                                 help="Don't convert the remote device's IP address to a hostname."
    245                                 )
    246
    247
    248  if __name__ == '__main__':
    249      zsl = ZenSyslog()
    250      zsl.run()
    251      zsl.report()
    252