Python技术迷

为什么pycharm输出的日志全部是红色!

日志一片红,不一定是报错。

我第一次碰到这个事,也以为程序炸了。PyCharm 控制台里刷出来的日志,info 是红的,warning 是红的,连我自己 print() 出来的东西都红了,看着跟线上雪崩一样,手都差点伸去改代码了。

结果最后发现,业务没毛病,日志框架也没毛病,红的是 stderr。

Python 里最常见就两条输出通道:

import sys

print("正常输出", file=sys.stdout)
print("看起来像报错,其实只是走了 stderr", file=sys.stderr)

在终端里,这俩很多时候看起来差不多。

但在 PyCharm 里,默认会把 stderr 渲染成红色,所以你会产生一种错觉:怎么我程序全在报错?

这事最容易出现在下面几种情况。

1.你自己把日志打到 stderr 了

很多人配 logging,随手就是一个 StreamHandler(),然后没细看它默认绑的是谁。

像这种:

import logging

logger = logging.getLogger("demo")
logger.setLevel(logging.DEBUG)

handler = logging.StreamHandler()   # 默认就是 sys.stderr
formatter = logging.Formatter("%(levelname)s - %(message)s")
handler.setFormatter(formatter)

logger.addHandler(handler)

logger.info("这条在 PyCharm 里大概率也是红的")

代码没问题。

红,是因为 StreamHandler() 默认输出到 sys.stderr,不是 sys.stdout。

改法也很直接:

import sys
import logging

logger = logging.getLogger("demo")
logger.setLevel(logging.DEBUG)

handler = logging.StreamHandler(sys.stdout)
formatter = logging.Formatter("%(asctime)s %(levelname)s %(message)s")
handler.setFormatter(formatter)

logger.handlers.clear()
logger.addHandler(handler)

logger.info("现在一般就不是红的了")

这类问题我一般先不看业务代码,先看日志初始化。 尤其是项目里封了一层 get_logger()、init_log() 这种工具方法的时候,八成就藏在那。

  1. 你没主动配 logging,框架替你打到 stderr 了

有些脚本你自己没配日志,但用了三方库、命令行工具、爬虫框架、测试框架,它们本身就喜欢往 stderr 打。

比如这种很常见:

import logging

logging.basicConfig(
    level=logging.INFO,
    format="%(levelname)s %(message)s"
)

logging.info("你以为是标准输出,其实默认还是 stderr")

basicConfig() 默认也是走 stderr。

想让它别红,直接指定 stream:

import sys
import logging

logging.basicConfig(
    level=logging.INFO,
    format="%(levelname)s %(message)s",
    stream=sys.stdout
)

logging.info("这次走 stdout")

这个地方挺坑的。 很多人以为自己没手动写 stderr,那肯定不是输出流的问题。其实 logging 默认就已经帮你选了。

  1. 不是日志红,是进程报错信息混进来了

还有一种更像真的“全红”。

你程序里某个子进程、某个外部命令、某段异常堆栈正好在刷 stderr,PyCharm 就全给你标红了,看上去像整段日志都挂了。

比如:

import subprocess

result = subprocess.run(
    ["python", "-c", "import sys; print('hello'); print('warn', file=sys.stderr)"],
    capture_output=True,
    text=True
)

print("stdout:", result.stdout)
print("stderr:", result.stderr)

这时候你如果直接把 stderr 内容又 print 回控制台,颜色还是会乱。

我平时排这种,会先把两个流拆开看:

import subprocess

p = subprocess.Popen(
    ["python", "task.py"],
    stdout=subprocess.PIPE,
    stderr=subprocess.PIPE,
    text=True
)

out, err = p.communicate()

if out:
    print("STDOUT >>>")
    print(out)

if err:
    print("STDERR >>>")
    print(err)

先分清楚到底是谁在红,不然后面越看越像玄学。

  1. PyCharm 运行配置里,把 stderr 单独高亮了

这个就不是 Python 的锅了,是 IDE 的展示习惯。

PyCharm 对标准错误流本来就会做醒目标记。 所以只要你的内容走了 stderr,哪怕只是普通提示,它也给你上红色。

这时候代码层面的修正还是那一套:

  • print() 默认没问题,它走的是 stdout
  • logging 默认常常有问题,它走的是 stderr
  • 三方库和外部命令,很多也喜欢走 stderr

所以别一上来怀疑 PyCharm 抽风,先确认输出流。

我平时怎么快速定位

我一般会在入口脚本先插两行,先把现象钉死:

import sys
print("stdout check", file=sys.stdout)
print("stderr check", file=sys.stderr)

然后再看你那条“发红的日志”到底是跟哪边一起出来的。

如果还嫌不够直接,就把 handler 信息打出来:

import logging

logger = logging.getLogger()
for h in logger.handlers:
    print(type(h), getattr(h, "stream", None))

现场一看,基本就知道是不是 StreamHandler(stderr) 了。

我自己写脚本,尤其是导数、清洗、巡检这类小工具,通常会把控制台日志和文件日志分开,不让控制台颜色影响判断。

import sys
import logging

logger = logging.getLogger("task")
logger.setLevel(logging.INFO)
logger.handlers.clear()

console = logging.StreamHandler(sys.stdout)
console.setLevel(logging.INFO)
console.setFormatter(logging.Formatter(
"%(asctime)s | %(levelname)s | %(message)s"
))

file_handler = logging.FileHandler("task.log", encoding="utf-8")
file_handler.setLevel(logging.INFO)
file_handler.setFormatter(logging.Formatter(
"%(asctime)s | %(name)s | %(levelname)s | %(message)s"
))

logger.addHandler(console)
logger.addHandler(file_handler)

logger.info("开始处理")
logger.warning("这条是 warning,但不该因为走 stderr 被染红")
logger.error("真报错了就让它按级别报错")

这样至少看控制台的时候,颜色和严重程度更接近,不会被 IDE 的 stderr 渲染带偏。

PyCharm 里日志全红,最常见的原因不是程序全报错了,是你的日志走了 stderr。

重点看这两个地方就够了:

logging.StreamHandler()         # 默认 stderr
logging.basicConfig(...)        # 默认 stderr

把它们改成 sys.stdout,十有八九就清净了:

logging.StreamHandler(sys.stdout)

这事不算大坑,但很烦。 因为它不影响程序跑,就是特别影响你判断现场。你盯着一屏红字看半天,最后发现只是输出流选错了,这种感觉,挺像白查半小时日志。