xref: /aosp_15_r20/external/pigweed/pw_log_tokenized/py/pw_log_tokenized/__init__.py (revision 61c4878ac05f98d0ceed94b57d316916de578985)
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