kingszelda 阅读(5) 评论(0)

1.业务背景

  对于线上业务而言,打印日志是一个系统运行状况的全面体检,日志打得约详细,越容易查找问题,但是机器磁盘是有限的,这时候很容易将磁盘撑爆。所以打印日志多少要选取一个平衡,打印适量的日志,只在关键环节,容易出错的地方打印日志即可。但是随着业务量的提升,即使我们控制了打印日志的频率,但日志文件的容量也在大量扩大。如果我们对日志文件的处理方式不当,日志文件将打到磁盘上线,新业务就再也刷不出来任何日志了。

  因此,我们对日志的处理一般分为三个步骤:

  1. 打印当天日志,历史日志重命名为带日期格式,以示区分。
  2. 压缩历史日志,减少历史日志空间占用,同时留存证据。
  3. 删除过早的历史日志,减少磁盘占用(建议有单独服务提前收集)。

2.日志按天切分

  主流的日志服务组件都是支持日志文件时间切分的,以loabgck为例,看一个典型的logback配置logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!--自定义变量-->
    <property name="LOG_NAME" value="MyApp"/>
    <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg,50是logger名称最大长度:日志消息,%n是换行符-->
    <property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50}- %msg%n"/>

    <appender name="Console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder charset="UTF-8">
            <!--%highlight(%-5level) %cyan表示高亮日志等级,并使用藏青色打印logger部分-->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %highlight(%-5level) %cyan(%logger{50}) - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- 输出格式 appender -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${catalina.base}/logs/${LOG_NAME}.log</file>
        <encoder charset="UTF-8">
            <pattern>${LOG_PATTERN}</pattern>
        </encoder>
        <!--按照logback提供的时间切分策略分隔日志文件-->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${catalina.base}/logs/${LOG_NAME}.%d{yyyy-MM-dd}.log</fileNamePattern>
        </rollingPolicy>
    </appender>

    <!-- error 日志 appender -->
    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${catalina.base}/logs/${LOG_NAME}_error.log</file>
        <!--过滤器,只有级别高于ERROR以上的才会打印,级别包括:TRACE < DEBUG < INFO < WARN < ERROR-->
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>ERROR</level>
        </filter>
        <encoder charset="UTF-8">
            <pattern>${LOG_PATTERN}</pattern>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${catalina.base}/logs/${LOG_NAME}_error.%d{yyyy-MM-dd}.log</fileNamePattern>
        </rollingPolicy>
    </appender>
    <!--自定义包下面的日志级别 name是包结构 level是级别 addtivity:是否向上级logger传递打印信息。默认是true-->
    <logger name="com.myApp" level="INFO" addtivity="true">
        <appender ref="FILE"/>
    </logger>
    <!--默认的日志级别,如果上面的logger没有命中,则按照root的级别打印日志,root是所有logger的父节点,如果addtivity是false,则不会抛到这里-->
    <root level="ERROR">
        <appender-ref ref="FILE"/>
        <appender-ref ref="ERROR_FILE"/>
    </root>
</configuration>

  通过上面的logback配置,当天的日志会打印到myApp/logs/myApp.log与myApp/logs/myApp_error.log,分别对应的是info日志与error日志。当到达24点之后,昨天的日志会重命名为myApp/logs/myApp.2017-09-06.log与myApp/logs/myApp_error.2017-09-06.log。与此同时,当天的日志会打印到myApp/logs/myApp.log中。

  这样就做到了按时间分割日志文件,历史日志文件按照固定格式重命名。

3.压缩与删除历史日志

  由于线上机器都是linux操作系统,我们可以使用crontab脚本进行文件压缩与删除。

  操作流程分为3步:

  1. 找到历史日志文件
  2. 压缩昨天的日志文件
  3. 删除n天钱的日志文件压缩包

  作为一个crontab脚本,就是充分发挥shell命令强度的时候了。显然,这是一个shell脚本可以完成的功能,直接看最终版。

#!/bin/bash
#
#
#
ZIPDATE=$(date +%F -d "-1 day");
DELDATE=$(date +%F -d "-10 day");
SECOND=$(echo $RANDOM | cut -c1-3)

