1. vLLM日志体系概述:日志级别、日志格式、日志输出目标、日志轮转策略

大家好,我是你们的老朋友。今天咱们来聊聊vLLM的日志体系。说实话,日志这东西,平时你可能觉得它不起眼,但一旦线上出问题,它就是你的救命稻草。我在维护生产环境时,有无数次都是靠日志定位到问题的根源。所以,这一章咱们把vLLM的日志体系彻底讲透。

1.1 日志级别:从DEBUG到CRITICAL

vLLM的日志级别,说白了就是Python标准库logging的那一套。它定义了5个级别,从低到高分别是:

级别 数值 说明 我常用的场景
DEBUG 10 最详细的调试信息 开发阶段排查问题,生产环境慎用
INFO 20 常规运行信息 记录请求量、模型加载等关键事件
WARNING 30 潜在问题警告 比如显存接近上限、配置不合理
ERROR 40 错误事件,不影响运行 请求超时、部分推理失败
CRITICAL 50 严重错误,可能导致服务中断 OOM、模型加载失败、核心进程崩溃

我个人习惯在生产环境把日志级别设为INFO。为什么?因为DEBUG日志量太大了,一天能刷几个GB,你想想看,磁盘撑不住。但如果你在排查某个诡异的问题,临时切到DEBUG级别,往往能发现一些蛛丝马迹。

小技巧: 我曾经遇到过一个显存泄漏的问题,INFO日志完全看不出异常。后来我把日志级别降到DEBUG,发现每次请求后都有几MB的tensor没有被释放。嗯,这就是DEBUG日志的价值。

1.2 日志格式:每一行都藏着信息

vLLM默认的日志格式长这样:

2024-01-15 10:23:45,678 - vllm.engine - INFO - Model loaded successfully in 12.34s

这个格式其实包含了几个关键字段:

  • 时间戳:精确到毫秒,方便做时序分析
  • 模块名:比如vllm.engine、vllm.scheduler,告诉你日志来自哪个组件
  • 日志级别:一目了然
  • 消息内容:具体的事件描述

但说实话,这个默认格式在生产环境还不够用。我建议你加上进程ID和请求ID。为什么?因为vLLM是多进程架构,没有进程ID,你根本分不清日志是哪个worker打印的。请求ID更是排查单次请求问题的关键。

你可以通过环境变量自定义格式:

export VLLM_CONFIGURE_LOGGING=1
export VLLM_LOGGING_FORMAT="%(asctime)s - %(process)d - %(name)s - %(levelname)s - %(message)s"
重点: 我个人强烈建议在日志格式中加入%(process)d。有一次线上告警说某个请求超时了,我翻日志发现所有worker的日志混在一起,根本没法定位。加上进程ID后,一秒就找到了是哪个worker卡住了。

1.3 日志输出目标:文件、控制台还是集中式系统?

vLLM默认把日志输出到标准输出(stdout)。这在开发环境没问题,但生产环境你肯定不希望日志只打在屏幕上。你需要考虑三个输出目标:

  1. 文件:最基础的方式,持久化保存
  2. 控制台:实时查看,适合调试
  3. 集中式日志系统:比如ELK、Loki,适合大规模集群

vLLM本身不直接支持输出到文件,但你可以通过Python的logging配置来实现。我一般会在启动脚本里这样配置:

import logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(process)d - %(name)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler('/var/log/vllm/vllm.log'),
        logging.StreamHandler()
    ]
)

这样日志会同时写入文件和打印到控制台。你想想看,调试时看控制台,事后排查翻文件,两不耽误。

注意: 我曾经犯过一个错误,把日志直接输出到控制台,然后用重定向写到文件。结果进程重启时,之前的日志全丢了。所以,还是老老实实用FileHandler吧。

1.4 日志轮转策略:别让日志撑爆磁盘

日志轮转,说白了就是当日志文件达到一定大小或时间后,自动切割、压缩、清理。vLLM本身没有内置轮转机制,但你可以借助Linux的logrotate或者Python的RotatingFileHandler来实现。

我推荐用Python的RotatingFileHandler,配置简单,效果也不错:

from logging.handlers import RotatingFileHandler

handler = RotatingFileHandler(
    '/var/log/vllm/vllm.log',
    maxBytes=100*1024*1024,  # 每个文件100MB
    backupCount=5            # 保留5个备份
)

这样配置后,当日志文件超过100MB时,会自动重命名为vllm.log.1,然后新建一个vllm.log继续写。最多保留5个备份文件,也就是最多500MB的日志。

如果你用的是容器化部署,比如Docker,那日志轮转就交给Docker的日志驱动来处理。我一般用json-file驱动,配合max-size和max-file参数:

docker run \
  --log-driver json-file \
  --log-opt max-size=100m \
  --log-opt max-file=5 \
  vllm-image:latest
避坑指南: 我曾经遇到过一个问题,日志轮转配置好了,但磁盘还是被撑爆了。排查后发现是vLLM的某个模块在疯狂打印DEBUG日志,而我的轮转策略只针对INFO级别。所以,日志级别和轮转策略要配合使用,别让低级别日志钻了空子。

1.5 知识体系总览

为了让你更直观地理解vLLM日志体系的整体结构,我画了一张图:

vLLM日志体系结构 日志级别 • DEBUG (10) • INFO (20) • WARNING (30) • ERROR (40) • CRITICAL (50) 生产环境建议:INFO 排查问题用:DEBUG 日志格式 时间戳 模块名 日志级别 消息内容 建议添加: 进程ID 请求ID 输出目标 • 文件 • 控制台 • 集中式系统 推荐:文件+控制台 大规模集群:ELK/Loki 轮转策略 • 按大小轮转 • 按时间轮转 • 保留备份数 推荐:100MB/文件 保留5个备份 注意:容器化用 Docker日志驱动

这张图把vLLM日志体系的四个核心维度串起来了。你从日志级别开始,决定要记录什么;然后定义日志格式,决定怎么记录;接着选择输出目标,决定记录到哪里;最后配置轮转策略,决定怎么管理这些日志。环环相扣,缺一不可。

好了,这一章的内容就到这里。日志体系是监控和排查的基础,搞懂了这些,后面咱们聊具体的异常排查才会更顺手。

专注资料整理