Skip to content

Commit 3766f9b

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 14bed32 commit 3766f9b

File tree

3 files changed

+169
-28
lines changed

3 files changed

+169
-28
lines changed

doc/releases/migration-guide-4.3.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,15 @@ Networking
4141
Other subsystems
4242
****************
4343

44+
Logging
45+
=======
46+
47+
* The UART dictionary log parsing script
48+
:zephyr_file:`scripts/logging/dictionary/log_parser_uart.py` has been deprecated. Instead, the
49+
more generic script of :zephyr_file:`scripts/logging/dictionary/live_log_parser.py` should be
50+
used. The new script supports the same functionality (and more), but requires different command
51+
line arguments when invoked.
52+
4453
Modules
4554
*******
4655

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
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("filepath", nargs="?", default=None,
99+
help="Input file path, leave empty for stdin")
100+
101+
return parser.parse_args()
102+
103+
104+
def main():
105+
"""function of serial parser"""
106+
args = parse_args()
107+
108+
if args.dbfile is None or '.json' not in args.dbfile:
109+
logger.error("ERROR: invalid log database path: %s, exiting...", args.dbfile)
110+
sys.exit(1)
111+
112+
logging.basicConfig(format=LOGGER_FORMAT)
113+
114+
if args.debug:
115+
logger.setLevel(logging.DEBUG)
116+
else:
117+
logger.setLevel(logging.INFO)
118+
119+
log_parser = parserlib.get_log_parser(args.dbfile, logger)
120+
121+
data = b''
122+
123+
if args.mode == "serial":
124+
reader = SerialReader(args.port, args.baudrate)
125+
elif args.mode == "file":
126+
reader = FileReader(args.filepath)
127+
else:
128+
raise ValueError("Invalid mode selected. Use 'serial' or 'file'.")
129+
130+
with reader.open():
131+
while True:
132+
ready, _, _ = select.select([reader], [], [])
133+
if ready:
134+
data += reader.read_non_blocking()
135+
parsed_data_offset = parserlib.parser(data, log_parser, logger)
136+
data = data[parsed_data_offset:]
137+
138+
139+
if __name__ == "__main__":
140+
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)