hat.syslog.encoder
Syslog message encoder/decoder
1"""Syslog message encoder/decoder""" 2 3import datetime 4import re 5 6from hat import json 7 8from hat.syslog import common 9 10 11def msg_to_str(msg: common.Msg) -> str: 12 """Create string representation of message according to RFC 5424""" 13 buff = [ 14 f'<{msg.facility.value * 8 + msg.severity.value}>{msg.version}', 15 _timestamp_to_str(msg.timestamp), 16 msg.hostname if msg.hostname else '-', 17 msg.app_name if msg.app_name else '-', 18 msg.procid if msg.procid else '-', 19 msg.msgid if msg.msgid else '-', 20 _data_to_str(msg.data)] 21 if msg.msg: 22 buff.append('BOM' + msg.msg) 23 return ' '.join(buff) 24 25 26def msg_from_str(msg_str: str) -> common.Msg: 27 """Parse message string formatted according to RFC 5424""" 28 match = _msg_pattern.fullmatch(msg_str).groupdict() 29 prival = int(match['prival']) 30 return common.Msg( 31 facility=common.Facility(prival // 8), 32 severity=common.Severity(prival % 8), 33 version=int(match['version']), 34 timestamp=_parse_timestamp(match['timestamp']), 35 hostname=None if match['hostname'] == '-' else match['hostname'], 36 app_name=None if match['app_name'] == '-' else match['app_name'], 37 procid=None if match['procid'] == '-' else match['procid'], 38 msgid=None if match['msgid'] == '-' else match['msgid'], 39 data=_parse_data(match['data']), 40 msg=(match['msg'][3:] if match['msg'] and match['msg'][:3] == 'BOM' 41 else match['msg'])) 42 43 44def msg_to_json(msg: common.Msg) -> json.Data: 45 """Convert message to json serializable data""" 46 return {'facility': msg.facility.name, 47 'severity': msg.severity.name, 48 'version': msg.version, 49 'timestamp': msg.timestamp, 50 'hostname': msg.hostname, 51 'app_name': msg.app_name, 52 'procid': msg.procid, 53 'msgid': msg.msgid, 54 'data': msg.data, 55 'msg': msg.msg} 56 57 58def msg_from_json(data: json.Data) -> common.Msg: 59 """Convert json serializable data to message""" 60 return common.Msg(facility=common.Facility[data['facility']], 61 severity=common.Severity[data['severity']], 62 version=data['version'], 63 timestamp=data['timestamp'], 64 hostname=data['hostname'], 65 app_name=data['app_name'], 66 procid=data['procid'], 67 msgid=data['msgid'], 68 data=data['data'], 69 msg=data['msg']) 70 71 72_msg_pattern = re.compile(r''' 73 < (?P<prival> \d+) > 74 (?P<version> \d+) 75 \ (?P<timestamp> - | 76 [^ ]+) 77 \ (?P<hostname> - | 78 [^ ]+) 79 \ (?P<app_name> - | 80 [^ ]+) 81 \ (?P<procid> - | 82 [^ ]+) 83 \ (?P<msgid> - | 84 [^ ]+) 85 \ (?P<data> - | 86 (\[ 87 ((\\(\\\\)*\]) | 88 [^\]])* 89 \])+) 90 (\ (?P<msg> .*))? 91''', re.X | re.DOTALL) 92 93_timestamp_pattern = re.compile(r''' 94 (?P<year> \d{4}) 95 - 96 (?P<month> \d{2}) 97 - 98 (?P<day> \d{2}) 99 T 100 (?P<hour> \d{2}) 101 : 102 (?P<minute> \d{2}) 103 : 104 (?P<second> \d{2}) 105 (\. (?P<fraction> \d+))? 106 ((?P<tz_utc> Z) | 107 ((?P<tz_sign> \+ | 108 -) 109 (?P<tz_hour> \d{2}) 110 : 111 (?P<tz_minute> \d{2}))) 112''', re.X | re.DOTALL) 113 114_data_pattern = re.compile(r''' 115 \[ 116 (?P<id> [^ \]]+) 117 (?P<param> ((\\(\\\\)*\]) | 118 [^\]])*) 119 \] 120 (?P<rest> .*) 121''', re.X | re.DOTALL) 122 123_param_pattern = re.compile(r''' 124 \ (?P<name> [^=\]]+) 125 =" 126 (?P<value> ((\\\\) | 127 (\\") | 128 (\\\]) | 129 [^"\]\\])*) 130 " 131 (?P<rest> .*) 132''', re.X | re.DOTALL) 133 134_escape_pattern = re.compile(r'''((\\\\)|(\\")|(\\]))''') 135 136 137def _timestamp_to_str(timestamp): 138 if not timestamp: 139 return '-' 140 return datetime.datetime.fromtimestamp( 141 timestamp, datetime.timezone.utc).replace( 142 tzinfo=None).isoformat() + 'Z' 143 144 145def _data_to_str(data_json): 146 data = json.decode(data_json) if data_json else None 147 if not data: 148 return '-' 149 return ''.join(f'[{sd_id}{_param_to_str(param)}]' 150 for sd_id, param in data.items()) 151 152 153def _param_to_str(param): 154 if not param: 155 return '' 156 return ' ' + ' '.join(f'{k}="{_escape_value(v)}"' 157 for k, v in param.items()) 158 159 160def _parse_timestamp(timestamp_str): 161 if timestamp_str == '-': 162 return 163 match = _timestamp_pattern.fullmatch(timestamp_str).groupdict() 164 return datetime.datetime( 165 year=int(match['year']), 166 month=int(match['month']), 167 day=int(match['day']), 168 hour=int(match['hour']), 169 minute=int(match['minute']), 170 second=int(match['second']), 171 microsecond=(int(int(match['fraction']) * 172 pow(10, 6 - len(match['fraction']))) 173 if match['fraction'] else None), 174 tzinfo=(datetime.timezone.utc if match['tz_utc'] else 175 datetime.timezone(datetime.timedelta( 176 hours=((1 if match['tz_sign'] == '+' else -1) * 177 int(match['tz_hour'])), 178 minutes=int(match['tz_hour']))))).timestamp() 179 180 181def _parse_data(data_str): 182 if data_str == '-': 183 return 184 data = {} 185 while data_str: 186 match = _data_pattern.fullmatch(data_str).groupdict() 187 data[match['id']] = _parse_param(match['param']) 188 data_str = match['rest'] 189 data_json = json.encode(data) 190 return data_json 191 192 193def _parse_param(param_str): 194 param = {} 195 while param_str: 196 match = _param_pattern.fullmatch(param_str).groupdict() 197 param[match['name']] = _unescape_value(match['value']) 198 param_str = match['rest'] 199 return param 200 201 202def _escape_value(value): 203 return value.replace('\\', '\\\\').replace('"', '\\"').replace(']', '\\]') 204 205 206def _unescape_value(value): 207 return re.sub(_escape_pattern, _unescape_value_char, value) 208 209 210def _unescape_value_char(match): 211 return {r'\\': '\\', 212 r'\"': r'"', 213 r'\]': r']'}[match.group(0)]
12def msg_to_str(msg: common.Msg) -> str: 13 """Create string representation of message according to RFC 5424""" 14 buff = [ 15 f'<{msg.facility.value * 8 + msg.severity.value}>{msg.version}', 16 _timestamp_to_str(msg.timestamp), 17 msg.hostname if msg.hostname else '-', 18 msg.app_name if msg.app_name else '-', 19 msg.procid if msg.procid else '-', 20 msg.msgid if msg.msgid else '-', 21 _data_to_str(msg.data)] 22 if msg.msg: 23 buff.append('BOM' + msg.msg) 24 return ' '.join(buff)
Create string representation of message according to RFC 5424
27def msg_from_str(msg_str: str) -> common.Msg: 28 """Parse message string formatted according to RFC 5424""" 29 match = _msg_pattern.fullmatch(msg_str).groupdict() 30 prival = int(match['prival']) 31 return common.Msg( 32 facility=common.Facility(prival // 8), 33 severity=common.Severity(prival % 8), 34 version=int(match['version']), 35 timestamp=_parse_timestamp(match['timestamp']), 36 hostname=None if match['hostname'] == '-' else match['hostname'], 37 app_name=None if match['app_name'] == '-' else match['app_name'], 38 procid=None if match['procid'] == '-' else match['procid'], 39 msgid=None if match['msgid'] == '-' else match['msgid'], 40 data=_parse_data(match['data']), 41 msg=(match['msg'][3:] if match['msg'] and match['msg'][:3] == 'BOM' 42 else match['msg']))
Parse message string formatted according to RFC 5424
def
msg_to_json( msg: hat.syslog.common.Msg) -> None | bool | int | float | str | list[ForwardRef('Data')] | dict[str, ForwardRef('Data')]:
45def msg_to_json(msg: common.Msg) -> json.Data: 46 """Convert message to json serializable data""" 47 return {'facility': msg.facility.name, 48 'severity': msg.severity.name, 49 'version': msg.version, 50 'timestamp': msg.timestamp, 51 'hostname': msg.hostname, 52 'app_name': msg.app_name, 53 'procid': msg.procid, 54 'msgid': msg.msgid, 55 'data': msg.data, 56 'msg': msg.msg}
Convert message to json serializable data
def
msg_from_json( data: None | bool | int | float | str | list[ForwardRef('Data')] | dict[str, ForwardRef('Data')]) -> hat.syslog.common.Msg:
59def msg_from_json(data: json.Data) -> common.Msg: 60 """Convert json serializable data to message""" 61 return common.Msg(facility=common.Facility[data['facility']], 62 severity=common.Severity[data['severity']], 63 version=data['version'], 64 timestamp=data['timestamp'], 65 hostname=data['hostname'], 66 app_name=data['app_name'], 67 procid=data['procid'], 68 msgid=data['msgid'], 69 data=data['data'], 70 msg=data['msg'])
Convert json serializable data to message