如何避免使用日志记录将转义字符输出到日志文件?
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"),
]
)
我正在尝试为 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"),
]
)