@@ -2995,6 +2995,95 @@ refer to the comments in the code snippet for more detailed information.
2995
2995
if __name__=='__main__':
2996
2996
main()
2997
2997
2998
+ 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
+
2998
3087
2999
3088
.. patterns-to-avoid:
3000
3089
0 commit comments