From d54fb25a453ca425966bd48aa392fca3c2954bff Mon Sep 17 00:00:00 2001 From: Jaspal Singh Date: Fri, 17 Oct 2025 12:47:05 -0500 Subject: [PATCH 1/4] journal techsupport changes --- .../plugins/inband/journal/collector_args.py | 33 +++++++++++++++++++ .../inband/journal/journal_collector.py | 28 +++++++++++++--- .../plugins/inband/journal/journal_plugin.py | 5 ++- 3 files changed, 60 insertions(+), 6 deletions(-) create mode 100644 nodescraper/plugins/inband/journal/collector_args.py diff --git a/nodescraper/plugins/inband/journal/collector_args.py b/nodescraper/plugins/inband/journal/collector_args.py new file mode 100644 index 00000000..583c94ab --- /dev/null +++ b/nodescraper/plugins/inband/journal/collector_args.py @@ -0,0 +1,33 @@ +############################################################################### +# +# MIT License +# +# Copyright (c) 2025 Advanced Micro Devices, Inc. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# +############################################################################### + +from typing import Optional + +from nodescraper.models import CollectorArgs + + +class JournalCollectorArgs(CollectorArgs): + boot: Optional[int] = None diff --git a/nodescraper/plugins/inband/journal/journal_collector.py b/nodescraper/plugins/inband/journal/journal_collector.py index 90c44e5f..03d33e43 100644 --- a/nodescraper/plugins/inband/journal/journal_collector.py +++ b/nodescraper/plugins/inband/journal/journal_collector.py @@ -23,28 +23,40 @@ # SOFTWARE. # ############################################################################### +from typing import Optional from nodescraper.base import InBandDataCollector from nodescraper.enums import EventCategory, EventPriority, ExecutionStatus, OSFamily from nodescraper.models import TaskResult +from .collector_args import JournalCollectorArgs from .journaldata import JournalData -class JournalCollector(InBandDataCollector[JournalData, None]): +class JournalCollector(InBandDataCollector[JournalData, JournalCollectorArgs]): """Read journal log via journalctl.""" SUPPORTED_OS_FAMILY = {OSFamily.LINUX} DATA_MODEL = JournalData - def _read_with_journalctl(self): + def _read_with_journalctl(self, args: Optional[JournalCollectorArgs] = None): """Read journal logs using journalctl Returns: str|None: system journal read """ - cmd = "journalctl --no-pager --system --output=short-iso" + boot_arg = "" + + if args is not None: + try: + # Accept ints or numeric strings + boot_arg = f" -b {args.boot}" + except (TypeError, ValueError): + pass + + cmd = f"journalctl --no-pager{boot_arg} --system --output=short-iso" res = self._run_sut_cmd(cmd, sudo=True, log_artifact=False, strip=False) + self.result.message = cmd if res.exit_code != 0: self._log_event( @@ -60,7 +72,10 @@ def _read_with_journalctl(self): return res.stdout - def collect_data(self, args=None) -> tuple[TaskResult, JournalData | None]: + def collect_data( + self, + args: Optional[JournalCollectorArgs] = None, + ) -> tuple[TaskResult, JournalData | None]: """Collect journal logs Args: @@ -69,7 +84,10 @@ def collect_data(self, args=None) -> tuple[TaskResult, JournalData | None]: Returns: tuple[TaskResult, JournalData | None]: Tuple of results and data model or none. """ - journal_log = self._read_with_journalctl() + if args is None: + args = JournalCollectorArgs() + + journal_log = self._read_with_journalctl(args) if journal_log: data = JournalData(journal_log=journal_log) self.result.message = self.result.message or "Journal data collected" diff --git a/nodescraper/plugins/inband/journal/journal_plugin.py b/nodescraper/plugins/inband/journal/journal_plugin.py index 72ccca5a..a3044fbe 100644 --- a/nodescraper/plugins/inband/journal/journal_plugin.py +++ b/nodescraper/plugins/inband/journal/journal_plugin.py @@ -25,13 +25,16 @@ ############################################################################### from nodescraper.base import InBandDataPlugin +from .collector_args import JournalCollectorArgs from .journal_collector import JournalCollector from .journaldata import JournalData -class JournalPlugin(InBandDataPlugin[JournalData, None, None]): +class JournalPlugin(InBandDataPlugin[JournalData, JournalCollectorArgs, None]): """Plugin for collection of journal data""" DATA_MODEL = JournalData COLLECTOR = JournalCollector + + COLLECTOR_ARGS = JournalCollectorArgs From 754c5902a8b6e7819ae7baef68580e976a52b446 Mon Sep 17 00:00:00 2001 From: Jaspal Singh Date: Thu, 6 Nov 2025 19:57:10 -0600 Subject: [PATCH 2/4] change to check args/args.boot safely and wrap the call in a try/except that logs the exception --- .../inband/journal/journal_collector.py | 27 ++++++++++++++++--- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/nodescraper/plugins/inband/journal/journal_collector.py b/nodescraper/plugins/inband/journal/journal_collector.py index 48f98b4a..4bdc1508 100644 --- a/nodescraper/plugins/inband/journal/journal_collector.py +++ b/nodescraper/plugins/inband/journal/journal_collector.py @@ -47,11 +47,30 @@ def _read_with_journalctl(self, args: Optional[JournalCollectorArgs] = None): str|None: system journal read """ - if args is not None and args.boot: - boot_arg = f" -b {args.boot}" - self.CMD = f"journalctl --no-pager{boot_arg} --system --output=short-iso" + cmd = "journalctl --no-pager --system --output=short-iso" + try: + # safe check for args.boot + if args is not None and getattr(args, "boot", None): + cmd = f"journalctl --no-pager -b {args.boot} --system --output=short-iso" - res = self._run_sut_cmd(self.CMD, sudo=True, log_artifact=False, strip=False) + res = self._run_sut_cmd(cmd, sudo=True, log_artifact=False, strip=False) + + except Exception as exc: + + import traceback + + tb = traceback.format_exc() + self._log_event( + category=EventCategory.OS, + description="Exception while running journalctl", + data={"command": cmd, "exception": str(exc), "traceback": tb}, + priority=EventPriority.ERROR, + console_log=True, + ) + # set result to error and return None so upstream code can continue + self.result.message = "Could not read journalctl data" + self.result.status = ExecutionStatus.ERROR + return None if res.exit_code != 0: self._log_event( From d000770f21f1c2db18357ad1eb25f798de81bc19 Mon Sep 17 00:00:00 2001 From: Jaspal Singh Date: Wed, 12 Nov 2025 12:27:34 -0600 Subject: [PATCH 3/4] addressed review comments --- .../plugins/inband/journal/journal_collector.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/nodescraper/plugins/inband/journal/journal_collector.py b/nodescraper/plugins/inband/journal/journal_collector.py index 4bdc1508..6c8d0945 100644 --- a/nodescraper/plugins/inband/journal/journal_collector.py +++ b/nodescraper/plugins/inband/journal/journal_collector.py @@ -25,9 +25,12 @@ ############################################################################### from typing import Optional +from pydantic import ValidationError + from nodescraper.base import InBandDataCollector from nodescraper.enums import EventCategory, EventPriority, ExecutionStatus, OSFamily from nodescraper.models import TaskResult +from nodescraper.utils import get_exception_details from .collector_args import JournalCollectorArgs from .journaldata import JournalData @@ -55,15 +58,11 @@ def _read_with_journalctl(self, args: Optional[JournalCollectorArgs] = None): res = self._run_sut_cmd(cmd, sudo=True, log_artifact=False, strip=False) - except Exception as exc: - - import traceback - - tb = traceback.format_exc() + except ValidationError as val_err: self._log_event( category=EventCategory.OS, description="Exception while running journalctl", - data={"command": cmd, "exception": str(exc), "traceback": tb}, + data=get_exception_details(val_err), priority=EventPriority.ERROR, console_log=True, ) From 0bee7cecb8390640d284bc49f75b607562745a90 Mon Sep 17 00:00:00 2001 From: Jaspal Singh Date: Mon, 17 Nov 2025 14:28:50 -0600 Subject: [PATCH 4/4] fixed comment --- nodescraper/plugins/inband/journal/journal_collector.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nodescraper/plugins/inband/journal/journal_collector.py b/nodescraper/plugins/inband/journal/journal_collector.py index 6c8d0945..6b41dcc1 100644 --- a/nodescraper/plugins/inband/journal/journal_collector.py +++ b/nodescraper/plugins/inband/journal/journal_collector.py @@ -66,7 +66,6 @@ def _read_with_journalctl(self, args: Optional[JournalCollectorArgs] = None): priority=EventPriority.ERROR, console_log=True, ) - # set result to error and return None so upstream code can continue self.result.message = "Could not read journalctl data" self.result.status = ExecutionStatus.ERROR return None