Skip to content

Commit 24db2bb

Browse files
author
Omri Sarig
committed
scripts/logging: Create live_log_parser
Create a new, generic parser, that is able to parse dictionary logs live - showing the messages as they are received from the device. This functionality previously existed only for reading the log from a UART. With the new script, the functionality is extended to be able to also read the log from a file or stdin. Additionally, the script is written in an extend-able way, making it simple to support more input sources in the future. The new script contains a better version of the live functionality than the old script of log_parser_uart script, therefore, the old script has been deprecated. The UART script is still available, and will work by invoking the new implementation with relevant arguments translation, however, it should ideally not be used any longer, and should be removed from a future release. Signed-off-by: Omri Sarig <[email protected]>
1 parent 9243a50 commit 24db2bb

File tree

3 files changed

+158
-28
lines changed

3 files changed

+158
-28
lines changed

doc/releases/migration-guide-4.2.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -750,6 +750,15 @@ hawkBit
750750
be prepended with :kconfig:option:`CONFIG_BOARD`. It is the user's responsibility to write a
751751
callback that prepends the board name if needed.
752752

753+
Logging
754+
=======
755+
756+
* The UART dictionary log parsing script
757+
:zephyr_file:`scripts/logging/dictionary/log_parser_uart.py` has been deprecated. Instead, the
758+
more generic script of :zephyr_file:`scripts/logging/dictionary/live_log_parser.py` should be
759+
used. The new script supports the same functionality (and more), but requires different command
760+
line arguments when invoked.
761+
753762
State Machine Framework
754763
=======================
755764

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
#!/usr/bin/env python3
2+
#
3+
# Copyright (c) 2024 Nordic Semiconductor ASA
4+
#
5+
# SPDX-License-Identifier: Apache-2.0
6+
7+
"""
8+
Log Parser for Dictionary-based Logging
9+
10+
This uses the JSON database file to decode the binary
11+
log data taken directly from input serialport and print
12+
the log messages.
13+
"""
14+
15+
import argparse
16+
import logging
17+
import sys
18+
import select
19+
import os
20+
21+
import parserlib
22+
import serial
23+
24+
LOGGER_FORMAT = "%(message)s"
25+
logger = logging.getLogger("parser")
26+
27+
28+
class SerialReader:
29+
"""Class to read data from serial port and parse it"""
30+
31+
def __init__(self, serial_port, baudrate):
32+
self.serial = serial.Serial(serial_port, baudrate)
33+
34+
def close(self):
35+
self.serial.close()
36+
37+
def fileno(self):
38+
return self.serial.fileno()
39+
40+
def read_non_blocking(self):
41+
size = self.serial.in_waiting
42+
return self.serial.read(size)
43+
44+
45+
class FileReader:
46+
"""Class to read data from serial port and parse it"""
47+
48+
def __init__(self, filepath):
49+
if filepath is not None:
50+
self.file = open(filepath, 'rb')
51+
else:
52+
sys.stdin = os.fdopen(sys.stdin.fileno(), 'rb', 0)
53+
self.file = sys.stdin
54+
55+
def close(self):
56+
self.file.close()
57+
58+
def fileno(self):
59+
return self.file.fileno()
60+
61+
def read_non_blocking(self):
62+
# Read available data using a reasonable buffer size (without buffer size, this blocks
63+
# forever, but with buffer size it returns even when less data than the buffer read was
64+
# available).
65+
return self.file.read(1024)
66+
67+
68+
def parse_args():
69+
"""Parse command line arguments"""
70+
parser = argparse.ArgumentParser(allow_abbrev=False)
71+
72+
parser.add_argument("dbfile", help="Dictionary Logging Database file")
73+
parser.add_argument("--debug", action="store_true", help="Print extra debugging information")
74+
75+
# Create subparsers for different input modes
76+
subparsers = parser.add_subparsers(dest="mode", required=True, help="Input source mode")
77+
78+
# Serial subparser
79+
serial_parser = subparsers.add_parser("serial", help="Read from serial port")
80+
serial_parser.add_argument("port", help="Serial port")
81+
serial_parser.add_argument("baudrate", type=int, help="Baudrate")
82+
83+
# File subparser
84+
file_parser = subparsers.add_parser("file", help="Read from file")
85+
file_parser.add_argument("filepath", nargs="?", default=None,
86+
help="Input file path, leave empty for stdin")
87+
88+
return parser.parse_args()
89+
90+
91+
def main():
92+
"""function of serial parser"""
93+
args = parse_args()
94+
95+
if args.dbfile is None or '.json' not in args.dbfile:
96+
logger.error("ERROR: invalid log database path: %s, exiting...", args.dbfile)
97+
sys.exit(1)
98+
99+
logging.basicConfig(format=LOGGER_FORMAT)
100+
101+
if args.debug:
102+
logger.setLevel(logging.DEBUG)
103+
else:
104+
logger.setLevel(logging.INFO)
105+
106+
log_parser = parserlib.get_log_parser(args.dbfile, logger)
107+
108+
data = b''
109+
110+
if args.mode == "serial":
111+
reader = SerialReader(args.port, args.baudrate)
112+
elif args.mode == "file":
113+
reader = FileReader(args.filepath)
114+
else:
115+
raise ValueError("Invalid mode selected. Use 'serial' or 'file'.")
116+
117+
try:
118+
while True:
119+
ready, _, _ = select.select([reader], [], [])
120+
if ready:
121+
data += reader.read_non_blocking()
122+
parsed_data_offset = parserlib.parser(data, log_parser, logger)
123+
data = data[parsed_data_offset:]
124+
finally:
125+
reader.close()
126+
127+
128+
if __name__ == "__main__":
129+
main()

