解决方案:

进入 zookeeper 安装目录下,创建启动脚本文件 start.sh

start.sh

#!/usr/bin/env bash

ZOO_LOG_DIR='/data0/logs/zookeeper'  ZOO_LOG4J_PROP='INFO,ROLLINGFILE'  bin/zkServer.sh start

ZOO_LOG_DIR 设置了日志文件夹路径, ZOO_LOG4J_PROP='INFO,ROLLINGFILE' 设置日志输出级别是INFO,采用滚动文件更新的方式生成日志文件。

为了方便停止zookeeper 程序,我们也可以创建 stop.sh 文件,用来停止 zookeeper。这个文件可有可无,不影响解决本问题。

stop.sh

#!/usr/bin/env bash

bin/zkServer.sh stop

找解决方案的思路

我先给出了解决方案,方便读者解决问题。然后我再分享一下我自己是怎么找到这个解决方案的。这个章节主要讲一下解决的过程。

在zookeeper的安装文件夹里,我先进入配置文件夹 conf,里面有 log4j.properties 文件。log4j 是常见的日志组件,我猜测这是日志的配置文件。打开 log4j.properties 文件,

log4j.properties

# Copyright 2012 The Apache Software Foundation
# 
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# Define some default values that can be overridden by system properties
zookeeper.root.logger=INFO, CONSOLE

zookeeper.console.threshold=INFO

zookeeper.log.dir=.
zookeeper.log.file=zookeeper.log
zookeeper.log.threshold=INFO
zookeeper.log.maxfilesize=256MB
zookeeper.log.maxbackupindex=20

zookeeper.tracelog.dir=${zookeeper.log.dir}
zookeeper.tracelog.file=zookeeper_trace.log

log4j.rootLogger=${zookeeper.root.logger}

#
# console
# Add "console" to rootlogger above if you want to use this 
#
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.Threshold=${zookeeper.console.threshold}
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} [myid:%X{myid}] - %-5p [%t:%C{1}@%L] - %m%n

#
# Add ROLLINGFILE to rootLogger to get log file output
#
log4j.appender.ROLLINGFILE=org.apache.log4j.RollingFileAppender
log4j.appender.ROLLINGFILE.Threshold=${zookeeper.log.threshold}
log4j.appender.ROLLINGFILE.File=${zookeeper.log.dir}/${zookeeper.log.file}
log4j.appender.ROLLINGFILE.MaxFileSize=${zookeeper.log.maxfilesize}
log4j.appender.ROLLINGFILE.MaxBackupIndex=${zookeeper.log.maxbackupindex}
log4j.appender.ROLLINGFILE.layout=org.apache.log4j.PatternLayout
log4j.appender.ROLLINGFILE.layout.ConversionPattern=%d{ISO8601} [myid:%X{myid}] - %-5p [%t:%C{1}@%L] - %m%n

#
# Add TRACEFILE to rootLogger to get log file output
#    Log TRACE level and above messages to a log file
#
log4j.appender.TRACEFILE=org.apache.log4j.FileAppender
log4j.appender.TRACEFILE.Threshold=TRACE
log4j.appender.TRACEFILE.File=${zookeeper.tracelog.dir}/${zookeeper.tracelog.file}

log4j.appender.TRACEFILE.layout=org.apache.log4j.PatternLayout
### Notice we are including log4j's NDC here (%x)
log4j.appender.TRACEFILE.layout.ConversionPattern=%d{ISO8601} [myid:%X{myid}] - %-5p [%t:%C{1}@%L][%x] - %m%n

文件中,log4j.appender.CONSOLE 相关代码和 log4j.appender.ROLLINGFILE 相关代码表示log4j定义了两个appender:CONSOLE 和 ROLLINGFILE。

显然 zookeeper.root.logger=INFO, CONSOLE 设置了log4j 内部的 log4j.rootLogger 参数,这个参数控制日志级别和日志输出方式。INFO, CONSOLE 指默认是INFO级别和控制台输出(appender=CONSOLE)。
log4j.rootLogger 第二项说明了是用哪个appender。

