-
Notifications
You must be signed in to change notification settings - Fork 1
New plugin: journal logs #38
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: development
Are you sure you want to change the base?
Changes from all commits
d2fd50f
f216ace
1d37038
5da215b
ed246f1
96c1e80
2920d4c
d6e620d
ee5fba3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
############################################################################### | ||
# | ||
# 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 .journal_plugin import JournalPlugin | ||
|
||
__all__ = ["JournalPlugin"] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
############################################################################### | ||
# | ||
# 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 nodescraper.base import InBandDataCollector | ||
from nodescraper.connection.inband import TextFileArtifact | ||
from nodescraper.enums import EventCategory, EventPriority, OSFamily | ||
from nodescraper.models import TaskResult | ||
|
||
from .journaldata import JournalData | ||
|
||
|
||
class JournalCollector(InBandDataCollector[JournalData, None]): | ||
"""Read journal log via journalctl.""" | ||
|
||
SUPPORTED_OS_FAMILY = {OSFamily.LINUX} | ||
DATA_MODEL = JournalData | ||
|
||
CMD = "ls -1 /var/log/journal/*/system* 2>/dev/null || true" | ||
|
||
def _shell_quote(self, s: str) -> str: | ||
"""single-quote fix. | ||
|
||
Args: | ||
s (str): path | ||
|
||
Returns: | ||
str: escaped path | ||
""" | ||
return "'" + s.replace("'", "'\"'\"'") + "'" | ||
|
||
def _flat_name(self, path: str) -> str: | ||
"""Flatten path name | ||
|
||
Args: | ||
path (str): path | ||
|
||
Returns: | ||
str: flattened path name | ||
""" | ||
return "journalctl__" + path.lstrip("/").replace("/", "__") + ".json" | ||
|
||
def _read_with_journalctl(self, path: str): | ||
"""Read journal logs using journalctl | ||
|
||
Args: | ||
path (str): path for log to read | ||
|
||
Returns: | ||
str|None: name of local journal log filed, or None if log was not read | ||
""" | ||
qp = self._shell_quote(path) | ||
cmd = f"journalctl --no-pager --system --all --file={qp} --output=json" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should not need to read from a file. Please also add '-o short-iso' for iso timestamps. |
||
res = self._run_sut_cmd(cmd, sudo=True, log_artifact=False, strip=False) | ||
|
||
if res.exit_code == 0: | ||
text = ( | ||
res.stdout.decode("utf-8", "replace") | ||
if isinstance(res.stdout, (bytes, bytearray)) | ||
else res.stdout | ||
) | ||
fname = self._flat_name(path) | ||
self.result.artifacts.append(TextFileArtifact(filename=fname, contents=text)) | ||
self.logger.info("Collected journal: %s", path) | ||
return fname | ||
|
||
return None | ||
|
||
def _get_journals(self) -> list[str]: | ||
"""Read journal log files on remote system | ||
|
||
Returns: | ||
list[str]: List of names of read logs | ||
""" | ||
list_res = self._run_sut_cmd(self.CMD, sudo=True) | ||
paths = [p.strip() for p in (list_res.stdout or "").splitlines() if p.strip()] | ||
|
||
if not paths: | ||
self._log_event( | ||
category=EventCategory.OS, | ||
description="No /var/log/journal files found (including rotations).", | ||
data={"list_exit_code": list_res.exit_code}, | ||
priority=EventPriority.WARNING, | ||
) | ||
return [] | ||
Comment on lines
+96
to
+106
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should not be necessary for journalctl logs. The journalctl command will automatically merge and display available logs, including rotated ones. We do not want to read log files individually and should instead rely on journalctls management of these files. |
||
|
||
collected, failed = [], [] | ||
for p in paths: | ||
self.logger.debug("Reading journal file: %s", p) | ||
fname = self._read_with_journalctl(p) | ||
if fname: | ||
collected.append(fname) | ||
else: | ||
failed.append(fname) | ||
|
||
if collected: | ||
self._log_event( | ||
category=EventCategory.OS, | ||
description="Collected journal logs.", | ||
data={"collected": collected}, | ||
priority=EventPriority.INFO, | ||
) | ||
self.result.message = self.result.message or "journalctl logs collected" | ||
|
||
if failed: | ||
self._log_event( | ||
category=EventCategory.OS, | ||
description="Some journal files could not be read with journalctl.", | ||
data={"failed": failed}, | ||
priority=EventPriority.WARNING, | ||
) | ||
|
||
return collected | ||
|
||
def collect_data(self, args=None) -> tuple[TaskResult, JournalData | None]: | ||
"""Collect journal lofs | ||
|
||
Args: | ||
args (_type_, optional): Collection args. Defaults to None. | ||
|
||
Returns: | ||
tuple[TaskResult, JournalData | None]: Tuple of results and data model or none. | ||
""" | ||
collected = self._get_journals() | ||
if collected: | ||
data = JournalData(journal_logs=collected) | ||
self.result.message = self.result.message or "Journal data collected" | ||
return self.result, data | ||
return self.result, None |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
############################################################################### | ||
# | ||
# 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 nodescraper.base import InBandDataPlugin | ||
|
||
from .journal_collector import JournalCollector | ||
from .journaldata import JournalData | ||
|
||
|
||
class JournalPlugin(InBandDataPlugin[JournalData, None, None]): | ||
"""Plugin for collection of journal data""" | ||
|
||
DATA_MODEL = JournalData | ||
|
||
COLLECTOR = JournalCollector |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
############################################################################### | ||
# | ||
# 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 nodescraper.models import DataModel | ||
|
||
|
||
class JournalData(DataModel): | ||
"""Data model for journal logs""" | ||
|
||
journal_logs: list[str] = [] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be moved into utils since it is now being used in multiple places