scripts/logging/dictionary/log_parser_uart.py

Lines changed: 20 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,10 @@
1313
"""
1414

1515
import argparse
16-
import logging
1716
import sys
18-
import time
1917

20-
import parserlib
21-
import serial
18+
import live_log_parser
2219

23-
LOGGER_FORMAT = "%(message)s"
24-
logger = logging.getLogger("parser")
2520

2621
def parse_args():
2722
"""Parse command line arguments"""
@@ -35,34 +30,31 @@ def parse_args():
3530

3631
return argparser.parse_args()
3732

33+
3834
def main():
3935
"""function of serial parser"""
40-
args = parse_args()
4136

42-
if args.dbfile is None or '.json' not in args.dbfile:
43-
logger.error("ERROR: invalid log database path: %s, exiting...", args.dbfile)
44-
sys.exit(1)
37+
print("This script is deprecated. Use 'live_log_parser.py' instead.",
38+
file=sys.stderr)
4539

46-
logging.basicConfig(format=LOGGER_FORMAT)
40+
# Convert the arguments to the format expected by live_log_parser, and invoke it directly.
41+
args = parse_args()
4742

43+
sys.argv = [
44+
'live_log_parser.py',
45+
args.dbfile,
46+
]
4847
if args.debug:
49-
logger.setLevel(logging.DEBUG)
50-
else:
51-
logger.setLevel(logging.INFO)
52-
53-
log_parser = parserlib.get_log_parser(args.dbfile, logger)
54-
55-
# Parse the log every second from serial port
56-
data = b''
57-
with serial.Serial(args.serialPort, args.baudrate) as ser:
58-
ser.timeout = 2
59-
while True:
60-
size = ser.inWaiting()
61-
if size:
62-
data += ser.read(size)
63-
parsed_data_offset = parserlib.parser(data, log_parser, logger)
64-
data = data[parsed_data_offset:]
65-
time.sleep(1)
48+
sys.argv.append('--debug')
49+
50+
sys.argv += [
51+
'serial',
52+
args.serialPort,
53+
args.baudrate
54+
]
55+
56+
live_log_parser.main()
57+
6658

6759
if __name__ == "__main__":
6860
main()

0 commit comments

Comments
 (0)