sleep $SECOND

for i in `find /webapps/ -maxdepth 2 \( -type d -o -type l \) -name logs`;
do
        find -L $i -maxdepth 1 -type f \( -name "*${ZIPDATE}*" -a ! -name "*.gz" \) -exec gzip {} \;
        find -L $i -maxdepth 1 -type f \( -name "*${DELDATE}*" -a -name "*.gz" \) -exec rm -f {} \; 
done

  首先定义了三个变量,举例:

ZIPDATE是前一天的日期yyyy-MM-dd格式:2017-09-06

DELDATE是要删除的10天前的日期yyyy-MM-dd格式:2017-08-28

SECOND是随即生成的3为数,用来休眠定时脚本,避免多个脚本同时执行,造成cpu集中计算。。

下面的for循环是真正的处理逻辑。for i in `find /webapps/ -maxdepth 2 \( -type d -o -type l \) -name logs`;

do
…………
done

外层的逻辑是:查找/webapps/目录下的最深层级是2的文件,因为不同的项目都会可能在同一台机器的webapps目录下,不同的项目下都会有logs目录用来存放日志文件。查询条件是文件是文件夹类型或者链接类型,名字是logs。这样我们就找到了所有logs文件夹。

这时候看里层逻辑:

find -L $i -maxdepth 1 -type f \( -name "*${ZIPDATE}*" -a ! -name "*.gz" \) -exec gzip {} \;

查找logs文件夹下,层级是1的即logs文件夹下的,名字包含昨天日期同时结尾不是.gz格式的文件,因为我们的目标是压缩为gz格式,这里避免重复压缩。查找到这些文件之后,运行gzip命令进行压缩。
这里依赖的是find命令的-exec扩展功能。可以参考find命令的手册:
-exec command ;
      Execute command; true if 0 status is returned.  All following arguments to find are taken to be arguments to the command until an argument consisting of ‘;’
      is encountered.  The string ‘{}’ is replaced by the current file name being processed everywhere it occurs in the arguments to  the  command,  not  just  in
      arguments  where it is alone, as in some versions of find.  Both of these constructions might need to be escaped (with a ‘\’) or quoted to protect them from
      expansion by the shell.  See the EXAMPLES section for examples of the use of the -exec option.  The specified command is run once  for  each  matched  file.
      The  command  is  executed  in  the  starting  directory.    There are unavoidable security problems surrounding use of the -exec action; you should use the
      -execdir option instead.

-exec command {} +
      This variant of the -exec action runs the specified command on the selected files, but the command line is built by appending each selected file name at the
      end; the total number of invocations of the command will be much less than the number of matched files.  The command line is built in much the same way that
      xargs builds its command lines.  Only one instance of ‘{}’ is allowed within the command.  The command is executed in the starting directory.

  同样的,删除n天前文件也是使用shell实现的。

find -L $i -maxdepth 1 -type f \( -name "*${DELDATE}*" -a -name "*.gz" \) -exec rm -f {} \; 

4.启动crontab服务

cron服务是linux的内置服务,但它不会开机自动启动。可以用以下命令启动和停止服务:

/sbin/service crond start
/sbin/service crond stop
/sbin/service crond restart
/sbin/service crond reload

以上1-4行分别为启动、停止、重启服务和重新加载配置。

要把cron设为在开机的时候自动启动,在 /etc/rc.d/rc.local 脚本中加入 /sbin/service crond start 即可

查看当前用户的crontab,输入 crontab -l;

编辑crontab,输入 crontab -e;

删除crontab,输入 crontab -r

这时候,加入我们上面定时任务:

每天0点5分执行这个脚本,不过脚本内本身会随机休眠100-999秒。

5 0 * * *  sh /myTask/log_daily.sh 1>/dev/null

5.小结

  本文提供了一种处理线上日志的方法,具体实现是通过logback进行按日期切分,ctontab定时压缩与删除。在这个思路下,实现按小时切分以及其他的切分方案都是可行的,只要新文件名与crontab中定位的文件名一致即可。