alerta-contrib/integrations/syslog/syslogfwder.py
2023-03-21 00:15:34 +01:00

288 lines
8.7 KiB
Python

import logging
import os
import platform
import re
import select
import socket
import sys
from alertaclient.api import Client
__version__ = '3.5.0'
SYSLOG_TCP_PORT = int(os.environ.get('SYSLOG_TCP_PORT', 514))
SYSLOG_UDP_PORT = int(os.environ.get('SYSLOG_UDP_PORT', 514))
SYSLOG_FACILITY_NAMES = [
'kern',
'user',
'mail',
'daemon',
'auth',
'syslog',
'lpr',
'news',
'uucp',
'cron',
'authpriv',
'ftp',
'ntp',
'audit',
'alert',
'clock',
'local0',
'local1',
'local2',
'local3',
'local4',
'local5',
'local6',
'local7'
]
SYSLOG_SEVERITY_NAMES = [
'emerg',
'alert',
'crit',
'err',
'warning',
'notice',
'info',
'debug'
]
SYSLOG_SEVERITY_MAP = {
'emerg': 'critical',
'alert': 'critical',
'crit': 'major',
'err': 'minor',
'warning': 'warning',
'notice': 'normal',
'info': 'informational',
'debug': 'debug',
}
if sys.version_info[0] >= 3:
unicode = str
def priority_to_code(name):
return SYSLOG_SEVERITY_MAP.get(name, 'unknown')
def decode_priority(priority):
facility = priority >> 3
level = priority & 7
return SYSLOG_FACILITY_NAMES[facility], SYSLOG_SEVERITY_NAMES[level]
LOOP_EVERY = 20 # seconds
LOG = logging.getLogger('alerta.syslog')
logging.basicConfig(
format='%(asctime)s - %(name)s: %(levelname)s - %(message)s', level=logging.DEBUG)
class SyslogDaemon:
def __init__(self):
self.api = Client()
LOG.info('Starting UDP listener...')
# Set up syslog UDP listener
try:
self.udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.udp.bind(('', SYSLOG_UDP_PORT))
except OSError as e:
LOG.error('Syslog UDP error: %s', e)
sys.exit(2)
LOG.info('Listening on syslog port %s/udp' % SYSLOG_UDP_PORT)
LOG.info('Starting TCP listener...')
# Set up syslog TCP listener
try:
self.tcp = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.tcp.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.tcp.bind(('', SYSLOG_TCP_PORT))
self.tcp.listen(5)
except OSError as e:
LOG.error('Syslog TCP error: %s', e)
sys.exit(2)
LOG.info('Listening on syslog port %s/tcp' % SYSLOG_TCP_PORT)
self.shuttingdown = False
def run(self):
count = 0
while not self.shuttingdown:
try:
LOG.debug('Waiting for syslog messages...')
ip, op, rdy = select.select(
[self.udp, self.tcp], [], [], LOOP_EVERY)
if ip:
for i in ip:
if i == self.udp:
data, addr = self.udp.recvfrom(4096)
data = unicode(data, 'utf-8', errors='ignore')
LOG.debug(
'Syslog UDP data received from %s: %s', addr, data)
if i == self.tcp:
client, addr = self.tcp.accept()
data = client.recv(4096)
data = unicode(data, 'utf-8', errors='ignore')
client.close()
LOG.debug(
'Syslog TCP data received from %s: %s', addr, data)
alerts = self.parse_syslog(ip=addr[0], data=data)
for alert in alerts:
try:
self.api.send_alert(**alert)
except Exception as e:
LOG.warning('Failed to send alert: %s', e)
count += 1
if not ip or count % 5 == 0:
LOG.debug('Send heartbeat...')
try:
origin = '{}/{}'.format('syslog', platform.uname()[1])
self.api.heartbeat(origin, tags=[__version__])
except Exception as e:
LOG.warning('Failed to send heartbeat: %s', e)
except (KeyboardInterrupt, SystemExit):
self.shuttingdown = True
LOG.info('Shutdown request received...')
def parse_syslog(self, ip, data):
LOG.debug('Parsing syslog message...')
syslogAlerts = list()
event = None
resource = None
for msg in data.split('\n'):
if not msg or 'last message repeated' in msg:
continue
if re.match(r'<\d+>1', msg):
# Parse RFC 5424 compliant message
m = re.match(
r'<(\d+)>1 (\S+) (\S+) (\S+) (\S+) (\S+) (.*)', msg)
if m:
PRI = int(m.group(1))
# ISOTIMESTAMP = m.group(2)
HOSTNAME = m.group(3)
APPNAME = m.group(4)
PROCID = m.group(5)
MSGID = m.group(6)
TAG = '{}[{}] {}'.format(APPNAME, PROCID, MSGID)
MSG = m.group(7)
LOG.info('Parsed RFC 5424 message OK')
else:
LOG.error(
'Could not parse RFC 5424 syslog message: %s', msg)
continue
elif re.match(r'<(\d{1,3})>\S{3}\s', msg):
# Parse RFC 3164 compliant message
m = re.match(
r'<(\d{1,3})>\S{3}\s{1,2}\d?\d \d{2}:\d{2}:\d{2} (\S+)( (\S+):)? (.*)', msg)
if m:
PRI = int(m.group(1))
HOSTNAME = m.group(2)
TAG = m.group(4)
MSG = m.group(5)
LOG.info('Parsed RFC 3164 message OK')
else:
LOG.error(
'Could not parse RFC 3164 syslog message: %s', msg)
continue
elif re.match(r'<\d+>.*%[A-Z0-9_-]+', msg):
# Parse Cisco Syslog message
m = re.match(r'<(\d+)>.*(%([A-Z0-9_-]+)):? (.*)', msg)
if m:
LOG.debug(m.groups())
PRI = int(m.group(1))
CISCO_SYSLOG = m.group(2)
try:
CISCO_FACILITY, CISCO_MNEMONIC = m.group(
3).split('-')
except ValueError as e:
LOG.error(
'Could not parse Cisco syslog - %s: %s', e, m.group(3))
CISCO_FACILITY = CISCO_MNEMONIC = 'na'
TAG = CISCO_MNEMONIC
MSG = m.group(4)
event = CISCO_SYSLOG
# replace IP address with a hostname, if necessary
try:
socket.inet_aton(ip)
(resource, _, _) = socket.gethostbyaddr(ip)
except (OSError, socket.herror):
resource = ip
resource = '{}:{}'.format(resource, CISCO_FACILITY)
else:
LOG.error('Could not parse Cisco syslog message: %s', msg)
continue
facility, level = decode_priority(PRI)
# Defaults
event = event or '{}{}'.format(
facility.capitalize(), level.capitalize())
resource = resource or '{}{}'.format(
HOSTNAME, ':' + TAG if TAG else '')
severity = priority_to_code(level)
group = 'Syslog'
value = level
text = MSG
environment = 'Production'
service = ['Platform']
tags = ['{}.{}'.format(facility, level)]
correlate = ['{}{}'.format(facility.capitalize(), s.capitalize())
for s in SYSLOG_SEVERITY_NAMES]
raw_data = msg
syslogAlert = {
'resource': resource,
'event': event,
'environment': environment,
'severity': severity,
'correlate': correlate,
'service': service,
'group': group,
'value': value,
'text': text,
'tags': tags,
'event_type': 'syslogAlert',
'raw_data': raw_data
}
syslogAlerts.append(syslogAlert)
return syslogAlerts
def main():
LOG = logging.getLogger('alerta.syslog')
try:
SyslogDaemon().run()
except (SystemExit, KeyboardInterrupt):
LOG.info('Exiting alerta syslog.')
sys.exit(0)
except Exception as e:
LOG.error(e, exc_info=1)
sys.exit(1)