Skip to content

Commit 8145194

Browse files
core: update dump journal to deal with older journalctl
1 parent 44e0aee commit 8145194

File tree

1 file changed

+57
-1
lines changed

1 file changed

+57
-1
lines changed

core/tools/dump_journal/dump_journal.py

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import json
44
import logging
5+
import re
56
from datetime import datetime
67
from pathlib import Path
78
from typing import Optional
@@ -13,13 +14,68 @@
1314
logger = logging.getLogger(__name__)
1415

1516

17+
def parse_boot_list_table(output: str) -> list[dict]:
18+
"""
19+
Parse journalctl --list-boots table format output.
20+
21+
Format: <index> <boot_id> <start_time>—<end_time>
22+
Example: -12 8d18dbbee48a4a62868e139603ab7fd3 Fri 2025-09-05 13:17:01 BST—Fri 2025-09-05 13:44:34 BST
23+
"""
24+
boots = []
25+
for line in output.strip().split("\n"):
26+
line = line.strip()
27+
if not line:
28+
continue
29+
30+
# Match: <index> <boot_id> <rest of line>
31+
match = re.match(r"^\s*(-?\d+)\s+([0-9a-f]+)\s+(.+)$", line)
32+
if not match:
33+
continue
34+
35+
index = int(match.group(1))
36+
boot_id = match.group(2)
37+
time_range = match.group(3)
38+
39+
# Extract start time from the time range (before the em dash or en dash)
40+
# The separator is — (em dash U+2014) or – (en dash U+2013), NOT regular hyphen
41+
time_parts = re.split(r"[—–]", time_range, maxsplit=1)
42+
if time_parts:
43+
start_time_str = time_parts[0].strip()
44+
# Try to parse the timestamp - multiple formats possible
45+
# Format: "Fri 2025-09-05 13:17:01 BST"
46+
try:
47+
# Remove timezone abbreviation for simpler parsing
48+
time_no_tz = re.sub(r"\s+[A-Z]{2,4}$", "", start_time_str)
49+
# Parse without the weekday - split on first space to remove "Fri"
50+
date_time_part = time_no_tz.split(" ", 1)[1]
51+
dt = datetime.strptime(date_time_part, "%Y-%m-%d %H:%M:%S")
52+
first_entry = int(dt.timestamp() * 1000000) # Convert to microseconds
53+
except (ValueError, IndexError) as e:
54+
logger.warning(f"Could not parse timestamp '{start_time_str}': {e}")
55+
first_entry = 0
56+
else:
57+
first_entry = 0
58+
59+
boots.append({"index": index, "boot_id": boot_id, "first_entry": first_entry})
60+
61+
return boots
62+
63+
1664
def get_boot_info(boot_index: int) -> Optional[dict]:
1765
try:
1866
result = run_command("journalctl --list-boots --output=json")
1967
if result.returncode != 0:
2068
logger.error(f"Failed to get boot list: {result.stderr}")
2169
return None
22-
boots = json.loads(result.stdout)
70+
71+
# Try to parse as JSON first
72+
try:
73+
boots = json.loads(result.stdout)
74+
except json.JSONDecodeError as e:
75+
# Fallback: journalctl has a bug where --output=json is ignored for --list-boots
76+
logger.warning(f"JSON parsing failed ({e}), falling back to table format parser")
77+
boots = parse_boot_list_table(result.stdout)
78+
2379
boots_dict = {boot["index"]: boot for boot in boots}
2480
if boot_index not in boots_dict:
2581
logger.error(f"Boot index {boot_index} not found in boot list")

0 commit comments

Comments
 (0)