@@ -714,6 +714,32 @@ which, when run, produces something like:
714
714
2010-09-06 22:38:15,301 d.e.f DEBUG IP: 123.231.231.123 User: fred A message at DEBUG level with 2 parameters
715
715
2010-09-06 22:38:15,301 d.e.f INFO IP: 123.231.231.123 User: fred A message at INFO level with 2 parameters
716
716
717
+ Imparting contextual information in handlers
718
+ --------------------------------------------
719
+
720
+ Each :class: `~Handler ` has its own chain of filters.
721
+ If you want to add contextual information to a :class: `LogRecord ` without leaking
722
+ it to other handlers, you can use a filter that returns
723
+ a new :class: `~LogRecord ` instead of modifying it in-place, as shown in the following script::
724
+
725
+ import copy
726
+ import logging
727
+
728
+ def filter(record: logging.LogRecord):
729
+ record = copy.copy(record)
730
+ record.user = 'jim'
731
+ return record
732
+
733
+ if __name__ == '__main__':
734
+ logger = logging.getLogger()
735
+ logger.setLevel(logging.INFO)
736
+ handler = logging.StreamHandler()
737
+ formatter = logging.Formatter('%(message)s from %(user)-8s')
738
+ handler.setFormatter(formatter)
739
+ handler.addFilter(filter)
740
+ logger.addHandler(handler)
741
+
742
+ logger.info('A log message')
717
743
718
744
.. _multiple-processes :
719
745
@@ -2996,6 +3022,95 @@ refer to the comments in the code snippet for more detailed information.
2996
3022
if __name__=='__main__':
2997
3023
main()
2998
3024
3025
+ Logging to syslog with RFC5424 support
3026
+ --------------------------------------
3027
+
3028
+ Although :rfc: `5424 ` dates from 2009, most syslog servers are configured by detault to
3029
+ use the older :rfc: `3164 `, which hails from 2001. When ``logging `` was added to Python
3030
+ in 2003, it supported the earlier (and only existing) protocol at the time. Since
3031
+ RFC5424 came out, as there has not been widespread deployment of it in syslog
3032
+ servers, the :class: `~logging.handlers.SysLogHandler ` functionality has not been
3033
+ updated.
3034
+
3035
+ RFC 5424 contains some useful features such as support for structured data, and if you
3036
+ need to be able to log to a syslog server with support for it, you can do so with a
3037
+ subclassed handler which looks something like this::
3038
+
3039
+ import datetime
3040
+ import logging.handlers
3041
+ import re
3042
+ import socket
3043
+ import time
3044
+
3045
+ class SysLogHandler5424(logging.handlers.SysLogHandler):
3046
+
3047
+ tz_offset = re.compile(r'([+-]\d{2})(\d{2})$')
3048
+ escaped = re.compile(r'([\]"\\])')
3049
+
3050
+ def __init__(self, *args, **kwargs):
3051
+ self.msgid = kwargs.pop('msgid', None)
3052
+ self.appname = kwargs.pop('appname', None)
3053
+ super().__init__(*args, **kwargs)
3054
+
3055
+ def format(self, record):
3056
+ version = 1
3057
+ asctime = datetime.datetime.fromtimestamp(record.created).isoformat()
3058
+ m = self.tz_offset.match(time.strftime('%z'))
3059
+ has_offset = False
3060
+ if m and time.timezone:
3061
+ hrs, mins = m.groups()
3062
+ if int(hrs) or int(mins):
3063
+ has_offset = True
3064
+ if not has_offset:
3065
+ asctime += 'Z'
3066
+ else:
3067
+ asctime += f'{hrs}:{mins}'
3068
+ try:
3069
+ hostname = socket.gethostname()
3070
+ except Exception:
3071
+ hostname = '-'
3072
+ appname = self.appname or '-'
3073
+ procid = record.process
3074
+ msgid = '-'
3075
+ msg = super().format(record)
3076
+ sdata = '-'
3077
+ if hasattr(record, 'structured_data'):
3078
+ sd = record.structured_data
3079
+ # This should be a dict where the keys are SD-ID and the value is a
3080
+ # dict mapping PARAM-NAME to PARAM-VALUE (refer to the RFC for what these
3081
+ # mean)
3082
+ # There's no error checking here - it's purely for illustration, and you
3083
+ # can adapt this code for use in production environments
3084
+ parts = []
3085
+
3086
+ def replacer(m):
3087
+ g = m.groups()
3088
+ return '\\' + g[0]
3089
+
3090
+ for sdid, dv in sd.items():
3091
+ part = f'[{sdid}'
3092
+ for k, v in dv.items():
3093
+ s = str(v)
3094
+ s = self.escaped.sub(replacer, s)
3095
+ part += f' {k}="{s}"'
3096
+ part += ']'
3097
+ parts.append(part)
3098
+ sdata = ''.join(parts)
3099
+ return f'{version} {asctime} {hostname} {appname} {procid} {msgid} {sdata} {msg}'
3100
+
3101
+ You'll need to be familiar with RFC 5424 to fully understand the above code, and it
3102
+ may be that you have slightly different needs (e.g. for how you pass structural data
3103
+ to the log). Nevertheless, the above should be adaptable to your speciric needs. With
3104
+ the above handler, you'd pass structured data using something like this::
3105
+
3106
+ sd = {
3107
+ 'foo@12345': {'bar': 'baz', 'baz': 'bozz', 'fizz': r'buzz'},
3108
+ 'foo@54321': {'rab': 'baz', 'zab': 'bozz', 'zzif': r'buzz'}
3109
+ }
3110
+ extra = {'structured_data': sd}
3111
+ i = 1
3112
+ logger.debug('Message %d', i, extra=extra)
3113
+
2999
3114
3000
3115
.. patterns-to-avoid:
3001
3116
0 commit comments