如何修改pytest输出中文件名与行号的显示颜色?
实现pytest日志中文件名和行号的颜色高亮
下面分三部分修改现有代码,覆盖自定义日志工具和pytest自带日志的颜色高亮需求:
一、修改自定义log_helper模块的格式化器
在RelativePathFormatter中添加颜色逻辑,给路径和行号加上高亮:
import logging import os import pprint # 新增ANSI颜色常量 CYAN = '\033[96m' RESET = '\033[0m' # Initialize the logger at the module level, so it's only created once logger = logging.getLogger('debug_logger') # Set up the logger only if it hasn't been set up already if not logger.handlers: log_level = os.environ.get('LOG_LEVEL', 'WARNING').upper() logger.setLevel(getattr(logging, log_level, logging.WARNING)) formatter = logging.Formatter('%(levelname)s: %(message)s') handler = logging.StreamHandler() handler.setFormatter(formatter) logger.addHandler(handler) class RelativePathFormatter(logging.Formatter): def __init__(self, *args, base_path=None, **kwargs): super().__init__(*args, **kwargs) self.base_path = base_path if base_path else os.path.expanduser('~/python') def format(self, record): # 先处理路径相对化 if record.pathname.startswith(self.base_path): record.pathname = os.path.relpath(record.pathname, self.base_path) # 给路径和行号添加颜色 colored_path = f"{CYAN}{record.pathname}:{record.lineno}{RESET}" # 临时替换格式字符串中的路径行号部分 original_format = self._fmt self._fmt = original_format.replace("%(pathname)s:%(lineno)d", colored_path) # 执行格式化 result = super().format(record) # 恢复原格式避免影响后续日志 self._fmt = original_format return result # Setup logger using the custom formatter def get_logger(name='debug_logger'): logger = logging.getLogger(name) # Remove existing handlers to avoid conflicts if logger.hasHandlers(): logger.handlers.clear() log_level = os.environ.get('LOG_LEVEL', 'DEBUG').upper() logger.setLevel(getattr(logging, log_level, logging.DEBUG)) # Apply custom formatter formatter = RelativePathFormatter( '%(levelname)-8s %(pathname)s:%(lineno)d - %(message)s', base_path=os.path.expanduser('~/python') ) handler = logging.StreamHandler() handler.setFormatter(formatter) logger.addHandler(handler) return logger # Debug print function that respects log level and logs caller info def d(data, depth=None): """ Debug print function that logs data in DEBUG mode with optional depth control for complex data structures. Shows the correct file and line number of the caller, not log_helper.py. :param data: The data to log (complex or simple). :param depth: Optional depth to limit the structure's representation, defaults to None. """ logger = get_logger() # Get the global logger # Bail out immediately if the logger is not in DEBUG mode if not logger.isEnabledFor(logging.DEBUG): return # Format the data for debug output formatted_data = pprint.pformat(data, depth=depth) # Check if the formatted data has multiple lines if "\n" in formatted_data: # Add a newline before the data if it spans multiple lines formatted_data = "\n" + formatted_data # Adjust the stack level so the logger reports the caller's file and line number logger.debug(formatted_data, stacklevel=2)
二、修改conftest.py配置pytest自带日志
在pytest_configure钩子中直接修改日志格式,给路径行号添加颜色:
# File: ~/python/tests/conftest.py import time from pathlib import Path from collections import defaultdict import pytest import logging # ANSI color codes CYAN = '\033[96m' YELLOW = '\033[93m' GREEN = '\033[92m' RED = '\033[91m' RESET = '\033[0m' test_results = {} test_structure = defaultdict(lambda: defaultdict(list)) start_time = None @pytest.hookimpl(trylast=True) def pytest_configure(config): logging_plugin = config.pluginmanager.get_plugin("logging-plugin") # 给log_cli格式中的路径和行号添加颜色 config.option.log_cli_format = f'%(levelname)-8s {CYAN}%(pathname)s:%(lineno)d{RESET} - %(message)s' # 保留原有日志级别颜色设置 logging_plugin.log_cli_handler.formatter.add_color_level(logging.DEBUG, "blue") logging_plugin.log_cli_handler.formatter.add_color_level(logging.INFO, "cyan") def pytest_sessionstart(session): global start_time start_time = time.time() def pytest_collection_modifyitems(session, config, items): # Sort items based on their location in the file items.sort(key=lambda x: x.location[1]) @pytest.hookimpl(tryfirst=True, hookwrapper=True) def pytest_runtest_makereport(item, call): outcome = yield result = outcome.get_result() if result.when == 'call': test_results[item.nodeid] = result.outcome def pytest_sessionfinish(session, exitstatus): global start_time duration = time.time() - start_time verbosity = session.config.option.verbose if verbosity > 1: print("\n") for dir_path in sorted(test_structure.keys()): print(f"\n{CYAN}{dir_path}{RESET}") for file_name in sorted(test_structure[dir_path].keys()): print(f" {YELLOW}{file_name}{RESET}") for item in test_structure[dir_path][file_name]: result = test_results.get(item.nodeid, "UNKNOWN") if result == "passed": color = GREEN elif result == "failed": color = RED else: color = YELLOW indent = " " if "::" in item.name: class_name, test_name = item.name.split("::") print(f" {class_name}") indent = " " else: test_name = item.name print(f"{indent}{test_name} {color}{result.upper()}{RESET}") # Print summary passed = sum(1 for result in test_results.values() if result == "passed") failed = sum(1 for result in test_results.values() if result == "failed") total = len(test_results) print(f"\n{GREEN if failed == 0 else RED}=== {passed} passed, {failed} failed, {total} total in {duration:.2f}s ==={RESET}\n") def pytest_terminal_summary(terminalreporter, exitstatus, config): # Completely override the default summary pass
三、修改pytest.ini(可选)
如果不想在conftest中硬编码格式,可直接修改pytest.ini的日志格式:
[pytest] # Include numbered files as test files python_files = test_*.py *_test.py [0-9]*-*.py 00-*_test log_cli_format = %(levelname)-8s \033[96m%(pathname)s:%(lineno)d\033[0m - %(message)s
注意事项
- 确保终端支持ANSI颜色代码(现代终端基本都支持,Windows需开启终端颜色功能)
- 若需关闭颜色,可添加环境变量判断,比如检测到
NO_COLOR存在时跳过颜色代码添加
内容的提问来源于stack exchange,提问作者StevieD
相关产品推荐
相关产品推荐

