@@ -10,108 +10,98 @@
import logging
import os.path
-from typing import TypedDict
-
-from .settings import SETTINGS
+from enum import Enum
+from logging import FileHandler, StreamHandler
+from pathlib import Path
date_fmt = "%Y/%m/%d %H:%M:%S"
-stream_fmt = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
+stream_fmt = "%(asctime)s - %(stage)s - %(name)s - %(levelname)s - %(message)s"
-class LoggerDictType(TypedDict):
- logger: "DTSLOG"
- name: str
- node: str
+def init_logger(verbose: bool, output_dir: str):
+ logging.raiseExceptions = False
+ DTSLog._output_dir = output_dir
+ logging.setLoggerClass(DTSLog)
-# List for saving all using loggers
-Loggers: list[LoggerDictType] = []
+ root_logger = logging.getLogger()
+ root_logger.setLevel(1)
+ sh = StreamHandler()
+ sh.setFormatter(logging.Formatter(stream_fmt, date_fmt))
+ sh.setLevel(logging.INFO)
+ if verbose:
+ sh.setLevel(logging.DEBUG)
+ root_logger.addHandler(sh)
-class DTSLOG(logging.LoggerAdapter):
- """
- DTS log class for framework and testsuite.
- """
+ if not os.path.exists(output_dir):
+ os.mkdir(output_dir)
- _logger: logging.Logger
- node: str
- sh: logging.StreamHandler
- fh: logging.FileHandler
- verbose_fh: logging.FileHandler
+ add_file_handlers(Path(output_dir, "dts"))
- def __init__(self, logger: logging.Logger, node: str = "suite"):
- self._logger = logger
- # 1 means log everything, this will be used by file handlers if their level
- # is not set
- self._logger.setLevel(1)
- self.node = node
+def add_file_handlers(log_file_path: Path) -> list[FileHandler]:
+ root_logger = logging.getLogger()
- # add handler to emit to stdout
- sh = logging.StreamHandler()
- sh.setFormatter(logging.Formatter(stream_fmt, date_fmt))
- sh.setLevel(logging.INFO) # console handler default level
+ fh = FileHandler(f"{log_file_path}.log")
+ fh.setFormatter(logging.Formatter(stream_fmt, date_fmt))
+ root_logger.addHandler(fh)
- if SETTINGS.verbose is True:
- sh.setLevel(logging.DEBUG)
+ verbose_fh = FileHandler(f"{log_file_path}.verbose.log")
+ verbose_fh.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,
+ )
+ )
+ root_logger.addHandler(verbose_fh)
- self._logger.addHandler(sh)
- self.sh = sh
+ return [fh, verbose_fh]
- # prepare the output folder
- if not os.path.exists(SETTINGS.output_dir):
- os.mkdir(SETTINGS.output_dir)
- logging_path_prefix = os.path.join(SETTINGS.output_dir, node)
+class DtsStage(Enum):
+ pre_execution = "pre-execution"
+ execution = "execution"
+ build_target = "build-target"
+ suite = "suite"
+ post_execution = "post-execution"
- fh = logging.FileHandler(f"{logging_path_prefix}.log")
- fh.setFormatter(
- logging.Formatter(
- fmt="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
- datefmt=date_fmt,
- )
- )
+ def __str__(self) -> str:
+ return self.value
- self._logger.addHandler(fh)
- self.fh = fh
-
- # This outputs EVERYTHING, intended for post-mortem debugging
- # Also optimized for processing via AWK (awk -F '|' ...)
- verbose_fh = logging.FileHandler(f"{logging_path_prefix}.verbose.log")
- verbose_fh.setFormatter(
- logging.Formatter(
- fmt="%(asctime)s|%(name)s|%(levelname)s|%(pathname)s|%(lineno)d|"
- "%(funcName)s|%(process)d|%(thread)d|%(threadName)s|%(message)s",
- datefmt=date_fmt,
- )
- )
- self._logger.addHandler(verbose_fh)
- self.verbose_fh = verbose_fh
-
- super(DTSLOG, self).__init__(self._logger, dict(node=self.node))
-
- def logger_exit(self) -> None:
- """
- Remove stream handler and logfile handler.
- """
- for handler in (self.sh, self.fh, self.verbose_fh):
- handler.flush()
- self._logger.removeHandler(handler)
-
-
-def getLogger(name: str, node: str = "suite") -> DTSLOG:
- """
- Get logger handler and if there's no handler for specified Node will create one.
- """
- global Loggers
- # return saved logger
- logger: LoggerDictType
- for logger in Loggers:
- if logger["name"] == name and logger["node"] == node:
- return logger["logger"]
-
- # return new logger
- dts_logger: DTSLOG = DTSLOG(logging.getLogger(name), node)
- Loggers.append({"logger": dts_logger, "name": name, "node": node})
- return dts_logger
+class DTSLog(logging.Logger):
+ _stage: DtsStage = DtsStage.pre_execution
+ _extra_file_handlers: list[FileHandler] = []
+ _output_dir: None | str = None
+
+ def makeRecord(self, *args, **kwargs):
+ record = super().makeRecord(*args, **kwargs)
+ record.stage = DTSLog._stage
+ return record
+
+ def set_stage(self, stage: DtsStage, log_file_name: str | None = None):
+ self._remove_extra_file_handlers()
+
+ if DTSLog._stage != stage:
+ self.info(f"Moving from stage '{DTSLog._stage}' to stage '{stage}'.")
+ DTSLog._stage = stage
+
+ if log_file_name:
+ if DTSLog._output_dir:
+ DTSLog._extra_file_handlers.extend(
+ add_file_handlers(Path(DTSLog._output_dir, log_file_name))
+ )
+ else:
+ self.warning(
+ f"Cannot log '{DTSLog._stage}' stage in separate file, "
+ "output dir is not defined."
+ )
+
+ def _remove_extra_file_handlers(self) -> None:
+ if DTSLog._extra_file_handlers:
+ for extra_file_handler in DTSLog._extra_file_handlers:
+ self.root.removeHandler(extra_file_handler)
+
+ DTSLog._extra_file_handlers = []
@@ -11,10 +11,10 @@
"""
# pylama:ignore=W0611
+import logging
from framework.config import OS, NodeConfiguration
from framework.exception import ConfigurationError
-from framework.logger import DTSLOG
from .linux_session import LinuxSession
from .os_session import InteractiveShellType, OSSession
@@ -30,7 +30,7 @@
)
-def create_session(node_config: NodeConfiguration, name: str, logger: DTSLOG) -> OSSession:
+def create_session(node_config: NodeConfiguration, name: str, logger: logging.Logger) -> OSSession:
match node_config.os:
case OS.linux:
return LinuxSession(node_config, name, logger)
@@ -2,6 +2,7 @@
# Copyright(c) 2023 PANTHEON.tech s.r.o.
# Copyright(c) 2023 University of New Hampshire
+import logging
from abc import ABC, abstractmethod
from collections.abc import Iterable
from ipaddress import IPv4Interface, IPv6Interface
@@ -9,7 +10,6 @@
from typing import Type, TypeVar, Union
from framework.config import Architecture, NodeConfiguration, NodeInfo
-from framework.logger import DTSLOG
from framework.remote_session.remote import InteractiveShell
from framework.settings import SETTINGS
from framework.testbed_model import LogicalCore
@@ -36,7 +36,7 @@ class OSSession(ABC):
_config: NodeConfiguration
name: str
- _logger: DTSLOG
+ _logger: logging.Logger
remote_session: RemoteSession
interactive_session: InteractiveRemoteSession
@@ -44,7 +44,7 @@ def __init__(
self,
node_config: NodeConfiguration,
name: str,
- logger: DTSLOG,
+ logger: logging.Logger,
):
self._config = node_config
self.name = name
@@ -4,8 +4,9 @@
# pylama:ignore=W0611
+import logging
+
from framework.config import NodeConfiguration
-from framework.logger import DTSLOG
from .interactive_remote_session import InteractiveRemoteSession
from .interactive_shell import InteractiveShell
@@ -16,12 +17,12 @@
def create_remote_session(
- node_config: NodeConfiguration, name: str, logger: DTSLOG
+ node_config: NodeConfiguration, name: str, logger: logging.Logger
) -> RemoteSession:
return SSHSession(node_config, name, logger)
def create_interactive_session(
- node_config: NodeConfiguration, logger: DTSLOG
+ node_config: NodeConfiguration, logger: logging.Logger
) -> InteractiveRemoteSession:
return InteractiveRemoteSession(node_config, logger)
@@ -2,7 +2,7 @@
# Copyright(c) 2023 University of New Hampshire
"""Handler for an SSH session dedicated to interactive shells."""
-
+import logging
import socket
import traceback
@@ -16,7 +16,6 @@
from framework.config import NodeConfiguration
from framework.exception import SSHConnectionError
-from framework.logger import DTSLOG
class InteractiveRemoteSession:
@@ -54,11 +53,11 @@ class InteractiveRemoteSession:
username: str
password: str
session: SSHClient
- _logger: DTSLOG
+ _logger: logging.Logger
_node_config: NodeConfiguration
_transport: Transport | None
- def __init__(self, node_config: NodeConfiguration, _logger: DTSLOG) -> None:
+ def __init__(self, node_config: NodeConfiguration, _logger: logging.Logger) -> None:
self._node_config = node_config
self._logger = _logger
self.hostname = node_config.hostname
@@ -11,14 +11,13 @@
elevated privileges to start it is expected that the method for gaining those
privileges is provided when initializing the class.
"""
-
+import logging
from abc import ABC
from pathlib import PurePath
from typing import Callable
from paramiko import Channel, SSHClient, channel # type: ignore[import]
-from framework.logger import DTSLOG
from framework.settings import SETTINGS
@@ -58,7 +57,7 @@ class InteractiveShell(ABC):
_stdin: channel.ChannelStdinFile
_stdout: channel.ChannelFile
_ssh_channel: Channel
- _logger: DTSLOG
+ _logger: logging.Logger
_timeout: float
_app_args: str
_default_prompt: str = ""
@@ -69,7 +68,7 @@ class InteractiveShell(ABC):
def __init__(
self,
interactive_session: SSHClient,
- logger: DTSLOG,
+ logger: logging.Logger,
get_privileged_command: Callable[[str], str] | None,
app_args: str = "",
timeout: float = SETTINGS.timeout,
@@ -2,14 +2,13 @@
# Copyright(c) 2010-2014 Intel Corporation
# Copyright(c) 2022-2023 PANTHEON.tech s.r.o.
# Copyright(c) 2022-2023 University of New Hampshire
-
import dataclasses
+import logging
from abc import ABC, abstractmethod
from pathlib import PurePath
from framework.config import NodeConfiguration
from framework.exception import RemoteCommandExecutionError
-from framework.logger import DTSLOG
from framework.settings import SETTINGS
@@ -50,14 +49,14 @@ class RemoteSession(ABC):
username: str
password: str
history: list[CommandResult]
- _logger: DTSLOG
+ _logger: logging.Logger
_node_config: NodeConfiguration
def __init__(
self,
node_config: NodeConfiguration,
session_name: str,
- logger: DTSLOG,
+ logger: logging.Logger,
):
self._node_config = node_config
@@ -120,7 +119,6 @@ def close(self, force: bool = False) -> None:
"""
Close the remote session and free all used resources.
"""
- self._logger.logger_exit()
self._close(force)
@abstractmethod
@@ -1,6 +1,6 @@
# SPDX-License-Identifier: BSD-3-Clause
# Copyright(c) 2023 PANTHEON.tech s.r.o.
-
+import logging
import socket
import traceback
from pathlib import PurePath
@@ -20,7 +20,6 @@
from framework.config import NodeConfiguration
from framework.exception import SSHConnectionError, SSHSessionDeadError, SSHTimeoutError
-from framework.logger import DTSLOG
from .remote_session import CommandResult, RemoteSession
@@ -49,7 +48,7 @@ def __init__(
self,
node_config: NodeConfiguration,
session_name: str,
- logger: DTSLOG,
+ logger: logging.Logger,
):
super(SSHSession, self).__init__(node_config, session_name, logger)
@@ -11,6 +11,7 @@
from copy import deepcopy
from dataclasses import dataclass
from types import MethodType, ModuleType
+from typing import cast
from .config import (
BuildTargetConfiguration,
@@ -24,7 +25,7 @@
SSHTimeoutError,
TestCaseVerifyError,
)
-from .logger import DTSLOG, getLogger
+from .logger import DTSLog, DtsStage
from .settings import SETTINGS
from .test_result import (
BuildTargetResult,
@@ -68,12 +69,12 @@ def processed_config(self) -> ExecutionConfiguration:
class DTSRunner:
- _logger: DTSLOG
+ _logger: DTSLog
_result: DTSResult
_executions: list[Execution]
def __init__(self, configuration: Configuration):
- self._logger = getLogger("DTSRunner")
+ self._logger = cast(DTSLog, logging.getLogger("DTSRunner"))
self._result = DTSResult(configuration, self._logger)
self._executions = create_executions(configuration.executions)
@@ -146,6 +147,7 @@ def _run_execution(
Run the given execution. This involves running the execution setup as well as
running all build targets in the given execution.
"""
+ self._logger.set_stage(DtsStage.execution)
self._logger.info(
"Running execution with SUT "
f"'{execution.config.system_under_test_node.name}'."
@@ -175,6 +177,7 @@ def _run_execution(
sut_node.tear_down_execution()
execution_result.update_teardown(Result.PASS)
except Exception as e:
+ self._logger.set_stage(DtsStage.execution)
self._logger.exception("Execution teardown failed.")
execution_result.update_teardown(Result.FAIL, e)
@@ -189,6 +192,7 @@ def _run_build_target(
"""
Run the given build target.
"""
+ self._logger.set_stage(DtsStage.build_target)
self._logger.info(f"Running build target '{build_target.name}'.")
build_target_result = execution_result.add_child_result(build_target)
@@ -209,6 +213,7 @@ def _run_build_target(
sut_node.tear_down_build_target()
build_target_result.update_teardown(Result.PASS)
except Exception as e:
+ self._logger.set_stage(DtsStage.build_target)
self._logger.exception("Build target teardown failed.")
build_target_result.update_teardown(Result.FAIL, e)
@@ -265,6 +270,7 @@ def _run_test_suite(
"""
test_suite = test_suite_setup.test_suite(sut_node, tg_node)
test_suite_name = test_suite_setup.test_suite.__name__
+ self._logger.set_stage(DtsStage.suite, test_suite_name)
test_suite_result = build_target_result.add_child_result(
test_suite_setup.processed_config()
)
@@ -397,6 +403,7 @@ def _exit_dts(self) -> None:
self._result.process()
if self._logger:
+ self._logger.set_stage(DtsStage.post_execution)
self._logger.info("DTS execution has ended.")
logging.shutdown()
@@ -6,6 +6,7 @@
Generic result container and reporters
"""
+import logging
import os.path
from collections.abc import MutableSequence
from enum import Enum, auto
@@ -24,7 +25,6 @@
TestSuiteConfig,
)
from .exception import DTSError, ErrorSeverity
-from .logger import DTSLOG
from .settings import SETTINGS
@@ -153,13 +153,13 @@ class DTSResult(BaseResult):
dpdk_version: str | None
_child_configs: list[ExecutionConfiguration]
- _logger: DTSLOG
+ _logger: logging.Logger
_errors: list[Exception]
_return_code: ErrorSeverity
_stats_result: Union["Statistics", None]
_stats_filename: str
- def __init__(self, configuration: Configuration, logger: DTSLOG):
+ def __init__(self, configuration: Configuration, logger: logging.Logger):
super(DTSResult, self).__init__()
self.dpdk_version = None
self._child_configs = configuration.executions
@@ -6,6 +6,7 @@
Base class for creating DTS test cases.
"""
+import logging
from ipaddress import IPv4Interface, IPv6Interface, ip_interface
from typing import Union
@@ -14,7 +15,6 @@
from scapy.packet import Packet, Padding # type: ignore[import]
from .exception import TestCaseVerifyError
-from .logger import DTSLOG, getLogger
from .testbed_model import SutNode, TGNode
from .testbed_model.hw.port import Port, PortLink
from .utils import get_packet_summaries
@@ -41,7 +41,7 @@ class TestSuite(object):
sut_node: SutNode
tg_node: TGNode
is_blocking = False
- _logger: DTSLOG
+ _logger: logging.Logger
_port_links: list[PortLink]
_sut_port_ingress: Port
_sut_port_egress: Port
@@ -59,7 +59,7 @@ def __init__(
):
self.sut_node = sut_node
self.tg_node = tg_node
- self._logger = getLogger(self.__class__.__name__)
+ self._logger = logging.getLogger(self.__class__.__name__)
self._port_links = []
self._process_links()
self._sut_port_ingress, self._tg_port_egress = (
@@ -6,7 +6,7 @@
"""
A node is a generic host that DTS connects to and manages.
"""
-
+import logging
from abc import ABC
from ipaddress import IPv4Interface, IPv6Interface
from typing import Any, Callable, Type, Union
@@ -16,7 +16,6 @@
ExecutionConfiguration,
NodeConfiguration,
)
-from framework.logger import DTSLOG, getLogger
from framework.remote_session import InteractiveShellType, OSSession, create_session
from framework.settings import SETTINGS
@@ -43,7 +42,7 @@ class Node(ABC):
name: str
lcores: list[LogicalCore]
ports: list[Port]
- _logger: DTSLOG
+ _logger: logging.Logger
_other_sessions: list[OSSession]
_execution_config: ExecutionConfiguration
virtual_devices: list[VirtualDevice]
@@ -51,7 +50,7 @@ class Node(ABC):
def __init__(self, node_config: NodeConfiguration):
self.config = node_config
self.name = node_config.name
- self._logger = getLogger(self.name)
+ self._logger = logging.getLogger(self.name)
self.main_session = create_session(self.config, self.name, self._logger)
self._logger.info(f"Connected to node: {self.name}")
@@ -137,7 +136,7 @@ def create_session(self, name: str) -> OSSession:
connection = create_session(
self.config,
session_name,
- getLogger(session_name, node=self.name),
+ logging.getLogger(session_name),
)
self._other_sessions.append(connection)
return connection
@@ -237,7 +236,6 @@ def close(self) -> None:
self.main_session.close()
for session in self._other_sessions:
session.close()
- self._logger.logger_exit()
@staticmethod
def skip_setup(func: Callable[..., Any]) -> Callable[..., Any]:
@@ -13,6 +13,7 @@
"""
import inspect
+import logging
import marshal
import time
import types
@@ -24,7 +25,6 @@
from scapy.packet import Packet # type: ignore[import]
from framework.config import OS, ScapyTrafficGeneratorConfig
-from framework.logger import DTSLOG, getLogger
from framework.remote_session import PythonShell
from framework.settings import SETTINGS
@@ -190,12 +190,12 @@ class ScapyTrafficGenerator(CapturingTrafficGenerator):
rpc_server_proxy: xmlrpc.client.ServerProxy
_config: ScapyTrafficGeneratorConfig
_tg_node: TGNode
- _logger: DTSLOG
+ _logger: logging.Logger
def __init__(self, tg_node: TGNode, config: ScapyTrafficGeneratorConfig):
self._config = config
self._tg_node = tg_node
- self._logger = getLogger(f"{self._tg_node.name} {self._config.traffic_generator_type}")
+ self._logger = logging.getLogger(f"{self._tg_node.name} {self._config.traffic_generator_type}")
assert (
self._tg_node.config.os == OS.linux
@@ -7,12 +7,11 @@
These traffic generators can't capture received traffic,
only count the number of received packets.
"""
-
+import logging
from abc import ABC, abstractmethod
from scapy.packet import Packet # type: ignore[import]
-from framework.logger import DTSLOG
from framework.utils import get_packet_summaries
from .hw.port import Port
@@ -24,7 +23,7 @@ class TrafficGenerator(ABC):
Defines the few basic methods that each traffic generator must implement.
"""
- _logger: DTSLOG
+ _logger: logging.Logger
def send_packet(self, packet: Packet, port: Port) -> None:
"""Send a packet and block until it is fully sent.
@@ -8,18 +8,18 @@
A test framework for testing DPDK.
"""
-import logging
-
from framework.config import load_config
+from framework.logger import init_logger
from framework.runner import DTSRunner
+from framework.settings import SETTINGS
def main() -> None:
+ init_logger(SETTINGS.verbose, SETTINGS.output_dir)
dts = DTSRunner(configuration=load_config())
dts.run()
# Main program begins here
if __name__ == "__main__":
- logging.raiseExceptions = True
main()