问题

线上出了一个奇怪的bug,上传文件部分请求出现500错误

同一个文件上传,部分请求会出现500错误,部分返回正确的图片地址。

环境:20台服务器的集群,Java jar程序

经过排查发现是磁盘满了,参考之前我的文章
如何排查磁盘100%

发现一个令人大吃一惊的事情,每台机器是40G硬盘,日志文件占了32G,-,-

发现info的日志有几天的info.log文件有2G左右,这谁顶的住,应该是线上出了bug,然后疯狂打日志(内网环境没法复现)。

(解释一下不是我写的…)

至于为什么部分请求成功呢?因为20台还没有所有机器都满。

问题解决

首先肯定是删除一波日志文件解决问题

删除文件是解决了,但是不可能隔一段时间就上去删一下吧,程序员是不可能干这样的事情的,-。-,

最后肯定是需要定时删除下日志的,要么程序本身定时删除,要么定时脚本删除

(不推荐脚本删除,别人接收你的项目会发现联带的东西很多,很乱,尽量保持程序的专一和简洁,用行话叫 解耦 🎅🏽)

日志解决方案

直接说日志配置解决方案

Configuration:
  Properties:
    Property:
      - name: log.path
        value: /Users/admin/log/lib
      - name: application.name
        value: lib
  Appenders:
    RollingRandomAccessFile:
      - name: infoRollingFile
        ThresholdFilter:
          level: error
          onMatch: DENY
          onMismatch: ACCEPT
        fileName: ${log.path}/info.log
        filePattern: ${log.path}/info.%d{yyyy-MM-dd}.log
        PatternLayout:
          Pattern: '[%d]\t%p\t[${application.name}]\t%c{1}\t%M\t[%t]\t[%X{reqId}]\t--\t%m%n'
        Policies:
          SizeBasedTriggeringPolicy:
            size: 2M
          TimeBasedTriggeringPolicy:
            interval: 2
            modulate: true
        DefaultRolloverStrategy:
          Delete:
            IfFileName:
              glob: "*log"
            IfLastModified:
              age: 1s
          max: 3
      - name: errorRollingFile
        ThresholdFilter:
          level: error
          onMatch: ACCEPT
          onMismatch: DENY
        fileName: ${log.path}/err.log
        filePattern: ${log.path}/err.%d{yyyy-MM-dd}.log
        PatternLayout:
          Pattern: '[%d]\t%p\t[${application.name}]\t%c{1}\t%M\t[%t]\t[%X{reqId}]\t--\t%m%n'
        Policies:
          SizeBasedTriggeringPolicy:
            size: 1M
          TimeBasedTriggeringPolicy:
            interval: 1
            modulate: true
        DefaultRolloverStrategy:
          max: 3
          Delete:
            basePath: ${log.path}
            IfFileName:
              glob: "*log"
            IfLastModified:
              age: 1s
    Console:
      name: console
      PatternLayout:
        Pattern: '[%d]\t%p\t[${application.name}]\t%c{1}\t%M\t[%t]\t[%X{reqId}]\t--\t%m%n'
      target: SYSTEM_OUT
  Loggers:
    Root:
      level: info
      AppenderRef:
#        - ref: console
        - ref: infoRollingFile
        - ref: errorRollingFile

上面是我测试使用的日志配置,可以实现过期日志文件自动删除,亲测可以! 但是里面的配置还是要按实际的来

核心讲解

log4j2本身就考虑到日志文件太大的问题,支持日志的限制和过期删除,但是百度的教程大部分是xml文件,是比较旧的教程,而且没有详细讲解参数的具体作用,只给出配置文件。

作为合格的程序员,当然要手动尝试一下,亲测才能推荐出来。(q.q)

核心配置

	#清除策略
        DefaultRolloverStrategy:
        #删除的触发条件
          Delete:
          #要删除扫描的文件夹
            basePath: ${log.path}
            #限定扫描的文件名
            IfFileName:
            #匹配规则 以log结尾的
              glob: "*log"
              #文件的最新的修改时间间隔
            IfLastModified:
            #1s 表示1秒前  7d表示7天
              age: 1s

踩坑记录

1、IfLastModified表示的是最新修改时间,一般是设置为7d,也就是7天,如果你测试日志删除也是配置7d,你会发现删除没有效果,因为你就算新建日志文件,名字取为info.2021-06-02.log也不会删除,因为是你新创建的,修改时间小于7天,所以不执行删除。

2、basePath: ${log.path}必须要,因为没有配置这个属性导致程序不确定你要扫描的文件夹,怕误删文件,所以不执行删除。这个坑是最大的坑,搞到这一步就算是成了。

3、xml配置转化为yml,部分xml教程里面应该是可以真的执行删除的,但是xml配置和yml文件不一样,比如

<DefaultRolloverStrategy max="3">
                <Delete basePath="/Users/admin/log/lib/history" maxDepth="1">
                    <IfFileName glob="*.log.gz"/>
                    <IfLastModified age="3s"/>
                </Delete>
</DefaultRolloverStrategy>

怎么转化为yml文件?又有属性又有子集,其实你把自己想象成开发者就好了,你觉得怎样写yml才和xml文件配置的逻辑一致?

max=3是DefaultRolloverStrategy属性,所以应该是子集

Delete是子集,和属性应该是平级(至少不是max的子集)

最后结论应该是

        DefaultRolloverStrategy:
          max: 3
          Delete:
            basePath: /Users/admin/log/lib/history
            maxDepth: 1
            IfFileName:
              glob: "*.log.gz"
            IfLastModified:
              age: 3s

注意有些可以值可以不加"",但是有些明显是字符串的需要""标记为字符串,
直接写glob: .log.gz 会导致无法识别

4、 IfFileName 也是必要的,不要配置"*.log",因为会删除所有匹配的文件,比如error.log,可以配置为"info*.log"

ok,这样配置一下就可以实现自动删除了,而且你可以本地测试一下,用while打日志,比如你的日志是info.2021-07-02.log名称,那你可以复制一下,名称改为info.2021-06-02.log,然后启动测试类打印日志,你就会发现,满足条件的这个旧的日志自动删除了。

最后附上一个简单的测试类

    @Test
    public void base(){
        StringBuffer buffer=new StringBuffer();
        for (int i=0;i<100;i++){
            buffer.append(i+"hello"+Math.random());
        }
        while (true){
            /*try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }*/
            log.info("hello"+buffer.toString());
            //log.error("hello"+Math.random());
        }
    }
Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