zookeeper.log.dir=. 控制了输出日志的文件夹。

打开 bin/zkServer.sh 文件,我们可以先往下翻,找到下面这段代码:

nohup "$JAVA" $ZOO_DATADIR_AUTOCREATE "-Dzookeeper.log.dir=${ZOO_LOG_DIR}" \
    "-Dzookeeper.log.file=${ZOO_LOG_FILE}" "-Dzookeeper.root.logger=${ZOO_LOG4J_PROP}" \
    -XX:+HeapDumpOnOutOfMemoryError -XX:OnOutOfMemoryError='kill -9 %p' \
    -cp "$CLASSPATH" $JVMFLAGS $ZOOMAIN "$ZOOCFG" > "$_ZOO_DAEMON_OUT" 2>&1 < /dev/null &

可见在启动zookeeper的时候,已经指定了 zookeeper.root.loggerzookeeper.log.dir 两个参数,变量分别是 ZOO_LOG4J_PROPZOO_LOG_DIR 。我们往上翻看文件:

# use POSTIX interface, symlink is followed automatically
ZOOBIN="${BASH_SOURCE-$0}"
ZOOBIN="$(dirname "${ZOOBIN}")"
ZOOBINDIR="$(cd "${ZOOBIN}"; pwd)"

if [ -e "$ZOOBIN/../libexec/zkEnv.sh" ]; then
  . "$ZOOBINDIR"/../libexec/zkEnv.sh
else
  . "$ZOOBINDIR"/zkEnv.sh
fi

ZOOBINDIR="$(cd "${ZOOBIN}"; pwd)" ZOOBINDIR 变量是脚本当前路径,即 安装目录/bin
zkServer.sh 还要执行 zkEnv.sh 中的内容。看名称猜测 zkEnv.sh 文件中是zookeeper的环境参数。

打开 zkEnv.sh 文件,看下面的代码:

if [ "x${ZOO_LOG4J_PROP}" = "x" ]
then
    ZOO_LOG4J_PROP="INFO,CONSOLE"
fi

"x${ZOO_LOG4J_PROP}" = "x" 这行代码是常见的判断变量是否赋值的方式。
如果前面的脚本或者用户输入的命令中没有赋值 ZOO_LOG4J_PROP ,使用默认值 "INFO,CONSOLE"

打开 zkEnv.sh 文件,看下面的代码:

if [ "x${ZOO_LOG_DIR}" = "x" ]
then
    ZOO_LOG_DIR="$ZOOKEEPER_PREFIX/logs"
fi

ZOO_LOG_DIR 的逻辑类似上面提到的 ZOO_LOG4J_PROP,如果前面的脚本或者用户输入的命令中没有赋值ZOO_LOG_DIR,ZOO_LOG_DIR的默认值是"$ZOOKEEPER_PREFIX/logs"

我们在zkEnv.sh 文件开头可以看到变量 ZOOKEEPER_PREFIX 的定义:

ZOOBINDIR="${ZOOBINDIR:-/usr/bin}"   # 28
ZOOKEEPER_PREFIX="${ZOOBINDIR}/.."   # 29

29 行说明 ZOOKEEPER_PREFIX 是 ZOOBINDIR 的父级目录。
28 行看一下 ZOOBINDIR 的取值。如果脚本中已经赋值过 ZOOBINDIR ,使用原先的值。如果没有赋值过 ZOOBINDIR ,默认值是 /usr/bin。
因为 zkServer.sh 是调用 zkEnv.sh 的父级脚本文件,zkServer.sh 中已经赋值了ZOOBINDIR,ZOOBINDIR当前目录,也就是 安装目录/bin ,所以 ZOOKEEPER_PREFIX 就应该是安装目录。这与我的经验相符,默认情况下zookeeper会在安装目录下生成一个log文件夹,里面放置日志文件。

综上所述,我们只需要在启动zookeeper的时候设置 ZOO_LOG4J_PROP 和 ZOO_LOG_DIR 这两个变量就行。不需要改动zookeeper原有的脚本文件。