日志输出到控制台:StreamHandler 的使用与颜色技巧

控制台输出,是咱们调试 Python 日志时最直接的反馈方式。StreamHandler 就是干这个的——它把日志消息送到标准输出或标准错误流。我早期写脚本时,经常直接 print 一堆信息,后来发现日志框架提供的灵活度完全不一样。

StreamHandler 基本用法

说白了,你只需要三行就能让日志从控制台蹦出来:

import logging

logger = logging.getLogger('console_demo')
handler = logging.StreamHandler()         # 默认输出到 sys.stderr
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.setLevel(logging.DEBUG)

logger.info('嘿,控制台我能看见你!')

你看,StreamHandler 不指定流时自动走 sys.stderr。想输出到标准输出?给个 sys.stdout 参数就行:

import sys
handler = logging.StreamHandler(sys.stdout)

我用过的坑:有些同学同时加了 FileHandler 和 StreamHandler,结果控制台重复打印——一个 logger 挂了两个输出相同的 handler。我之前有个自动部署脚本,日志重复打印搞得我排查时眼花缭乱。后来养成习惯:每次添加 handler 前先检查 logger.hasHandlers(),或者直接清空再新建。

控制台颜色输出技巧

黑白日志看久了眼睛疼,尤其 ERROR 级别混在 INFO 里根本分不清。我记得有个生产故障排查,几万行日志里找一条红色报错,结果全是灰白色调……那次之后我就把颜色输出作为标配。

实现方式主要有两种:

  • 用第三方库 colorama —— 跨平台,简单粗暴
  • DIY 自定义 Formatter —— 零依赖,但对 ANSI 码不熟的容易踩坑

方式一:colorama + StreamHandler

我的经验是,colorama 在 Windows 上要调用 init(autoreset=True),否则颜色会“泄露”到后续输出。直接上代码:

import logging
import sys
from colorama import init, Fore, Style

init(autoreset=True)  # Windows 下必须

class ColoredFormatter(logging.Formatter):
    """给不同级别上色"""
    COLORS = {
        'DEBUG': Fore.CYAN,
        'INFO': Fore.GREEN,
        'WARNING': Fore.YELLOW,
        'ERROR': Fore.RED,
        'CRITICAL': Fore.MAGENTA + Style.BRIGHT,
    }

    def format(self, record):
        level_color = self.COLORS.get(record.levelname, '')
        # 给整个消息加上颜色前缀
        msg = super().format(record)
        return f"{level_color}{msg}{Style.RESET_ALL}"

handler = logging.StreamHandler(sys.stdout)
formatter = ColoredFormatter('%(asctime)s | %(levelname)-8s | %(message)s')
handler.setFormatter(formatter)

logger = logging.getLogger('color_logger')
logger.addHandler(handler)
logger.setLevel(logging.DEBUG)

logger.debug('这个信息是青色的')
logger.info('正常的绿色信息')
logger.warning('黄色的警告要留意')
logger.error('红色的错误必须处理')
logger.critical('品红色加亮!别忽视啊')
小贴士:生产环境日志建议保留原始无颜色版本(比如文件输出),颜色只用于开发调试。我有个项目搭配了 --no-color 命令行参数来控制是否启用颜色。

方式二:纯 ANSI 转义序列(不依赖第三方)

如果你不想引入 colorama,可以用标准 ANSI 码:

class SimpleColorFormatter(logging.Formatter):
    GREY = '\033[90m'
    BLUE = '\033[94m'
    YELLOW = '\033[93m'
    RED = '\033[91m'
    RESET = '\033[0m'

    FORMATS = {
        logging.DEBUG: BLUE,
        logging.INFO: GREY,
        logging.WARNING: YELLOW,
        logging.ERROR: RED,
    }

    def format(self, record):
        color = self.FORMATS.get(record.levelno, self.RESET)
        record.msg = f"{color}{record.msg}{self.RESET}"
        return super().format(record)

嗯,这里要注意:Windows 旧版控制台(CMD)不支持 ANSI,要么用 colorama 的 init() 去自动转换,要么直接上 Windows Terminal 或 PowerShell。我一般直接用 colorama,省心。

SteamHandler 在日志体系中的位置

下面这张图展示了 Logger、Handler、Formatter 之间的协作关系——核心就是把日志记录“格式化”后送到输出目的地:

Logger → Handler → 控制台输出流程 Logger (getLogger) StreamHandler (sys.stdout/stderr) Formatter 控制台输出 (彩色日志) StreamHandler 只是 Handler 的一种,可以配合自定义 Formatter 实现颜色输出

实战配置建议

场景 推荐做法 为什么不推荐反例
开发测试 StreamHandler + ColoredFormatter + DEBUG 级别 全用 print 替代——失去级别过滤和时间戳
生产环境控制台 StreamHandler + 无颜色 Formatter + INFO 级别 保留颜色可能被日志采集系统乱码吃掉
Windows + 颜色 必须调用 colorama.init() 直接 ANSI 码不可靠
日志去重 检查 logger.handlers 避免重复 多次 addHandler 造成双倍输出

几个你可能会踩的坑

  • 乱码? 我曾经因为 StreamHandler 默认编码是 sys.stderr.encoding,在中文 Windows 上输出乱码。加一行 handler.terminator = '\n' 或指定 stream=open(sys.stdout.fileno(), 'w', encoding='utf-8') 解决。
  • 颜色字符串跑到文件里? 如果同一个 logger 同时绑定了 FileHandler 和 StreamHandler,且都用了颜色 Formatter,日志文件里会有一堆 \033[93m 这种垃圾。我的经验是:颜色只给控制台专用 handler 用,文件 handler 用标准 Formatter。
  • 为什么设置 logger.setLevel() 后还有低级别日志输出? 注意:logger 的级别和 handler 的级别是“与”的关系。比如 logger 设了 INFO,handler 设了 DEBUG,最终还是 INFO 以上才通过。我记得有个同事调了半天,最后发现 handler 没设级别,流过了所有级别。
我的个人习惯:在项目初期就封装一个 setup_logging(use_color=True) 函数,根据参数决定是否启用颜色。这样团队所有成员都在同一个配置下工作,避免“你机器上有颜色,我机器上没有”的尴尬。

总结

StreamHandler 是最常用的输出方式,搭配颜色能让调试效率翻倍。核心要点就三个:

  1. 理解 logger/handler/formatter 的分工
  2. 颜色要区分场景(开发 vs 生产)
  3. 谨慎处理 handler 重复和编码问题

你下次写日志脚本时,试试把颜色加上去——保证你会觉得比纯黑白舒服不少。