Skip to content

Commit f0e9194

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 59faa7c commit f0e9194

File tree

3 files changed

+170
-28
lines changed

3 files changed

+170
-28
lines changed

doc/releases/migration-guide-4.3.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,15 @@ Other subsystems
112112

113113
.. zephyr-keep-sorted-start re(^\w)
114114
115+
Logging
116+
=======
117+
118+
* The UART dictionary log parsing script
119+
:zephyr_file:`scripts/logging/dictionary/log_parser_uart.py` has been deprecated. Instead, the
120+
more generic script of :zephyr_file:`scripts/logging/dictionary/live_log_parser.py` should be
121+
used. The new script supports the same functionality (and more), but requires different command
122+
line arguments when invoked.
123+
115124
.. zephyr-keep-sorted-stop
116125
117126
Modules
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
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 contextlib
17+
import logging
18+
import os
19+
import select
20+
import sys
21+
22+
import parserlib
23+
import serial
24+
25+
LOGGER_FORMAT = "%(message)s"
26+
logger = logging.getLogger("parser")
27+
28+
29+
class SerialReader:
30+
"""Class to read data from serial port and parse it"""
31+
32+
def __init__(self, serial_port, baudrate):
33+
self.serial_port = serial_port
34+
self.baudrate = baudrate
35+
self.serial = None
36+
37+
@contextlib.contextmanager
38+
def open(self):
39+
try:
40+
self.serial = serial.Serial(self.serial_port, self.baudrate)
41+
yield
42+
finally:
43+
self.serial.close()
44+
45+
def fileno(self):
46+
return self.serial.fileno()
47+
48+
def read_non_blocking(self):
49+
size = self.serial.in_waiting
50+
return self.serial.read(size)
51+
52+
53+
class FileReader:
54+
"""Class to read data from serial port and parse it"""
55+
56+
def __init__(self, filepath):
57+
self.filepath = filepath
58+
self.file = None
59+
60+
@contextlib.contextmanager
61+
def open(self):
62+
if self.filepath is not None:
63+
with open(self.filepath, 'rb') as f:
64+
self.file = f
65+
yield
66+
else:
67+
sys.stdin = os.fdopen(sys.stdin.fileno(), 'rb', 0)
68+
self.file = sys.stdin
69+
yield
70+
71+
def fileno(self):
72+
return self.file.fileno()
73+
74+
def read_non_blocking(self):
75+
# Read available data using a reasonable buffer size (without buffer size, this blocks
76+
# forever, but with buffer size it returns even when less data than the buffer read was
77+
# available).
78+
return self.file.read(1024)
79+
80+
81+
def parse_args():
82+
"""Parse command line arguments"""
83+
parser = argparse.ArgumentParser(allow_abbrev=False)
84+
85+
parser.add_argument("dbfile", help="Dictionary Logging Database file")
86+
parser.add_argument("--debug", action="store_true", help="Print extra debugging information")
87+
88+
# Create subparsers for different input modes
89+
subparsers = parser.add_subparsers(dest="mode", required=True, help="Input source mode")
90+
91+
# Serial subparser
92+
serial_parser = subparsers.add_parser("serial", help="Read from serial port")
93+
serial_parser.add_argument("port", help="Serial port")
94+
serial_parser.add_argument("baudrate", type=int, help="Baudrate")
95+
96+
# File subparser
97+
file_parser = subparsers.add_parser("file", help="Read from file")
98+
file_parser.add_argument(
99+
"filepath", nargs="?", default=None, help="Input file path, leave empty for stdin"
100+
)
101+
102+
return parser.parse_args()
103+
104+
105+
def main():
106+
"""function of serial parser"""
107+
args = parse_args()
108+
109+
if args.dbfile is None or '.json' not in args.dbfile:
110+
logger.error("ERROR: invalid log database path: %s, exiting...", args.dbfile)
111+
sys.exit(1)
112+
113+
logging.basicConfig(format=LOGGER_FORMAT)
114+
115+
if args.debug:
116+
logger.setLevel(logging.DEBUG)
117+
else:
118+
logger.setLevel(logging.INFO)
119+
120+
log_parser = parserlib.get_log_parser(args.dbfile, logger)
121+
122+
data = b''
123+
124+
if args.mode == "serial":
125+
reader = SerialReader(args.port, args.baudrate)
126+
elif args.mode == "file":
127+
reader = FileReader(args.filepath)
128+
else:
129+
raise ValueError("Invalid mode selected. Use 'serial' or 'file'.")
130+
131+
with reader.open():
132+
while True:
133+
ready, _, _ = select.select([reader], [], [])
134+
if ready:
135+
data += reader.read_non_blocking()
136+
parsed_data_offset = parserlib.parser(data, log_parser, logger)
137+
data = data[parsed_data_offset:]
138+
139+
140+
if __name__ == "__main__":
141+
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)