Source code for syslog_rfc5424_formatter

import logging
import time
import socket
import datetime
import re

version_info = (1, 2, 2)
__version__ = '.'.join(str(s) for s in version_info)
__author__ = 'EasyPost <oss@easypost.com>'


class RFC5424FormatterError(Exception):
    pass


class InvalidSDIDError(RFC5424FormatterError):
    pass


[docs]class RFC5424Formatter(logging.Formatter, object): ''' A derived formatter than allows for isotime specification for full RFC5424 compliancy (with corrected TZ format). This should be combined with the Syslog Handler to actually emit logs. For a "proper" ISOTIME format, use "%(isotime)s" in a formatter instance of this class or a class derived from this class. This is for a work-around where strftime has no mechanism to produce timezone in the format of "-08:00" as required by RFC5424. The '%(isotime)s' replacement will read in the record timestamp and try and reparse it. This really is a problem with RFC5424 and strftime. I am unsure if this will be fixed in the future (in one or the other case) This formatter has an added benefit of allowing for '%(hostname)s' to be specified which will return a '-' as specified in RFC5424 if socket.gethostname() returns bad data (exception). This formatter will automatically insert the RFC5424 header for you; the format string that you pass in the constructor is only applied to the message body (and should typically just be %(message)). Stuctured Data Example: [exampleSDID@32473 iut="3" eventSource="Application" eventID="1011"] To use structured data: 1. Construct the logger with an sd_id kwarg (or set the `sd_id` attribute on the logger object) 2. Construct your individual records with `{'args': {'structured_data': {'iut': '3'}}}` ''' def __init__(self, fmt='%(message)s', datefmt=None, style='%', procid=None, msgid=None, sd_id=None): # note: we accept and throw away "style" because our stuff only works with % # we also accept and throw away datefmt for similar reasons self._tz_fix = re.compile(r'([+-]\d{2})(\d{2})$') self._procid = procid self._msgid = msgid self._sd_id = sd_id return super(RFC5424Formatter, self).__init__(fmt=fmt, datefmt=None) @property def procid(self): """Default PROCID to add to syslog message""" return self._procid @procid.setter def procid(self, id): self._procid = id @property def msgid(self): """Default MSGID to add to syslog message""" return self._msgid @msgid.setter def msgid(self, id): self._msgid = id @property def sd_id(self): """Default SD-ID to add to STRUCTURED-DATA section in syslog message""" return self._sd_id @sd_id.setter def sd_id(self, sd_id): if not sd_id: raise InvalidSDIDError('SD-ID cannot be empty') self._sd_id = sd_id
[docs] def format(self, record): try: record.__dict__['hostname'] = socket.gethostname() except Exception: record.__dict__['hostname'] = '-' isotime = datetime.datetime.fromtimestamp(record.created).isoformat() tz = self._tz_fix.match(time.strftime('%z')) if time.timezone and tz: (offset_hrs, offset_min) = tz.groups() if int(offset_hrs) == 0 and int(offset_min) == 0: isotime = isotime + 'Z' else: isotime = '{0}{1}:{2}'.format(isotime, offset_hrs, offset_min) else: isotime = isotime + 'Z' record.__dict__['isotime'] = isotime record.__dict__['procid'] = self.procid if self.procid else record.process record.__dict__['msgid'] = self.msgid if self.msgid else '-' if 'structured_data' in record.args: if not isinstance(record.args['structured_data'], dict): raise ValueError('structured_data must be a dict') all_sddata = {} default_sdparam = {} for key, value in record.args['structured_data'].items(): if isinstance(value, dict): all_sddata[key] = value else: default_sdparam[key] = value if len(default_sdparam) > 0: if self.sd_id in all_sddata: raise InvalidSDIDError('Cannot use same SD-ID twice') if not self.sd_id: raise InvalidSDIDError('SD-ID cannot be empty') all_sddata[self.sd_id] = default_sdparam sd = '' for sdid, data in all_sddata.items(): sd += '[{0}'.format(sdid) for key, value in data.items(): escaped = str(value).replace('\\', '\\\\').replace(']', '\\]').\ replace('"', '\\"') sd += ' {0}="{1}"'.format(key, escaped) sd += ']' record.__dict__['sd'] = sd else: record.__dict__['sd'] = '-' for key in ('procid', 'msgid'): if key in record.args: record.__dict__[key] = record.args.pop(key) header = '1 {isotime} {hostname} {name} {procid} {msgid} {sd} '.format( **record.__dict__ ) return header + super(RFC5424Formatter, self).format(record)