Skip to content

Commit d2fd50f

Browse files
committed
added collector, needs utest
1 parent 752247a commit d2fd50f

File tree

3 files changed

+182
-0
lines changed

3 files changed

+182
-0
lines changed
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
###############################################################################
2+
#
3+
# MIT License
4+
#
5+
# Copyright (c) 2025 Advanced Micro Devices, Inc.
6+
#
7+
# Permission is hereby granted, free of charge, to any person obtaining a copy
8+
# of this software and associated documentation files (the "Software"), to deal
9+
# in the Software without restriction, including without limitation the rights
10+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
# copies of the Software, and to permit persons to whom the Software is
12+
# furnished to do so, subject to the following conditions:
13+
#
14+
# The above copyright notice and this permission notice shall be included in all
15+
# copies or substantial portions of the Software.
16+
#
17+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23+
# SOFTWARE.
24+
#
25+
###############################################################################
26+
from nodescraper.base import InBandDataCollector
27+
from nodescraper.connection.inband import TextFileArtifact
28+
from nodescraper.enums import EventCategory, EventPriority, OSFamily
29+
from nodescraper.models import TaskResult
30+
31+
from .journaldata import JournalData
32+
33+
34+
class JournalCollector(InBandDataCollector[JournalData, None]):
35+
"""Read journal log via journalctl."""
36+
37+
SUPPORTED_OS_FAMILY = {OSFamily.LINUX}
38+
DATA_MODEL = JournalData
39+
40+
CMD = "ls -1 /var/log/journal/*/system* 2>/dev/null || true"
41+
42+
def _shell_quote(self, s: str) -> str:
43+
return "'" + s.replace("'", "'\"'\"'") + "'"
44+
45+
def _flat_name(self, path: str) -> str:
46+
return "journalctl__" + path.lstrip("/").replace("/", "__") + ".jsonl"
47+
48+
def _read_with_journalctl(self, path: str):
49+
qp = self._shell_quote(path)
50+
cmd = f"journalctl --no-pager --system --all --file={qp} --output=json"
51+
res = self._run_sut_cmd(cmd, sudo=True, log_artifact=False, strip=False)
52+
53+
if res.exit_code == 0 and res.stdout:
54+
text = (
55+
res.stdout.decode("utf-8", "replace")
56+
if isinstance(res.stdout, (bytes, bytearray))
57+
else res.stdout
58+
)
59+
fname = self._flat_name(path)
60+
self.result.artifacts.append(TextFileArtifact(filename=fname, contents=text))
61+
self.logger.info("Collected journal: %s", path)
62+
return fname
63+
64+
return None
65+
66+
def _get_journals(self):
67+
list_res = self._run_sut_cmd(self.CMD, sudo=True)
68+
paths = [p.strip() for p in (list_res.stdout or "").splitlines() if p.strip()]
69+
70+
if not paths:
71+
self._log_event(
72+
category=EventCategory.OS,
73+
description="No /var/log/journal files found (including rotations).",
74+
data={"list_exit_code": list_res.exit_code},
75+
priority=EventPriority.WARNING,
76+
)
77+
return []
78+
79+
collected, failed = [], []
80+
for p in paths:
81+
self.logger.debug("Reading journal file: %s", p)
82+
fname = self._read_with_journalctl(p)
83+
if fname:
84+
collected.append(fname)
85+
else:
86+
failed.append(fname)
87+
88+
if collected:
89+
self._log_event(
90+
category=EventCategory.OS,
91+
description="Collected journal logs.",
92+
data={"collected": collected},
93+
priority=EventPriority.INFO,
94+
)
95+
self.result.message = self.result.message or "journalctl logs collected"
96+
97+
if failed:
98+
self._log_event(
99+
category=EventCategory.OS,
100+
description="Some journal files could not be read with journalctl.",
101+
data={"failed": failed},
102+
priority=EventPriority.WARNING,
103+
)
104+
105+
return collected
106+
107+
def collect_data(self, args=None) -> tuple[TaskResult, JournalData | None]:
108+
collected = self._get_journals()
109+
if collected:
110+
jd = JournalData(journal_logs=collected)
111+
self.result.message = self.result.message or "Journal data collected"
112+
return self.result, jd
113+
return self.result, None
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
###############################################################################
2+
#
3+
# MIT License
4+
#
5+
# Copyright (c) 2025 Advanced Micro Devices, Inc.
6+
#
7+
# Permission is hereby granted, free of charge, to any person obtaining a copy
8+
# of this software and associated documentation files (the "Software"), to deal
9+
# in the Software without restriction, including without limitation the rights
10+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
# copies of the Software, and to permit persons to whom the Software is
12+
# furnished to do so, subject to the following conditions:
13+
#
14+
# The above copyright notice and this permission notice shall be included in all
15+
# copies or substantial portions of the Software.
16+
#
17+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23+
# SOFTWARE.
24+
#
25+
###############################################################################
26+
from nodescraper.base import InBandDataPlugin
27+
28+
from .journal_collector import JournalCollector
29+
from .journaldata import JournalData
30+
31+
32+
class JournalPlugin(InBandDataPlugin[JournalData, None, None]):
33+
"""Plugin for collection of journal data"""
34+
35+
DATA_MODEL = JournalData
36+
37+
COLLECTOR = JournalCollector
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
###############################################################################
2+
#
3+
# MIT License
4+
#
5+
# Copyright (c) 2025 Advanced Micro Devices, Inc.
6+
#
7+
# Permission is hereby granted, free of charge, to any person obtaining a copy
8+
# of this software and associated documentation files (the "Software"), to deal
9+
# in the Software without restriction, including without limitation the rights
10+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
# copies of the Software, and to permit persons to whom the Software is
12+
# furnished to do so, subject to the following conditions:
13+
#
14+
# The above copyright notice and this permission notice shall be included in all
15+
# copies or substantial portions of the Software.
16+
#
17+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23+
# SOFTWARE.
24+
#
25+
###############################################################################
26+
from nodescraper.models import DataModel
27+
28+
29+
class JournalData(DataModel):
30+
"""Data model for journal logs"""
31+
32+
journal_logs: list[str] = None

0 commit comments

Comments
 (0)