Skip to content

Commit 1fe9aee

Browse files
committed
Syslog log collector similar to dmesg log collection
1 parent 752247a commit 1fe9aee

File tree

4 files changed

+246
-0
lines changed

4 files changed

+246
-0
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
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 .syslog_plugin import SyslogPlugin
27+
28+
__all__ = ["SyslogPlugin"]
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
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+
import re
27+
28+
from nodescraper.base import InBandDataCollector
29+
from nodescraper.connection.inband.inband import TextFileArtifact
30+
from nodescraper.enums import EventCategory, EventPriority, OSFamily
31+
from nodescraper.models import TaskResult
32+
33+
from .syslogdata import SyslogData
34+
35+
36+
class SyslogCollector(InBandDataCollector[SyslogData, None]):
37+
"""Read syslog log"""
38+
39+
SUPPORTED_OS_FAMILY = {OSFamily.LINUX}
40+
41+
DATA_MODEL = SyslogData
42+
43+
DMESG_LOGS_CMD = r"ls -1 /var/log/syslog* 2>/dev/null | grep -E '^/var/log/syslog(\.[0-9]+(\.gz)?)?$' || true"
44+
45+
def _shell_quote(self, s: str) -> str:
46+
"""single-quote fix."""
47+
return "'" + s.replace("'", "'\"'\"'") + "'"
48+
49+
def _nice_syslog_name(self, path: str) -> str:
50+
"""Map path to filename."""
51+
base = path.rstrip("/").rsplit("/", 1)[-1]
52+
if base == "syslog":
53+
return "syslog_log.log"
54+
m = re.fullmatch(r"syslog\.(\d+)\.gz", base)
55+
if m:
56+
return f"syslog.{m.group(1)}.gz.log"
57+
m = re.fullmatch(r"syslog\.(\d+)", base)
58+
if m:
59+
return f"syslog.{m.group(1)}.log"
60+
61+
return (base[:-3] if base.endswith(".gz") else base) + ".log"
62+
63+
def _collect_syslog_rotations(self) -> int:
64+
ret = 0
65+
list_res = self._run_sut_cmd(self.DMESG_LOGS_CMD, sudo=True)
66+
paths = [p.strip() for p in (list_res.stdout or "").splitlines() if p.strip()]
67+
if not paths:
68+
self._log_event(
69+
category=EventCategory.OS,
70+
description="No /var/log/syslog files found (including rotations).",
71+
data={"list_exit_code": list_res.exit_code},
72+
priority=EventPriority.WARNING,
73+
)
74+
return 0
75+
76+
collected_logs, failed_logs = [], []
77+
for p in paths:
78+
qp = self._shell_quote(p)
79+
if p.endswith(".gz"):
80+
cmd = f"gzip -dc {qp} 2>/dev/null || zcat {qp} 2>/dev/null"
81+
res = self._run_sut_cmd(cmd, sudo=True, log_artifact=False)
82+
if res.exit_code == 0 and res.stdout is not None:
83+
fname = self._nice_syslog_name(p)
84+
self.logger.info("Collected syslog log: %s", fname)
85+
self.result.artifacts.append(
86+
TextFileArtifact(filename=fname, contents=res.stdout)
87+
)
88+
collected_logs.append(
89+
{"path": p, "as": fname, "bytes": len(res.stdout.encode("utf-8", "ignore"))}
90+
)
91+
else:
92+
failed_logs.append(
93+
{"path": p, "exit_code": res.exit_code, "stderr": res.stderr, "cmd": cmd}
94+
)
95+
else:
96+
cmd = f"cat {qp}"
97+
res = self._run_sut_cmd(cmd, sudo=True, log_artifact=False)
98+
if res.exit_code == 0 and res.stdout is not None:
99+
fname = self._nice_syslog_name(p)
100+
self.logger.info("Collected syslog log: %s", fname)
101+
self.result.artifacts.append(
102+
TextFileArtifact(filename=fname, contents=res.stdout)
103+
)
104+
collected_logs.append(
105+
{"path": p, "as": fname, "bytes": len(res.stdout.encode("utf-8", "ignore"))}
106+
)
107+
else:
108+
failed_logs.append(
109+
{"path": p, "exit_code": res.exit_code, "stderr": res.stderr, "cmd": cmd}
110+
)
111+
112+
if collected_logs:
113+
self._log_event(
114+
category=EventCategory.OS,
115+
description="Collected syslog rotated files",
116+
data={"collected": collected_logs},
117+
priority=EventPriority.INFO,
118+
)
119+
self.result.message = self.result.message or "syslog rotated files collected"
120+
121+
if failed_logs:
122+
self._log_event(
123+
category=EventCategory.OS,
124+
description="Some syslog files could not be collected.",
125+
data={"failed": failed_logs},
126+
priority=EventPriority.WARNING,
127+
)
128+
129+
if collected_logs:
130+
ret = len(collected_logs)
131+
return ret
132+
133+
def collect_data(
134+
self,
135+
args=None,
136+
) -> tuple[TaskResult, SyslogData | None]:
137+
"""Collect syslog data from the system
138+
139+
Returns:
140+
tuple[TaskResult | None]: tuple containing the result of the task and the syslog data if available
141+
"""
142+
syslog_logs = self._collect_syslog_rotations()
143+
144+
if syslog_logs:
145+
syslog_data = SyslogData(syslog_logs=syslog_logs)
146+
self.result.message = "Syslog data collected"
147+
return self.result, syslog_data
148+
149+
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 .syslog_collector import SyslogCollector
29+
from .syslogdata import SyslogData
30+
31+
32+
class SyslogPlugin(InBandDataPlugin[SyslogData, None, None]):
33+
"""Plugin for collection of syslog data"""
34+
35+
DATA_MODEL = SyslogData
36+
37+
COLLECTOR = SyslogCollector
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 SyslogData(DataModel):
30+
"""Data model for in band syslog logs"""
31+
32+
syslog_logs: int = 0

0 commit comments

Comments
 (0)