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, asdict 17import re 18from typing import Mapping, Iterator 19 20 21def _mask(value: int, start: int, count: int) -> int: 22 mask = (1 << count) - 1 23 return (value & (mask << start)) >> start 24 25 26@dataclass 27class Metadata: 28 """Parses the metadata payload used by pw_log_tokenized.""" 29 30 value: int 31 log_bits: int = 3 32 line_bits: int = 11 33 flag_bits: int = 2 34 module_bits: int = 16 35 36 def __post_init__(self): 37 self.log_level = _mask(self.value, 0, self.log_bits) 38 self.line = _mask(self.value, self.log_bits, self.line_bits) 39 self.flags = _mask( 40 self.value, self.log_bits + self.line_bits, self.flag_bits 41 ) 42 self.module_token = _mask( 43 self.value, 44 self.log_bits + self.line_bits + self.flag_bits, 45 self.module_bits, 46 ) 47 48 def __iter__(self): 49 return iter(asdict(self).items()) 50 51 def __dict__(self): 52 return asdict(self) 53 54 55class FormatStringWithMetadata: 56 """Parses metadata from a log format string with metadata fields.""" 57 58 _FIELD_KEY = re.compile(r'■([a-zA-Z]\w*)♦', flags=re.ASCII) 59 60 def __init__(self, string: str) -> None: 61 self.raw_string = string 62 self.fields: dict[str, str] = {} 63 64 # Only look for fields if the raw string starts with one. 65 if self._FIELD_KEY.match(self.raw_string): 66 fields = self._FIELD_KEY.split(self.raw_string)[1:] 67 for name, value in zip(fields[::2], fields[1::2]): 68 self.fields[name] = value 69 70 @property 71 def message(self) -> str: 72 """Displays the msg field or the whole string if it is not present.""" 73 return self.fields.get('msg', self.raw_string) 74 75 @property 76 def module(self) -> str: 77 return self.fields.get('module', '') 78 79 @property 80 def file(self) -> str: 81 return self.fields.get('file', '') 82 83 def __repr__(self) -> str: 84 return self.message 85