scripts/sbom: setup sbom logging

Add logging infrastructure for warnings and errors.
Errors and warnings are accumulated and summarized in the end.

Assisted-by: Cursor:claude-sonnet-4-5
Assisted-by: OpenCode:GLM-4-7
Co-developed-by: Maximilian Huber <maximilian.huber@tngtech.com>
Signed-off-by: Maximilian Huber <maximilian.huber@tngtech.com>
Signed-off-by: Luis Augenstein <luis.augenstein@tngtech.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
Luis Augenstein
2026-05-18 08:20:50 +02:00
committed by Greg Kroah-Hartman
parent e72b635cea
commit 3fd7920083
4 changed files with 165 additions and 1 deletions

View File

@@ -6,9 +6,33 @@
Compute software bill of materials in SPDX format describing a kernel build.
"""
import logging
import sys
import sbom.sbom_logging as sbom_logging
from sbom.config import get_config
def _exit_with_summary(write_output_on_error: bool = False) -> None:
warning_summary = sbom_logging.summarize_warnings()
error_summary = sbom_logging.summarize_errors()
if warning_summary:
logging.warning(warning_summary)
if error_summary:
logging.error(error_summary)
sys.exit(1)
def main():
pass
# Read config
config = get_config()
# Configure logging
logging.basicConfig(
level=logging.DEBUG if config.debug else logging.INFO,
format="[%(levelname)s] %(message)s",
)
_exit_with_summary(config.write_output_on_error)
# Call main method

View File

View File

@@ -0,0 +1,46 @@
# SPDX-License-Identifier: GPL-2.0-only OR MIT
# Copyright (C) 2025 TNG Technology Consulting GmbH
import argparse
from dataclasses import dataclass
@dataclass
class KernelSbomConfig:
debug: bool
"""Whether to enable debug logging."""
def _parse_cli_arguments(parser: argparse.ArgumentParser) -> dict[str, bool]:
"""
Parse command-line arguments using argparse.
Returns:
Dictionary of parsed arguments.
"""
parser.add_argument(
"--debug",
action="store_true",
default=False,
help="Enable debug logs (default: False)",
)
args = vars(parser.parse_args())
return args
def get_config() -> KernelSbomConfig:
"""
Parse command-line arguments and construct the configuration object.
Returns:
KernelSbomConfig: Configuration object with all settings for SBOM generation.
"""
parser = argparse.ArgumentParser(
description="Generate SPDX SBOM documents for kernel builds",
)
args = _parse_cli_arguments(parser)
debug = args["debug"]
return KernelSbomConfig(debug=debug)

View File

@@ -0,0 +1,94 @@
# SPDX-License-Identifier: GPL-2.0-only OR MIT
# Copyright (C) 2025 TNG Technology Consulting GmbH
import logging
import inspect
from typing import Literal
MessageTemplate = str
class MessageLogger:
"""Logger that suppresses repeated messages and stores a summary of all logged messages."""
_messages: dict[MessageTemplate, list[str]]
_message_counts: dict[MessageTemplate, int]
_repeated_logs_limit: int
"""Maximum number of repeated messages of the same type to log before suppressing further output."""
def __init__(self, level: Literal["error", "warning"], repeated_logs_limit: int = 3) -> None:
self._level = level
self._messages = {}
self._message_counts = {}
self._repeated_logs_limit = repeated_logs_limit
def log(self, template: MessageTemplate, /, **kwargs: str) -> None:
"""Log a message based on a template and optional variables. Example: `log("Missing {path}", path=str(p))`."""
message = template
for key, value in kwargs.items():
message = message.replace("{" + key + "}", value)
if template not in self._messages:
self._messages[template] = []
self._message_counts[template] = 0
self._message_counts[template] += 1
if self._message_counts[template] <= self._repeated_logs_limit:
if self._level == "error":
logging.error(message)
elif self._level == "warning":
logging.warning(message)
self._messages[template].append(message)
def get_summary(self) -> str:
if len(self._messages) == 0:
return ""
summary: list[str] = [f"Summarize {self._level}s:"]
for template, messages in self._messages.items():
for message in messages:
summary.append(message)
n_suppressed_messages = self._message_counts[template] - self._repeated_logs_limit
if n_suppressed_messages > 0:
instances = "instance" if n_suppressed_messages == 1 else "instances"
summary.append(f"... (Found {n_suppressed_messages} more {instances} of this {self._level})")
return "\n".join(summary)
def has_messages(self) -> bool:
return len(self._message_counts) > 0
_warning_logger: MessageLogger
_error_logger: MessageLogger
def warning(msg_template: MessageTemplate, /, **kwargs: str) -> None:
_warning_logger.log(msg_template, **kwargs)
def error(msg_template: MessageTemplate, /, **kwargs: str) -> None:
frame = inspect.currentframe()
caller_frame = frame.f_back if frame else None
info = inspect.getframeinfo(caller_frame) if caller_frame else None
if info:
msg_template = f'File "{info.filename}", line {info.lineno}, in {info.function}\n{msg_template}'
_error_logger.log(msg_template, **kwargs)
def summarize_warnings() -> str:
return _warning_logger.get_summary()
def summarize_errors() -> str:
return _error_logger.get_summary()
def has_errors() -> bool:
return _error_logger.has_messages()
def init() -> None:
global _warning_logger, _error_logger
_warning_logger = MessageLogger("warning")
_error_logger = MessageLogger("error")
init()