From: Luca Vizzarro <luca.vizzarro@arm.com>
To: dev@dpdk.org
Cc: Luca Vizzarro <luca.vizzarro@arm.com>,
Paul Szczepanek <paul.szczepanek@arm.com>,
Patrick Robb <probb@iol.unh.edu>
Subject: [PATCH 5/6] dts: make log files into artifacts
Date: Fri, 25 Jul 2025 16:11:40 +0100 [thread overview]
Message-ID: <20250725151503.87374-6-luca.vizzarro@arm.com> (raw)
In-Reply-To: <20250725151503.87374-1-luca.vizzarro@arm.com>
Make log files behave like artifacts as dictated by the Artifact class.
Implicitly, this will automatically place all the logs in a structured
manner.
Signed-off-by: Luca Vizzarro <luca.vizzarro@arm.com>
Reviewed-by: Paul Szczepanek <paul.szczepanek@arm.com>
---
dts/framework/logger.py | 113 +++++++++++++++++++++-----------------
dts/framework/runner.py | 5 +-
dts/framework/test_run.py | 17 ++----
3 files changed, 69 insertions(+), 66 deletions(-)
diff --git a/dts/framework/logger.py b/dts/framework/logger.py
index f43b442bc9..230513c01e 100644
--- a/dts/framework/logger.py
+++ b/dts/framework/logger.py
@@ -2,43 +2,54 @@
# Copyright(c) 2010-2014 Intel Corporation
# Copyright(c) 2022-2023 PANTHEON.tech s.r.o.
# Copyright(c) 2022-2023 University of New Hampshire
+# Copyright(c) 2025 Arm Limited
"""DTS logger module.
The module provides several additional features:
* The storage of DTS execution stages,
- * Logging to console, a human-readable log file and a machine-readable log file,
- * Optional log files for specific stages.
+ * Logging to console, a human-readable log artifact and a machine-readable log artifact,
+ * Optional log artifacts for specific stages.
"""
import logging
-from logging import FileHandler, StreamHandler
-from pathlib import Path
-from typing import ClassVar
+from logging import StreamHandler
+from typing import TYPE_CHECKING, ClassVar, NamedTuple
+
+if TYPE_CHECKING:
+ from framework.testbed_model.artifact import Artifact
date_fmt = "%Y/%m/%d %H:%M:%S"
stream_fmt = "%(asctime)s - %(stage)s - %(name)s - %(levelname)s - %(message)s"
dts_root_logger_name = "dts"
+class ArtifactHandler(NamedTuple):
+ """A logger handler with an associated artifact."""
+
+ artifact: "Artifact"
+ handler: StreamHandler
+
+
class DTSLogger(logging.Logger):
"""The DTS logger class.
The class extends the :class:`~logging.Logger` class to add the DTS execution stage information
to log records. The stage is common to all loggers, so it's stored in a class variable.
- Any time we switch to a new stage, we have the ability to log to an additional log file along
- with a supplementary log file with machine-readable format. These two log files are used until
- a new stage switch occurs. This is useful mainly for logging per test suite.
+ Any time we switch to a new stage, we have the ability to log to an additional log artifact
+ along with a supplementary log artifact with machine-readable format. These two log artifacts
+ are used until a new stage switch occurs. This is useful mainly for logging per test suite.
"""
_stage: ClassVar[str] = "pre_run"
- _extra_file_handlers: list[FileHandler] = []
+ _root_artifact_handlers: list[ArtifactHandler] = []
+ _extra_artifact_handlers: list[ArtifactHandler] = []
def __init__(self, *args, **kwargs):
- """Extend the constructor with extra file handlers."""
- self._extra_file_handlers = []
+ """Extend the constructor with extra artifact handlers."""
+ self._extra_artifact_handlers = []
super().__init__(*args, **kwargs)
def makeRecord(self, *args, **kwargs) -> logging.LogRecord:
@@ -56,7 +67,7 @@ def makeRecord(self, *args, **kwargs) -> logging.LogRecord:
record.stage = DTSLogger._stage
return record
- def add_dts_root_logger_handlers(self, verbose: bool, output_dir: str) -> None:
+ def add_dts_root_logger_handlers(self, verbose: bool) -> None:
"""Add logger handlers to the DTS root logger.
This method should be called only on the DTS root logger.
@@ -65,18 +76,16 @@ def add_dts_root_logger_handlers(self, verbose: bool, output_dir: str) -> None:
Three handlers are added:
* A console handler,
- * A file handler,
- * A supplementary file handler with machine-readable logs
+ * An artifact handler,
+ * A supplementary artifact handler with machine-readable logs
containing more debug information.
- All log messages will be logged to files. The log level of the console handler
+ All log messages will be logged to artifacts. The log level of the console handler
is configurable with `verbose`.
Args:
verbose: If :data:`True`, log all messages to the console.
If :data:`False`, log to console with the :data:`logging.INFO` level.
- output_dir: The directory where the log files will be located.
- The names of the log files correspond to the name of the logger instance.
"""
self.setLevel(1)
@@ -86,70 +95,76 @@ def add_dts_root_logger_handlers(self, verbose: bool, output_dir: str) -> None:
sh.setLevel(logging.INFO)
self.addHandler(sh)
- self._add_file_handlers(Path(output_dir, self.name))
+ self._root_artifact_handlers = self._add_artifact_handlers(self.name)
- def set_stage(self, stage: str, log_file_path: Path | None = None) -> None:
- """Set the DTS execution stage and optionally log to files.
+ def set_stage(self, stage: str, log_file_name: str | None = None) -> None:
+ """Set the DTS execution stage and optionally log to artifact files.
Set the DTS execution stage of the DTSLog class and optionally add
- file handlers to the instance if the log file name is provided.
+ artifact handlers to the instance if the log artifact file name is provided.
- The file handlers log all messages. One is a regular human-readable log file and
- the other one is a machine-readable log file with extra debug information.
+ The artifact handlers log all messages. One is a regular human-readable log artifact and
+ the other one is a machine-readable log artifact with extra debug information.
Args:
stage: The DTS stage to set.
- log_file_path: An optional path of the log file to use. This should be a full path
- (either relative or absolute) without suffix (which will be appended).
+ log_file_name: An optional name of the log artifact file to use. This should be without
+ suffix (which will be appended).
"""
- self._remove_extra_file_handlers()
+ self._remove_extra_artifact_handlers()
if DTSLogger._stage != stage:
self.info(f"Moving from stage '{DTSLogger._stage}' to stage '{stage}'.")
DTSLogger._stage = stage
- if log_file_path:
- self._extra_file_handlers.extend(self._add_file_handlers(log_file_path))
+ if log_file_name:
+ self._extra_artifact_handlers.extend(self._add_artifact_handlers(log_file_name))
- def _add_file_handlers(self, log_file_path: Path) -> list[FileHandler]:
- """Add file handlers to the DTS root logger.
+ def _add_artifact_handlers(self, log_file_name: str) -> list[ArtifactHandler]:
+ """Add artifact handlers to the DTS root logger.
- Add two type of file handlers:
+ Add two type of artifact handlers:
- * A regular file handler with suffix ".log",
- * A machine-readable file handler with suffix ".verbose.log".
+ * A regular artifact handler with suffix ".log",
+ * A machine-readable artifact handler with suffix ".verbose.log".
This format provides extensive information for debugging and detailed analysis.
Args:
- log_file_path: The full path to the log file without suffix.
+ log_file_name: The name of the artifact log file without suffix.
Returns:
- The newly created file handlers.
-
+ The newly created artifact handlers.
"""
- fh = FileHandler(f"{log_file_path}.log")
- fh.setFormatter(logging.Formatter(stream_fmt, date_fmt))
- self.addHandler(fh)
+ from framework.testbed_model.artifact import Artifact
+
+ log_artifact = Artifact("local", f"{log_file_name}.log")
+ handler = StreamHandler(log_artifact.open("w"))
+ handler.setFormatter(logging.Formatter(stream_fmt, date_fmt))
+ self.addHandler(handler)
- verbose_fh = FileHandler(f"{log_file_path}.verbose.log")
- verbose_fh.setFormatter(
+ verbose_log_artifact = Artifact("local", f"{log_file_name}.verbose.log")
+ verbose_handler = StreamHandler(verbose_log_artifact.open("w"))
+ verbose_handler.setFormatter(
logging.Formatter(
"%(asctime)s|%(stage)s|%(name)s|%(levelname)s|%(pathname)s|%(lineno)d|"
"%(funcName)s|%(process)d|%(thread)d|%(threadName)s|%(message)s",
datefmt=date_fmt,
)
)
- self.addHandler(verbose_fh)
+ self.addHandler(verbose_handler)
- return [fh, verbose_fh]
+ return [
+ ArtifactHandler(log_artifact, handler),
+ ArtifactHandler(verbose_log_artifact, verbose_handler),
+ ]
- def _remove_extra_file_handlers(self) -> None:
- """Remove any extra file handlers that have been added to the logger."""
- if self._extra_file_handlers:
- for extra_file_handler in self._extra_file_handlers:
- self.removeHandler(extra_file_handler)
+ def _remove_extra_artifact_handlers(self) -> None:
+ """Remove any extra artifact handlers that have been added to the logger."""
+ if self._extra_artifact_handlers:
+ for extra_artifact_handler in self._extra_artifact_handlers:
+ self.removeHandler(extra_artifact_handler.handler)
- self._extra_file_handlers = []
+ self._extra_artifact_handlers = []
def get_dts_logger(name: str | None = None) -> DTSLogger:
diff --git a/dts/framework/runner.py b/dts/framework/runner.py
index 0a3d92b0c8..ae5ac014e2 100644
--- a/dts/framework/runner.py
+++ b/dts/framework/runner.py
@@ -9,7 +9,6 @@
The module is responsible for preparing DTS and running the test run.
"""
-import os
import sys
import textwrap
@@ -45,9 +44,7 @@ def __init__(self):
sys.exit(e.severity)
self._logger = get_dts_logger()
- if not os.path.exists(SETTINGS.output_dir):
- os.makedirs(SETTINGS.output_dir)
- self._logger.add_dts_root_logger_handlers(SETTINGS.verbose, SETTINGS.output_dir)
+ self._logger.add_dts_root_logger_handlers(SETTINGS.verbose)
test_suites_result = ResultNode(label="test_suites")
self._result = TestRunResult(test_suites=test_suites_result)
diff --git a/dts/framework/test_run.py b/dts/framework/test_run.py
index f70580f8fd..5609380c95 100644
--- a/dts/framework/test_run.py
+++ b/dts/framework/test_run.py
@@ -103,7 +103,6 @@
from collections.abc import Iterable
from dataclasses import dataclass
from functools import cached_property
-from pathlib import Path
from types import MethodType
from typing import ClassVar, Protocol, Union
@@ -115,7 +114,6 @@
from framework.settings import SETTINGS
from framework.test_result import Result, ResultNode, TestRunResult
from framework.test_suite import BaseConfig, TestCase, TestSuite
-from framework.testbed_model.artifact import Artifact
from framework.testbed_model.capability import (
Capability,
get_supported_capabilities,
@@ -259,11 +257,11 @@ class State(Protocol):
test_run: TestRun
result: TestRunResult | ResultNode
- def before(self):
+ def before(self) -> None:
"""Hook before the state is processed."""
- self.logger.set_stage(self.logger_name, self.log_file_path)
+ self.logger.set_stage(self.logger_name, self.get_log_file_name())
- def after(self):
+ def after(self) -> None:
"""Hook after the state is processed."""
return
@@ -280,13 +278,6 @@ def get_log_file_name(self) -> str | None:
"""Name of the log file for this state."""
return None
- @property
- def log_file_path(self) -> Path | None:
- """Path to the log file for this state."""
- if file_name := self.get_log_file_name():
- return Path(SETTINGS.output_dir, file_name)
- return None
-
def next(self) -> Union["State", None]:
"""Next state."""
@@ -604,7 +595,7 @@ class TestCaseState(State):
def get_log_file_name(self) -> str | None:
"""Get the log file name."""
- return self.test_suite.name
+ return self.test_case.name
@dataclass
--
2.43.0
next prev parent reply other threads:[~2025-07-25 15:15 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-07-25 15:11 [PATCH 0/6] dts: add file management Luca Vizzarro
2025-07-25 15:11 ` [PATCH 1/6] dts: merge RemoteSession class Luca Vizzarro
2025-07-25 15:11 ` [PATCH 2/6] dts: add node retriever by identifier Luca Vizzarro
2025-07-25 15:11 ` [PATCH 3/6] dts: add current test suite and cases to context Luca Vizzarro
2025-07-25 15:11 ` [PATCH 4/6] dts: add artifact module Luca Vizzarro
2025-07-25 15:11 ` Luca Vizzarro [this message]
2025-07-25 15:11 ` [PATCH 6/6] dts: use artifacts in packet capture and softnic Luca Vizzarro
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20250725151503.87374-6-luca.vizzarro@arm.com \
--to=luca.vizzarro@arm.com \
--cc=dev@dpdk.org \
--cc=paul.szczepanek@arm.com \
--cc=probb@iol.unh.edu \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).