你是不是也遇到过这种情况:一个Hive批任务在集群上正常跑着,但是你的终端窗口全被INFO日志刷屏了,一屏接一屏,想找一条真正的WARN或者ERROR,得靠滚动条慢慢扒拉半天。我第一次认真调Hive日志的时候,就是在这么一大堆英文日志里翻来翻去找问题,翻到最后才发现任务本身根本没出错,纯粹是被INFO级别的无用输出干扰了判断。后来我把Hive运行时的INFO日志显示关掉之后,不管是排查问题还是日常跑批,都清爽了不少。这篇就专门聊聊Hive运行日志里INFO级别这些事,包括它们到底从哪来、怎么临时关、怎么永久关,以及我在实际改动过程中踩过的坑。
1. 这些INFO日志是从哪儿冒出来的
1.1 Hive日志和普通Java日志的“血缘关系”
Hive本质上是跑在JVM上的一个大数据分析工具,它的日志框架沿用了Java生态里非常典型的log4j体系。Hive本身不产生日志,它只是通过Logger接口把运行过程中的各种状态暴露出来,真正的输出行为由log4j统一控制。
一个日志从产生到显示,中间要经过三层:
- Logger:代码里调用logger.info("xxx")的地方,Hive源码里到处都有这种调用,比如解析SQL、生成执行计划、提交MR任务等环节都会打日志。
- 级别过滤:log4j根据你配置的级别阈值来决定某条日志是否放行。级别从低到高一般是TRACE、DEBUG、INFO、WARN、ERROR、FATAL。如果你把阈值设成WARN,那INFO和DEBUG的记录直接被丢掉,不再往下走。
- Appender输出:通过过滤的日志交给Appender,决定是打印到控制台、写入文件还是发到远程。Hive默认同时配置了控制台Appender和文件Appender。
你看到的满屏INFO日志,本质就是“Logger产生太多INFO记录 + rootLogger的阈值正好允许INFO放行 + 控制台Appender把它们全打出来了”,三个条件缺一不可。
1.2 Hive日志其实有两条独立线路
在实际排查过程中,不少人会把两条日志线路搞混,导致改了半天发现“好像没生效”。这里务必要分清楚:
- 客户端日志:hive CLI或beeline启动时的日志,包括SQL解析、执行计划生成、连接HiveServer2的过程。这些日志由
$HIVE_HOME/conf下的log4j配置文件控制,默认输出到/tmp/当前用户名/hive.log和终端控制台。 - 任务日志:提交到YARN上的MapReduce/Tez/Spark任务运行日志,由YARN的日志聚合机制管理。你在ResourceManager界面上看到的Container日志属于这一类,受
mapreduce.map.log.level、mapreduce.reduce.log.level等参数控制。
本文主要是讲客户端/服务端进程的INFO日志显示问题,但第5部分我会专门提一下任务日志级别,因为它经常和Hive日志配置纠缠在一起。
1.3 Hive默认配置文件到底长什么样
早期Hive(2.1.0之前)使用log4j 1.x,配置文件叫hive-log4j.properties。后来Hive升级到Log4j2,配置文件改成了log4j2.properties。这个版本差异非常重要,我后面专门开一节讲踩坑。
默认情况下,配置文件里和日志级别直接相关的是这么几行:
# hive-log4j.properties(Hive 2.x早期及之前) hive.root.logger=INFO,console hive.log.dir=/tmp/${user.name} hive.log.file=hive.log如果是Log4j2格式:
# log4j2.properties(Hive 2.1.0之后) rootLogger.level = INFO rootLogger.appenderRef.console.ref = console注意这个hive.root.logger并不只是一个简单属性:Hive启动时会把它解析成log4j的root logger配置。INFO,console的意思是“rootLogger级别为INFO,输出到console这个Appender”。默认配置文件里还有DRFA、RFA等文件Appender定义,但我们目前只需要关心console和INFO这两个词的组合逻辑。
2. 临时任务怎么快速压掉INFO输出
2.1 一次性会话用--hiveconf最省事
如果你只是临时跑一条比较重的SQL,不想永久改动集群配置,直接在命令行加一个参数就行:
hive --hiveconf hive.root.logger=WARN,console -f your_script.sql我这里把INFO换成了WARN,这样INFO级别的日志就被过滤掉了,控制台只输出WARN以上的内容。实际跑完任务后,该输出的结果集、运行统计、执行状态依然正常显示,不会说日志级别调高就把关键信息也吞了。
有人会问,为什么不直接用--hiveconf hive.log.level=WARN?我实际测试下来的结果是:在某些Hive版本里,hive.log.level会被log4j配置引用为rootLogger的level值,但在另一些版本里,hive.root.logger里的级别优先级更高,你改了hive.log.level控制台照样刷INFO。所以追求稳定的话,直接改hive.root.logger最保险。
2.2 进入CLI之后再关
有时候你已经启动了hive命令行,不想退出重开。这种情况下也可以在当前会话里设置:
set hive.root.logger=WARN,console;但这里要提醒一句:set命令对已经初始化完成的日志系统未必实时生效。log4j的Appender一般在Hive进程启动时就构建好了,你在CLI里跑set,很可能只是改了一个Hive配置项,并没有重新配置日志系统。我的经验是:进入CLI之后再设置经常无效,所以更靠谱的做法是启动时就带上--hiveconf参数。
如果你用的是beeline连接HiveServer2,情况有些不同。beeline客户端的日志输出相对克制,很多INFO日志在服务端侧,客户端看到的并不多。beeline中可以用!set verbose true/false控制一些输出细节,但如果你想彻底压掉客户端日志,还是在启动命令上加参数更可靠:
beeline -u "jdbc:hive2://node01:10000/default" --hiveconf hive.root.logger=WARN,console2.3 在脚本里统一封装
如果是经常要跑的批处理脚本,不建议每次手敲参数。可以在你的shell脚本里定义公共变量:
#!/bin/bash HIVE_CMD="hive --hiveconf hive.root.logger=WARN,console" $HIVE_CMD -e "select * from your_table;"这样所有任务都走同一套日志级别配置,以后想调整级别也只需要改一个变量。我在公司内部的数据平台脚本里基本都这么写,维护成本非常低。
3. 改配置文件一劳永逸的完整步骤
3.1 先确认你用的是哪个配置文件
这是整个改造过程中最关键的一步。Hive的日志配置文件根据版本和部署模式有不同的名字,常见的组合如下:
| 版本/场景 | 配置文件 | 日志框架 |
|---|---|---|
| Hive 2.1.0之前 | hive-log4j.properties | Log4j 1.x |
| Hive 2.1.0及之后 | log4j2.properties | Log4j 2.x |
| HiveServer2独立进程 | hiveserver2-log4j2.properties(或同前缀properties) | Log4j 2.x / 1.x |
在动手改之前,先到$HIVE_HOME/conf目录下看一眼实际存在的文件,别拿网上旧教程套全新版本。我在一堆生产环境上就遇到过,有人拿2.0版的教程去改3.1版的配置,改完完全没有反应。
3.2 改log4j2.properties(Hive 3.x最常用)
如果你用的是Hive 3.x版本,日志配置文件是log4j2.properties,用文本编辑器打开后找到rootLogger.level:
# 原始配置 status = error name = HiveLogging appender.console.type = Console appender.console.name = console appender.console.target = SYSTEM_ERR appender.console.layout.type = PatternLayout appender.console.layout.pattern = %d{ISO8601} %-5p [%t] %c{2}: %m%n rootLogger.level = INFO rootLogger.appenderRef.console.ref = console rootLogger.appenderRef.DRFA.ref = DRFA这里只需要改一行:
rootLogger.level = WARN如果你用的是Hive 2.x早期,对应的hive-log4j.properties文件,修改的是:
hive.root.logger=INFO,console改成:
hive.root.logger=WARN,console这两类文件的改动方式不一样,但核心逻辑都一样:把rootLogger的级别从INFO提到WARN,让INFO和DEBUG日志在源头被丢弃。
3.3 更推荐的做法:控制台不要INFO,但文件保留INFO
我在生产环境调完一轮之后,形成了一个我个人很推荐的做法:把控制台的INFO关掉,但文件Appender里保留INFO。
什么意思?就是让日志完整写到文件里方便排查,但终端窗口别刷屏。在hive-log4j.properties里,你可以这样改:
hive.root.logger=INFO,DRFA注意我的写法:级别还是INFO,但Appender从console换成了DRFA。DRFA在默认配置里是DailyRollingFileAppender,按天滚动写文件。这样控制台几乎不输出日志,但/tmp/用户名/hive.log文件里还是完整记录了INFO级别以上的内容。
在log4j2.properties里则要把console这个appenderRef去掉或者把console换成文件Appender:
rootLogger.level = INFO rootLogger.appenderRef.DRFA.ref = DRFA很多老手在实际运维时都是这么干的,因为完全把级别提到WARN,虽然控制台干净了,但遇到疑难问题想回看日志,发现文件里也只有WARN,很多关键线索已经丢了。控制台和文件解耦是日志配置里非常核心的一个思路。
3.4 顺手把日志目录从/tmp挪走
Hive默认把日志写在/tmp/${user.name}/hive.log。这个默认值在测试环境无所谓,生产环境却很坑:/tmp目录会被系统定时清理,有时候日志没来得及排查就被删了;还有多用户共用服务器时,/tmp下不同用户的日志文件还可能互相干扰权限。
我一般会在hive-site.xml里显式指定日志目录:
<property> <name>hive.log.dir</name> <value>/data/hive/logs</value> </property>注意这个配置项写在hive-site.xml里,是在Hive启动时读入的,随后会传给log4j作为变量解析。提前把目录建好并给足权限,再配合前面的Appender配置,日志管理会顺畅很多。
4. 关掉INFO之后常见的“不生效”怎么排查
4.1 我改的就是这个文件,为什么还是刷INFO
这是最高频的疑问。先别急着怀疑配置格式,按下面这个顺序排查一遍,大部分问题都能定位到:
第一,确认当前Hive进程是不是新起的。日志配置在进程启动阶段加载,你改完文件之后,原来挂着的Hive CLI或HiveServer2进程不会自动感知变化。CLI每次启动都是新进程,影响不大;但HiveServer2是长驻服务,改完配置必须重启才生效。
第二,确认有没有环境变量覆盖了配置。Hive启动时会读取HIVE_OPTS这个环境变量,如果你在~/.bashrc或hive-env.sh里加了类似export HIVE_OPTS="-Dhive.root.logger=DEBUG,console"的内容,那命令行参数甚至配置文件的设置都可能被它压掉。排查方式很简单:
echo $HIVE_OPTS echo $HADOOP_OPTS如果输出里有-Dhive.root.logger=...之类的值,先把环境变量清掉再试。
第三,确认你用的是CLI还是HiveServer2。如果你改的是hive-log4j.properties,但实际业务都是通过beeline连HiveServer2跑的,那客户端日志真正读的是hiveserver2-log4j2.properties或HiveServer2进程的JVM参数。两边各改各的,别改错对象。
第四,检查软链。有些发行版在$HIVE_HOME/conf目录下没有独立的log4j2.properties,而是有一个指向其他位置的软链文件。用ls -l看一下文件属性,如果是指向/etc/hive/conf之类的公共配置目录,光改$HIVE_HOME/conf下的文件没用,需要改真正指向的那个目标文件。
4.2 为什么set hive.root.logger=WARN不生效
这个我在第2节提过,再展开说一遍。set命令修改的是Hive的配置会话变量,但log4j框架在JVM启动时就已经用初始配置生成了Logger层级和Appender实例。Hive并没有在运行时监听这个配置项的变更并实时刷新log4j。换句话说,你看到的“设置成功”只是Hive层面的一个假象,日志框架层面根本没有收到通知。
我验证过几次,确认“启动参数 > 配置文件 > set命令”这个优先级基本是稳定的。所以不要在运行时指望靠set来关INFO,一定要回到启动命令或配置文件上去改。
4.3 排查时怎么确认当前真实生效的日志级别
有些时候你改来改去不确定到底哪份配置生效了,有一个比较粗暴但有效的验证方式:
hive --hiveconf hive.root.logger=WARN,console -e "select 1;"如果这条命令执行时控制台不再刷INFO,说明至少命令行这个入口的日志是走hive.root.logger这套逻辑的。然后再跑一个不带参数的普通hive命令做对比,看有没有INFO刷出来。两次对比,就能快速判断是配置没改对,还是被其他配置覆盖了。
还有一种更彻底的办法,临时把你的hive-log4j.properties或log4j2.properties重命名备份,重启Hive CLI,如果日志系统正常降级到默认配置并打印警告,说明你的配置文件确实被加载了;如果没有变化,说明你改的文件根本不是Hive加载的那个。
4.4 权限和目录也会导致看似“不生效”
日志文件写不写、写到哪,和配置项hive.log.dir、hive.log.file强相关。如果你把hive.log.dir指定到了一个不存在的目录,Hive启动时会尝试创建目录,但如果目录创建失败,日志文件也写不出来,控制台反而显得很“正常”。这种情况下你以为配置生效了,其实是文件日志没落地,控制台日志被其他方式压住了。
建议在改动配置后,主动去看一眼目标日志文件是否在持续写入:
ls -l /data/hive/logs/hive.log tail -f /data/hive/logs/hive.log确认文件在动,再判断级别是否真的生效。尤其是多个用户共用一套Hive环境时,目录权限问题非常隐蔽,经常出现“A用户正常、B用户不写日志”的情况,本质就是B用户对日志目录没有写权限。
5. 日志级别调整之外,顺手做对的几个配置
5.1 别把WARN和ERROR一起误杀
把INFO关掉之后,建议至少保留WARN和ERROR级别。很多人在调日志时容易走极端,出于“日志太多了烦人”的想法,直接把级别调到ERROR,甚至FATAL,最后任务出错时一点线索都没有。
我的习惯是:控制台一级调到WARN,文件里保持INFO;如果某个任务连WARN都不需要,再单独针对该任务覆盖级别。这样既保证日常清爽,又不影响问题排查。对于线上正在跑的批任务,WARN级别通常已经能暴露大部分隐患,比如分区覆盖警告、数据倾斜提示、配置冲突信息等,这些往往比INFO有价值得多。
5.2 日志文件的滚动与保留策略
日志文件不滚动的话,时间一长体积会很吓人。早期Hive的hive-log4j.properties里有DRFA和RFA两种Appender,区别在于:
| Appender | 滚动方式 | 常用参数 | 适用场景 |
|---|---|---|---|
| DRFA | 按天滚动 | DailyRollingFileAppender | 长期运行服务,每天一个日志文件 |
| RFA | 按大小滚动 | MaxFileSize、MaxBackupIndex | 任务密集,单文件增长过快 |
实际配置示例:
log4j.appender.DRFA=org.apache.log4j.DailyRollingFileAppender log4j.appender.DRFA.File=${hive.log.dir}/${hive.log.file} log4j.appender.DRFA.DatePattern='.'yyyy-MM-dd log4j.appender.RFA=org.apache.log4j.RollingFileAppender log4j.appender.RFA.File=${hive.log.dir}/${hive.log.file} log4j.appender.RFA.MaxFileSize=256MB log4j.appender.RFA.MaxBackupIndex=10这里有一个坑要提:DailyRollingFileAppender的MaxFileSize参数实际上不会真正触发按大小滚动,因为按天滚动模式下日期变更才是滚动条件。如果你想严格按照文件大小滚动,应该用RFA而不是DRFA。之前我见过有人把DRFA配了MaxFileSize,日志涨到几个GB也不滚动,误以为配置没生效,其实就是Appender选错了。
5.3 YARN任务日志的级别也要单独看
Hive提交的MR任务,运行期间的具体mapper/reducer日志不会显示在你的Hive CLI终端里,它们被打到了YARN的Container日志中。如果你发现某个任务跑得很慢或者报错,需要在YARN Web UI上查看对应Application的logs,这时候INFO级别可能才是关键。
Hive会话里可以这样单独调整任务日志级别:
set mapreduce.map.log.level=WARN; set mapreduce.reduce.log.level=WARN;这两个参数只影响YARN侧任务运行日志,不影响Hive客户端日志。我建议在hive-site.xml里统一把MR任务日志级别设为WARN,既减少日志量,又避免日志落盘压力。注意:这个参数对Tez引擎不同,Tez引擎的日志级别通过tez.task.log.level控制,需要分别处理。
5.4 临时排查问题时要懂得“反向操作”
关掉INFO是为了日常清爽,但如果你真的在排查一个复杂的SQL问题,INFO甚至DEBUG反而能帮你定位到具体是哪个Stage卡住、哪条数据触发了异常。这种时候不要犹豫,临时开回DEBUG:
hive --hiveconf hive.root.logger=DEBUG,console -f debug_query.sql或者修改log4j2.properties重启HiveServer2,把rootLogger.level暂时调成DEBUG。但务必记得:DEBUG日志的量级可能是INFO的几十倍,大任务开DEBUG不仅会刷屏,还会拖慢执行速度,严重时甚至把磁盘写满。用完一定要改回来。
5.5 常用Hive日志配置项速查
最后整理一份我在实际工作中经常用到的配置项,方便你直接参考:
| 配置项 | 作用 | 推荐值 |
|---|---|---|
| hive.root.logger | rootLogger级别与输出目标 | WARN,console 或 INFO,DRFA |
| hive.log.dir | 日志文件目录 | /data/hive/logs |
| hive.log.file | 日志文件名 | hive.log |
| hive.log4j.file | 指定log4j配置文件路径 | 默认$HIVE_HOME/conf下 |
| hive.server2.logging.operation.log.location | HS2操作日志目录 | /data/hive/operation_logs |
| mapreduce.map.log.level | MR任务日志级别 | WARN |
| mapreduce.reduce.log.level | MR任务日志级别 | WARN |
| tez.task.log.level | Tez任务日志级别 | WARN |
这里有一个容易被忽略的小点:hive.server2.logging.operation.log.location,它专门管理HiveServer2执行操作时的日志位置。如果你通过beeline连接HS2时发现任务日志很乱,可以单独把这个目录独立出来,跟CLI日志分开存。
我在实际项目中见过一个很经典的场景:数仓跑批任务每天凌晨定时调度,Hive日志文件默认写在/tmp/调度用户/hive.log,结果某天系统清理临时目录时把这个日志文件删了,任务失败后想排查,发现日志早没了。后来我把日志目录统一挪到独立磁盘路径,同时配置了按大小滚动和保留10份历史,再配合WARN展示级别的瘦身操作,整个日志体系才算真的稳定下来。关掉INFO显示只是第一步,把日志这家伙关进合理的“笼子”,后续排查问题你才会真正省心。