• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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