1#!/usr/bin/env python3
2#
3# Copyright (c) 2021 Intel Corporation
4# Copyright (c) 2024 Nordic Semiconductor ASA
5#
6# SPDX-License-Identifier: Apache-2.0
7
8"""
9Log Parser for Dictionary-based Logging
10
11This uses the JSON database file to decode the input binary
12log data and print the log messages.
13"""
14
15import argparse
16import binascii
17import logging
18import sys
19
20import dictionary_parser
21import parserlib
22
23LOGGER_FORMAT = "%(message)s"
24logger = logging.getLogger("parser")
25
26LOG_HEX_SEP = "##ZLOGV1##"
27
28
29def parse_args():
30    """Parse command line arguments"""
31    argparser = argparse.ArgumentParser(allow_abbrev=False)
32
33    argparser.add_argument("dbfile", help="Dictionary Logging Database file")
34    argparser.add_argument("logfile", help="Log Data file")
35    argparser.add_argument(
36        "--hex", action="store_true", help="Log Data file is in hexadecimal strings"
37    )
38    argparser.add_argument(
39        "--rawhex", action="store_true", help="Log file only contains hexadecimal log data"
40    )
41    argparser.add_argument("--debug", action="store_true", help="Print extra debugging information")
42
43    return argparser.parse_args()
44
45
46def read_log_file(args):
47    """
48    Read the log from file
49    """
50    logdata = None
51    hexdata = ''
52
53    # Open log data file for reading
54    if args.hex:
55        if args.rawhex:
56            # Simply log file with only hexadecimal data
57            logdata = dictionary_parser.utils.convert_hex_file_to_bin(args.logfile)
58        else:
59            hexdata = ''
60
61            with open(args.logfile, encoding="iso-8859-1") as hexfile:
62                for line in hexfile.readlines():
63                    hexdata += line.strip()
64
65            if LOG_HEX_SEP not in hexdata:
66                logger.error("ERROR: Cannot find start of log data, exiting...")
67                sys.exit(1)
68
69            idx = hexdata.index(LOG_HEX_SEP) + len(LOG_HEX_SEP)
70            hexdata = hexdata[idx:]
71
72            if len(hexdata) % 2 != 0:
73                # Make sure there are even number of characters
74                idx = int(len(hexdata) / 2) * 2
75                hexdata = hexdata[:idx]
76
77            idx = 0
78            while idx < len(hexdata):
79                # When running QEMU via west or ninja, there may be additional
80                # strings printed by QEMU, west or ninja (for example, QEMU
81                # is terminated, or user interrupted, etc). So we need to
82                # figure out where the end of log data stream by
83                # trying to convert from hex to bin.
84                idx += 2
85
86                try:
87                    binascii.unhexlify(hexdata[:idx])
88                except binascii.Error:
89                    idx -= 2
90                    break
91
92            logdata = binascii.unhexlify(hexdata[:idx])
93    else:
94        with open(args.logfile, "rb") as logfile:
95            if not logfile:
96                logger.error(f"ERROR: Cannot open binary log data file: {args.logfile}, exiting...")
97                sys.exit(1)
98            logdata = logfile.read()
99
100        # RTT logs add header information to the logdata, the actual log comes
101        # after newline following "Process:" line in logdata
102        if b"Process:" in logdata:
103            process_idx = logdata.find(b"Process:")
104            newline_idx = logdata.find(b"\n", process_idx)
105            if newline_idx != -1:
106                # Keep only the data after this newline
107                logdata = logdata[newline_idx + 1 :]
108                logger.debug("Found 'Process:' in the RTT header, trimmed data")
109
110    return logdata
111
112
113def main():
114    """Main function of log parser"""
115    args = parse_args()
116
117    # Setup logging for parser
118    logging.basicConfig(format=LOGGER_FORMAT)
119    if args.debug:
120        logger.setLevel(logging.DEBUG)
121    else:
122        logger.setLevel(logging.INFO)
123
124    log_parser = parserlib.get_log_parser(args.dbfile, logger)
125
126    logdata = read_log_file(args)
127    if logdata is None:
128        logger.error("ERROR: cannot read log from file: %s, exiting...", args.logfile)
129        sys.exit(1)
130
131    parsed_data_offset = parserlib.parser(logdata, log_parser, logger)
132    if parsed_data_offset != len(logdata):
133        logger.error(
134            'ERROR: Not all data was parsed, %d bytes left unparsed',
135            len(logdata) - parsed_data_offset,
136        )
137        sys.exit(1)
138
139
140if __name__ == "__main__":
141    main()
142