日志级别与输出控制:loglevel参数、console_loglevel、default_message_loglevel

内核日志系统,说白了就是内核的「黑匣子」。你想想看,系统崩了、驱动挂了、莫名其妙卡住了,第一反应是什么?肯定是翻日志。但日志怎么控制、怎么过滤、怎么让该出现的出现、不该出现的别刷屏,这里面的门道可不少。

我个人习惯,调内核日志就像调音响——高音、中音、低音得分开控制。内核里提供了三个核心控制点:loglevel 启动参数、console_logleveldefault_message_loglevel。搞懂它们,你就能精准控制日志输出。

1. 日志级别的本质:数字越小越严重

内核日志级别用数字 0-7 表示。数字越小,代表事件越严重。我刚开始接触时总搞反,后来记了个口诀:「0 是完蛋,7 是废话」。

级别 名称 含义 典型场景
0 KERN_EMERG 紧急事件 系统即将崩溃
1 KERN_ALERT 需要立即处理 严重硬件故障
2 KERN_CRIT 临界条件 驱动加载失败
3 KERN_ERR 错误 I/O 操作失败
4 KERN_WARNING 警告 资源不足
5 KERN_NOTICE 普通但重要 系统启动信息
6 KERN_INFO 信息 设备枚举信息
7 KERN_DEBUG 调试信息 开发调试用

核心原则:只有日志级别 小于等于 控制阈值时,才会输出。比如 console_loglevel=4,那么只有 0-4 的日志能上控制台。

2. console_loglevel:控制台的门卫

console_loglevel 是控制台日志的「总闸门」。它决定了哪些级别的日志能打印到串口、显示器或虚拟终端上。

我在项目中遇到过一个问题:生产环境的服务器,控制台被调试日志刷爆了,导致关键错误信息被淹没。排查了半天,最后发现是 console_loglevel 被设成了 7。

查看当前值很简单:

cat /proc/sys/kernel/printk
# 输出示例:4 4 1 7
# 四个数字依次是:
# console_loglevel
# default_message_loglevel
# minimum_console_loglevel
# default_console_loglevel

修改方式有两种:

  • 运行时修改:echo "3 4 1 7" > /proc/sys/kernel/printk,立即生效,重启失效。
  • 启动时设置:在 GRUB 内核命令行加 loglevel=3,永久生效。

我的习惯:开发环境设成 7,方便调试。生产环境设成 3 或 4,只保留错误和更严重的信息。你想想看,线上机器每秒几十万次 I/O,要是每个 INFO 都打印,性能直接崩了。

3. default_message_loglevel:日志的默认标签

这个参数很有意思。它决定了:当代码里没指定日志级别时,默认按什么级别处理

为什么会这样?很多驱动开发者在写 printk() 时偷懒,不写日志级别前缀。比如:

// 不推荐:没有指定级别
printk("Device initialized\n");

// 推荐:明确指定级别
printk(KERN_INFO "Device initialized\n");

对于第一种写法,内核就用 default_message_loglevel 来补位。默认值是 4(KERN_WARNING)。也就是说,所有偷懒的 printk 都会被当成警告级别处理。

我曾经接手过一个项目,整个驱动代码里全是裸的 printk(),没有级别。结果控制台上全是「警告」,真正的错误反而被淹没了。后来我把 default_message_loglevel 改成 6(KERN_INFO),至少能区分一下。

避坑指南:千万不要依赖默认级别。我曾经见过一个案例,某驱动在调试阶段用裸 printk 打印敏感数据,上线前忘了改,结果默认级别是 4,所有调试信息全打到生产环境控制台上了。嗯,那哥们后来被约谈了。

4. loglevel 启动参数:一锤定音

loglevel 是内核启动参数,它同时设置 console_logleveldefault_message_loglevel。说白了,一个参数干两件事。

用法:在 GRUB 配置中,找到 linux 开头的行,末尾加上 loglevel=3

# /etc/default/grub 示例
GRUB_CMDLINE_LINUX="quiet splash loglevel=3"

# 更新 GRUB 配置
update-grub  # Debian/Ubuntu
grub2-mkconfig -o /boot/grub2/grub.cfg  # RHEL/CentOS

这里有个细节:quiet 参数会把 console_loglevel 设为 4,而 loglevel=3 会覆盖它。所以最终效果是控制台只打印 0-3 级别的日志。

5. 三个参数的关系:一张图说清楚

我画了一张流程图,帮你理清这三个参数怎么协同工作:

内核日志输出控制流程 printk() 调用 有级别? (printk(KERN_xxx)) 使用原始级别 使用 default_message_loglevel 级别 ≤ console_loglevel? 输出到控制台 丢弃(仅写日志缓冲区)

从这张图能清楚看到:default_message_loglevel 是兜底方案,console_loglevel 是最终过滤。而 loglevel 启动参数,就是一次性设置这两个值的快捷方式。

6. 实战技巧:什么时候调什么

根据我的经验,不同场景下的推荐配置:

场景 console_loglevel default_message_loglevel 说明
日常开发 7 6 所有日志都看,方便调试
生产环境 3 4 只保留错误和更严重的信息
性能压测 1 3 减少日志 I/O 开销
排查死机 7 7 临时开启,捕获所有信息

一个小技巧:如果你不想重启,可以用 echo "7" > /proc/sys/kernel/printk 只改第一个值。排查完再改回去。我经常在客户现场这么干,省得重启服务。

7. 内核代码里的实际用法

看一段真实的内核代码片段,你就明白这些参数怎么用了:

// kernel/printk/printk.c 中的核心逻辑
void vprintk_emit(int facility, int level,
                  const char *dict, size_t dictlen,
                  const char *fmt, va_list args)
{
    // 如果没指定级别,用 default_message_loglevel
    if (level == LOGLEVEL_DEFAULT)
        level = default_message_loglevel;

    // 判断是否要输出到控制台
    if (level <= console_loglevel) {
        // 写入控制台设备
        console_unlock();
    }

    // 无论如何,都写入日志缓冲区
    log_store(facility, level, ...);
}

注意最后一行:不管控制台输不输出,日志缓冲区都会记录。这意味着你用 dmesg 能看到所有日志,但控制台只显示一部分。这个设计很巧妙——既保证了信息不丢失,又避免了控制台被刷爆。

避坑指南:我曾经在生产环境遇到过一个问题:某驱动在错误路径下疯狂打印 KERN_ERR 日志,虽然 console_loglevel=3 没让它们上控制台,但日志缓冲区被撑爆了,导致 dmesg 里全是重复信息,真正的关键日志被冲掉了。后来我加了日志限速 printk_ratelimit() 才解决。

嗯,关于日志级别和输出控制,核心就是这三个参数。搞懂了它们,你就能像调音响一样,精准控制内核日志的「音量」和「音色」。下次遇到日志刷屏或者关键信息丢失,先检查一下 /proc/sys/kernel/printk 里的四个数字,八成能找到问题。


公众号:蓝海资料掘金营,微信deep3321