[3.11] Improve logging documentation with example and additional cook… · python/cpython@7bfeb8a

GitHub

@@ -2995,6 +2995,95 @@ refer to the comments in the code snippet for more detailed information.

29952995 if __name__=='__main__':

29962996 main()

299729972998+Logging to syslog with RFC5424 support

2999+--------------------------------------

3000+3001+Although :rfc:`5424` dates from 2009, most syslog servers are configured by detault to

3002+use the older :rfc:`3164`, which hails from 2001. When ``logging`` was added to Python

3003+in 2003, it supported the earlier (and only existing) protocol at the time. Since

3004+RFC5424 came out, as there has not been widespread deployment of it in syslog

3005+servers, the :class:`~logging.handlers.SysLogHandler` functionality has not been

3006+updated.

3007+3008+RFC 5424 contains some useful features such as support for structured data, and if you

3009+need to be able to log to a syslog server with support for it, you can do so with a

3010+subclassed handler which looks something like this::

3011+3012+ import datetime

3013+ import logging.handlers

3014+ import re

3015+ import socket

3016+ import time

3017+3018+ class SysLogHandler5424(logging.handlers.SysLogHandler):

3019+3020+ tz_offset = re.compile(r'([+-]\d{2})(\d{2})$')

3021+ escaped = re.compile(r'([\]"\\])')

3022+3023+ def __init__(self, *args, **kwargs):

3024+ self.msgid = kwargs.pop('msgid', None)

3025+ self.appname = kwargs.pop('appname', None)

3026+ super().__init__(*args, **kwargs)

3027+3028+ def format(self, record):

3029+ version = 1

3030+ asctime = datetime.datetime.fromtimestamp(record.created).isoformat()

3031+ m = self.tz_offset.match(time.strftime('%z'))

3032+ has_offset = False

3033+ if m and time.timezone:

3034+ hrs, mins = m.groups()

3035+ if int(hrs) or int(mins):

3036+ has_offset = True

3037+ if not has_offset:

3038+ asctime += 'Z'

3039+ else:

3040+ asctime += f'{hrs}:{mins}'

3041+ try:

3042+ hostname = socket.gethostname()

3043+ except Exception:

3044+ hostname = '-'

3045+ appname = self.appname or '-'

3046+ procid = record.process

3047+ msgid = '-'

3048+ msg = super().format(record)

3049+ sdata = '-'

3050+ if hasattr(record, 'structured_data'):

3051+ sd = record.structured_data

3052+ # This should be a dict where the keys are SD-ID and the value is a

3053+ # dict mapping PARAM-NAME to PARAM-VALUE (refer to the RFC for what these

3054+ # mean)

3055+ # There's no error checking here - it's purely for illustration, and you

3056+ # can adapt this code for use in production environments

3057+ parts = []

3058+3059+ def replacer(m):

3060+ g = m.groups()

3061+ return '\\' + g[0]

3062+3063+ for sdid, dv in sd.items():

3064+ part = f'[{sdid}'

3065+ for k, v in dv.items():

3066+ s = str(v)

3067+ s = self.escaped.sub(replacer, s)

3068+ part += f' {k}="{s}"'

3069+ part += ']'

3070+ parts.append(part)

3071+ sdata = ''.join(parts)

3072+ return f'{version} {asctime} {hostname} {appname} {procid} {msgid} {sdata} {msg}'

3073+3074+You'll need to be familiar with RFC 5424 to fully understand the above code, and it

3075+may be that you have slightly different needs (e.g. for how you pass structural data

3076+to the log). Nevertheless, the above should be adaptable to your speciric needs. With

3077+the above handler, you'd pass structured data using something like this::

3078+3079+ sd = {

3080+ 'foo@12345': {'bar': 'baz', 'baz': 'bozz', 'fizz': r'buzz'},

3081+ 'foo@54321': {'rab': 'baz', 'zab': 'bozz', 'zzif': r'buzz'}

3082+ }

3083+ extra = {'structured_data': sd}

3084+ i = 1

3085+ logger.debug('Message %d', i, extra=extra)

3086+2998308729993088.. patterns-to-avoid:

30003089