如何避免使用日志记录将转义字符输出到日志文件?

How to avoid escape characters outputted to log file with logging?

我正在尝试为 INFO、WARNING 等级别名称着色,但问题是当消息被记录到日志文件时 - 有奇怪的字符。但是,它可以完美地打印到控制台。

代码:

import logging
import sys

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(message)s",
    handlers=[
        logging.FileHandler("log.txt"),
        logging.StreamHandler(sys.stdout)
    ]
)

 logging.addLevelName(logging.INFO, "3[1;31m%s3[1;0m" % logging.getLevelName(logging.INFO))

logging.info("This is just an information for you")

这会用颜色打印级别名称“INFO”,但输出是:

2022-04-05 02:44:52,741 [[1;31mINFO[1;0m] This is just an information for you

如何解决这个问题?

编辑:

logging.addLevelName(logging.INFO, "3[1;94m%s3[1;0m" % logging.getLevelName(logging.INFO))
logging.addLevelName(logging.WARNING, "3[1;93m%s3[1;0m" % logging.getLevelName(logging.WARNING))
logging.addLevelName(logging.ERROR, "3[1;91m%s3[1;0m" % logging.getLevelName(logging.ERROR))

原始答案(不是 fully-functional;查看已接受的答案):

这些字符出现在 gui 文本编辑器中(但不出现在命令行中——取决于所使用的 shell),因为它们不理解 ASCII 转义序列——除非它们被专门配置为所以。

您可以添加代码以在将这些字符写入 log 文件时删除这些字符:

import logging
import logging.config
import re
import sys

from argparse import Namespace
from pathlib import Path
from typing import Optional

class NoColourFormatter(logging.Formatter):

    """Log formatter that strips terminal colour escape codes from the log message."""

    ANSI_RE = re.compile(r"\x1b\[[0-9;]*m")

    def format(self, record):
        """Return logger message with terminal escapes removed."""
        return "[%s] [%s]: %s" % (
            record.levelname,
            record.name,
            re.sub(self.ANSI_RE, "", record.msg % record.args),
        )

并像这样使用它:

# same file as above

def config_logger(args: Optional[Namespace] = None) -> None:

    logger = logging.getLogger(__package__)

    # other code

    logformatter = NoColourFormatter()
    loghandler = logging.FileHandler(args.logfile, mode="w", encoding="utf8")

    loghandler.setFormatter(logformatter)
    logger.addHandler(loghandler)

更新为每个级别分配了单独的颜色。要更改颜色,只需将 set_colours() 中的相应数字替换为所需的值即可。

这里有一些东西可以直接用来生成你想要的日志:

import logging
import re
import time
import sys

def set_colour(level):
    """
    Sets colour of text for the level name in
    logging statements using a dispatcher.
    """
    escaped = "[3[1;%sm%s3[1;0m]"
    return {
        'INFO': lambda: logging.addLevelName(logging.INFO, escaped % ('94', level)),
        'WARNING': lambda: logging.addLevelName(logging.ERROR, escaped % ('93', level)),
        'ERROR': lambda: logging.addLevelName(logging.WARNING, escaped % ('91', level))
    }.get(level, lambda: None)()


class NoColorFormatter(logging.Formatter):
    """
    Log formatter that strips terminal colour
    escape codes from the log message.
    """

    # Regex for ANSI colour codes
    ANSI_RE = re.compile(r"\x1b\[[0-9;]*m")

    def format(self, record):
        """Return logger message with terminal escapes removed."""
        return "%s %s %s" % (
            time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()),
            re.sub(self.ANSI_RE, "", record.levelname),
            record.msg,
        )

# Create logger
logger = logging.getLogger(__package__)

# Create formatters
logformatter = NoColorFormatter()
colorformatter = logging.Formatter("%(asctime)s %(levelname)s %(message)s")

# Set logging colours
for level in 'INFO', 'ERROR', 'WARNING':
    set_colour(level)

# Set logging level
logger.setLevel(logging.INFO)

# Set log handlers
loghandler = logging.FileHandler("log.txt", mode="w", encoding="utf8")
streamhandler = logging.StreamHandler(sys.stdout)

# Set log formatters
loghandler.setFormatter(logformatter)
streamhandler.setFormatter(colorformatter)

# Attach log handlers to logger
logger.addHandler(loghandler)
logger.addHandler(streamhandler)

# Example logging statements
logging.info("This is just an information for you")
logging.warning("This is just an information for you")
logging.error("This is just an information for you")

输出到stdout时有颜色,但在日志文件中没有。此输出的日志语句与原始代码中的日志语句之间的唯一区别是 %(asctime)s 似乎在 logging.Formatter 对象之外不起作用,因此我使用了 time反而;输出是一样的,除了没有任何毫秒。

我不会在全局范围内覆盖关卡名称,而是在特定的格式化程序中应用着色,然后在需要着色的处理程序上设置该格式化程序。

class ColouredFormatter(logging.Formatter):
    _lookup = {
        logging.INFO: "3[1;94m%s3[1;0m",
        logging.WARNING: "3[1;93m%s3[1;0m",
        logging.ERROR: "3[1;91m%s3[1;0m",
    }

    def format(self, record):
        result = super().format(record)
        # Operate on the result rather than record attributes to
        # avoid corrupting output for subsequent loggers.
        formatted_levelname = self._lookup[record.levelno] % record.levelname
        result = re.sub(rf'\[({record.levelname})\]', formatted_levelname, result, count=1)
        return result


streamhandler = logging.StreamHandler(sys.stdout)
streamhandler.setFormatter(ColouredFormatter(fmt="%(asctime)s [%(levelname)s] %(message)s"))

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(message)s",
    handlers=[
        streamhandler,
        logging.FileHandler("so71744086log.txt"),
    ]
)