1# Copyright 2021 The Pigweed Authors 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); you may not 4# use this file except in compliance with the License. You may obtain a copy of 5# the License at 6# 7# https://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12# License for the specific language governing permissions and limitations under 13# the License. 14"""Tools for working with tokenized logs.""" 15 16from dataclasses import dataclass 17import re 18from typing import Dict, Mapping 19 20 21def _mask(value: int, start: int, count: int) -> int: 22 mask = (1 << count) - 1 23 return (value & (mask << start)) >> start 24 25 26class Metadata: 27 """Parses the metadata payload used by pw_log_tokenized.""" 28 def __init__(self, 29 value: int, 30 *, 31 log_bits: int = 3, 32 line_bits: int = 11, 33 flag_bits: int = 2, 34 module_bits: int = 16) -> None: 35 self.value = value 36 37 self.log_level = _mask(value, 0, log_bits) 38 self.line = _mask(value, log_bits, line_bits) 39 self.flags = _mask(value, log_bits + line_bits, flag_bits) 40 self.module_token = _mask(value, log_bits + line_bits + flag_bits, 41 module_bits) 42 43 def __repr__(self) -> str: 44 return (f'{type(self).__name__}(' 45 f'log_level={self.log_level}, ' 46 f'line={self.line}, ' 47 f'flags={self.flags}, ' 48 f'module_token={self.module_token})') 49 50 51class FormatStringWithMetadata: 52 """Parses metadata from a log format string with metadata fields.""" 53 _FIELD_KEY = re.compile(r'■([a-zA-Z]\w*)♦', flags=re.ASCII) 54 55 def __init__(self, string: str) -> None: 56 self.raw_string = string 57 self.fields: Dict[str, str] = {} 58 59 # Only look for fields if the raw string starts with one. 60 if self._FIELD_KEY.match(self.raw_string): 61 fields = self._FIELD_KEY.split(self.raw_string)[1:] 62 for name, value in zip(fields[::2], fields[1::2]): 63 self.fields[name] = value 64 65 @property 66 def message(self) -> str: 67 """Displays the msg field or the whole string if it is not present.""" 68 return self.fields.get('msg', self.raw_string) 69 70 @property 71 def module(self) -> str: 72 return self.fields.get('module', '') 73 74 @property 75 def file(self) -> str: 76 return self.fields.get('file', '') 77 78 def __repr__(self) -> str: 79 return self.message 80