[RFC,v1,5/5] dts: refactor logging configuration

Message ID 20231220103331.60888-6-juraj.linkes@pantheon.tech (mailing list archive)
State Superseded, archived
Delegated to: Thomas Monjalon
Headers
Series test case blocking and logging |

Checks

Context Check Description
ci/checkpatch success coding style OK
ci/loongarch-compilation success Compilation OK
ci/loongarch-unit-testing success Unit Testing PASS
ci/Intel-compilation success Compilation OK
ci/intel-Testing success Testing PASS
ci/intel-Functional success Functional PASS

Commit Message

Juraj Linkeš Dec. 20, 2023, 10:33 a.m. UTC
  Refactor logging for improved configuration and flexibility,
investigating unclear arguments and exploring alternatives
for logging test suites into separate files. In addition,
efforts were made to ensure that the modules remained
independent from the logger module, enabling potential use
by other consumers.

Signed-off-by: Juraj Linkeš <juraj.linkes@pantheon.tech>
---
 dts/framework/logger.py                       | 162 ++++++++----------
 dts/framework/remote_session/__init__.py      |   4 +-
 dts/framework/remote_session/os_session.py    |   6 +-
 .../remote_session/remote/__init__.py         |   7 +-
 .../remote/interactive_remote_session.py      |   7 +-
 .../remote/interactive_shell.py               |   7 +-
 .../remote_session/remote/remote_session.py   |   8 +-
 .../remote_session/remote/ssh_session.py      |   5 +-
 dts/framework/runner.py                       |  13 +-
 dts/framework/test_result.py                  |   6 +-
 dts/framework/test_suite.py                   |   6 +-
 dts/framework/testbed_model/node.py           |  10 +-
 dts/framework/testbed_model/scapy.py          |   6 +-
 .../testbed_model/traffic_generator.py        |   5 +-
 dts/main.py                                   |   6 +-
 15 files changed, 124 insertions(+), 134 deletions(-)
  

Patch

diff --git a/dts/framework/logger.py b/dts/framework/logger.py
index bb2991e994..43c49c2d03 100644
--- a/dts/framework/logger.py
+++ b/dts/framework/logger.py
@@ -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 = []
diff --git a/dts/framework/remote_session/__init__.py b/dts/framework/remote_session/__init__.py
index 6124417bd7..a4ec2f40ae 100644
--- a/dts/framework/remote_session/__init__.py
+++ b/dts/framework/remote_session/__init__.py
@@ -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)
diff --git a/dts/framework/remote_session/os_session.py b/dts/framework/remote_session/os_session.py
index 8a709eac1c..2524a4e669 100644
--- a/dts/framework/remote_session/os_session.py
+++ b/dts/framework/remote_session/os_session.py
@@ -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
diff --git a/dts/framework/remote_session/remote/__init__.py b/dts/framework/remote_session/remote/__init__.py
index 06403691a5..4a22155153 100644
--- a/dts/framework/remote_session/remote/__init__.py
+++ b/dts/framework/remote_session/remote/__init__.py
@@ -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)
diff --git a/dts/framework/remote_session/remote/interactive_remote_session.py b/dts/framework/remote_session/remote/interactive_remote_session.py
index 098ded1bb0..bf0996a747 100644
--- a/dts/framework/remote_session/remote/interactive_remote_session.py
+++ b/dts/framework/remote_session/remote/interactive_remote_session.py
@@ -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
diff --git a/dts/framework/remote_session/remote/interactive_shell.py b/dts/framework/remote_session/remote/interactive_shell.py
index 4db19fb9b3..b6074838c2 100644
--- a/dts/framework/remote_session/remote/interactive_shell.py
+++ b/dts/framework/remote_session/remote/interactive_shell.py
@@ -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,
diff --git a/dts/framework/remote_session/remote/remote_session.py b/dts/framework/remote_session/remote/remote_session.py
index 719f7d1ef7..da78e5c921 100644
--- a/dts/framework/remote_session/remote/remote_session.py
+++ b/dts/framework/remote_session/remote/remote_session.py
@@ -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
diff --git a/dts/framework/remote_session/remote/ssh_session.py b/dts/framework/remote_session/remote/ssh_session.py
index 1a7ee649ab..42441c4587 100644
--- a/dts/framework/remote_session/remote/ssh_session.py
+++ b/dts/framework/remote_session/remote/ssh_session.py
@@ -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)
 
diff --git a/dts/framework/runner.py b/dts/framework/runner.py
index 28570d4a1c..5c06e4ca1a 100644
--- a/dts/framework/runner.py
+++ b/dts/framework/runner.py
@@ -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()
diff --git a/dts/framework/test_result.py b/dts/framework/test_result.py
index dba2c55d36..221e75205e 100644
--- a/dts/framework/test_result.py
+++ b/dts/framework/test_result.py
@@ -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
diff --git a/dts/framework/test_suite.py b/dts/framework/test_suite.py
index e73206993d..9c9a8c1e08 100644
--- a/dts/framework/test_suite.py
+++ b/dts/framework/test_suite.py
@@ -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 = (
diff --git a/dts/framework/testbed_model/node.py b/dts/framework/testbed_model/node.py
index ef700d8114..a98c58df4f 100644
--- a/dts/framework/testbed_model/node.py
+++ b/dts/framework/testbed_model/node.py
@@ -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]:
diff --git a/dts/framework/testbed_model/scapy.py b/dts/framework/testbed_model/scapy.py
index 9083e92b3d..61058cd38a 100644
--- a/dts/framework/testbed_model/scapy.py
+++ b/dts/framework/testbed_model/scapy.py
@@ -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
diff --git a/dts/framework/testbed_model/traffic_generator.py b/dts/framework/testbed_model/traffic_generator.py
index 28c35d3ce4..6b0838958a 100644
--- a/dts/framework/testbed_model/traffic_generator.py
+++ b/dts/framework/testbed_model/traffic_generator.py
@@ -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.
diff --git a/dts/main.py b/dts/main.py
index 879ce5cb89..f2828148f0 100755
--- a/dts/main.py
+++ b/dts/main.py
@@ -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()