如何在logback启动时滚动日志文件

52

我想配置logback以执行以下操作:

  • 记录到文件
  • 在文件大小达到50MB时滚动文件
  • 仅保留7天的日志
  • 在启动时始终生成新文件(进行滚动)

除了最后一个项目,即启动时的滚动,我已经实现了所有内容。有人知道如何实现吗?以下是配置...

  <appender name="File" class="ch.qos.logback.core.rolling.RollingFileAppender">

    <layout class="ch.qos.logback.classic.PatternLayout">
      <Pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg \(%file:%line\)%n</Pattern>
    </layout>

    <File>server.log</File>

    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
      <FileNamePattern>server.%d{yyyy-MM-dd}.log</FileNamePattern>
      <!-- keep 7 days' worth of history -->
      <MaxHistory>7</MaxHistory>

      <TimeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
        <MaxFileSize>50MB</MaxFileSize>
      </TimeBasedFileNamingAndTriggeringPolicy>

    </rollingPolicy>
  </appender>
12个回答

30

其他的建议都不适用于我的情况。我不想使用基于大小和时间的解决方案,因为它需要配置MaxFileSize,而我们正在使用严格的基于时间的策略。以下是我如何在启动时使用TimeBasedRollingPolicy滚动文件:

@NoAutoStart
public class StartupTimeBasedTriggeringPolicy<E> 
        extends DefaultTimeBasedFileNamingAndTriggeringPolicy<E> {

    @Override
    public void start() {
        super.start();
        nextCheck = 0L;
        isTriggeringEvent(null, null);
        try {
            tbrp.rollover();
        } catch (RolloverFailure e) {
            //Do nothing
        }
    }

}
设置nextCheck时间为0L,这样isTriggeringEvent()就会认为现在是滚动日志文件的时候了。它因此执行必要的代码来计算文件名,并方便地重置下一个检查时间值。随后调用rollover()函数将使日志文件进行滚动。由于这仅发生在启动时,因此比在每个日志消息上执行比较的方法更加优化。尽管这个比较很小,但仍会在每个日志消息上执行时轻微降低性能。此外,这也强制在启动时立即执行日志滚动,而不是等待第一个日志事件。
@NoAutoStart注释对于防止Joran在所有其他初始化完成之前执行start()方法非常重要。否则,你会得到一个NullPointerException。
以下是配置:
  <!-- Daily rollover appender that also appends timestamp and rolls over on startup -->
  <appender name="startupDailyRolloverAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>${LOG_FILE}</file>
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
      <fileNamePattern>${LOG_FILE}.%d{yyyyMMdd}_%d{HHmmss,aux}</fileNamePattern>
      <TimeBasedFileNamingAndTriggeringPolicy class="my.package.StartupTimeBasedTriggeringPolicy" />
    </rollingPolicy>
    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
  </appender> 
希望这可以帮助你!

1
我一直在苦苦挣扎,这正是我想要的,谢谢!我认为这应该成为一个标准功能,并且配置有点不直观。 - vincentlcy
1
感谢这个解决方案 - 我同意 @vincentlcy 的看法,并认为这应该是一个标准功能。 - dcompiled
2
由于我的需求是一个命令行工具,所以我根本不需要基于时间的触发器,因此我成功地切换到了“FixedWindowRollingPolicy”和自定义的“TriggeringPolicy”类,并使用一个“isTriggering”方法,该方法仅返回一次“true”。 - haridsv
1
因某种原因,这会导致在启动时创建多个日志文件而不是滚动一个。 - Andrew White
1
这个解决方案在使用logback 1.2.3时对我不起作用。在XML文件中配置TimeBasedFileNamingAndTriggeringPolicy会导致您选择的类被实例化,但稍后会被另一个实例覆盖。即使使用@NoAutoStart注释标记了start,也不会调用。我将发布我的解决方案。 - Dico
显示剩余2条评论

13

对于使用已经存在的组件的解决方案,logback建议使用唯一命名的文件http://logback.qos.ch/manual/appenders.html#uniquelyNamed

在应用程序开发阶段或短期应用程序(如批处理应用程序)中,在每次启动应用程序时创建一个新的日志文件是理想的。使用<timestamp>元素可以很容易地实现这一点。

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <timestamp key="startTimestamp" datePattern="yyyyMMddHHmmssSSS"/>
    <appender name="File"
    class="ch.qos.logback.core.rolling.RollingFileAppender">
        <layout class="ch.qos.logback.classic.PatternLayout">
            <Pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg \(%file:%line\)%n</Pattern>
        </layout>

        <file>server-${startTimestamp}.log</file>

        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <FileNamePattern>server-${startTimestamp}-%d{yyyy-MM-dd}-%i.log</FileNamePattern>
            <!-- keep 7 days' worth of history -->
            <MaxHistory>7</MaxHistory>

            <TimeBasedFileNamingAndTriggeringPolicy
            class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <MaxFileSize>1KB</MaxFileSize>
            </TimeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
    </appender>
    <root level="DEBUG">
        <appender-ref ref="File" />
    </root>
</configuration>

已更新至logback-1.2.1版本

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <timestamp key="startTimestamp" datePattern="yyyyMMddHHmmssSSS"/>
    <appender name="File"
    class="ch.qos.logback.core.rolling.RollingFileAppender">
        <layout class="ch.qos.logback.classic.PatternLayout">
            <Pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg \(%file:%line\)%n</Pattern>
        </layout>

        <file>server-${startTimestamp}.log</file>

        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>server-${startTimestamp}-%d{yyyy-MM-dd}-%i.log</fileNamePattern>
            <maxFileSize>10MB</maxFileSize>
            <!-- keep 7 days' worth of history -->
            <maxHistory>7</maxHistory>
            <totalSizeCap>20GB</totalSizeCap>
        </rollingPolicy>
    </appender>
    <root level="DEBUG">
        <appender-ref ref="File" />
    </root>
</configuration>

3
这种方法以及几乎所有的方法都存在问题,因为logback无法在重启时进行清理,因为fileNamePattern已经改变。仅当实例正在运行时,由于时间戳不会改变,因此才会发生清理。我唯一能想到的解决方案是修改TimeBaseArchiveRemover。 - Adam Gent

7

对我来说有效,使用以下类作为基于时间的文件命名和触发策略:

import java.io.File;
import java.util.concurrent.atomic.AtomicBoolean;

import ch.qos.logback.core.joran.spi.NoAutoStart;
import ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP;

@NoAutoStart
public class Trigger<E> extends SizeAndTimeBasedFNATP<E>
{
    private final AtomicBoolean trigger = new AtomicBoolean();

    public boolean isTriggeringEvent(final File activeFile, final E event) {
        if (trigger.compareAndSet(false, true) && activeFile.length() > 0) {
            String maxFileSize = getMaxFileSize();
            setMaxFileSize("1");
            super.isTriggeringEvent(activeFile, event);
            setMaxFileSize(maxFileSize);
            return true;
        }
        return super.isTriggeringEvent(activeFile, event);
    }
}

7

我找到了另一种解决方案,可以在应用程序启动时将logFile滚动。

我使用logback的RollingFileAppender,搭配logback的FixedWindowRollingPolicy和我自己实现的TriggeringPolicy<E>

FixedWindowRollingPolicy获取新日志文件的fileNamePattern,其中%1是新文件的数量。maxIndex表示我的“历史”最大数量。更多信息:FixedWindowRollingPolicy

我的实现TriggeringPolicy在第一次调用isTriggeringEvent(...)时返回true。因此,当策略第一次被调用时,WindowRollingPolicy会滚动日志文件,之后它将不会再次滚动。

RollingFileAppender的xml配置:

<configuration>
    ...
    <appender name="FILE_APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>logFile.log</file>

        <rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
            <fileNamePattern>logFile.%i.log</fileNamePattern>
            <minIndex>1</minIndex>
            <maxIndex>4</maxIndex>
        </rollingPolicy>

        <triggeringPolicy class="my.classpath.RollOncePerSessionTriggeringPolicy"/>
    </appender>
...
</configuration>

触发策略(TriggeringPolicy):
package my.classpath;

import ch.qos.logback.core.rolling.TriggeringPolicyBase;

import java.io.File;

public class RollOncePerSessionTriggeringPolicy<E> extends TriggeringPolicyBase<E> {
    private static boolean doRolling = true;

    @Override
    public boolean isTriggeringEvent(File activeFile, E event) {
        // roll the first time when the event gets called
        if (doRolling) {
            doRolling = false;
            return true;
        }
        return false;
    }
}

3
在ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP中覆盖isTriggeringEvent()方法应该很好用。只需在第一次调用isTriggeringEvent()方法时返回“true”即可。

有没有一种方法可以在启动时进行翻转 - 而无需触发事件? 我有一个仅用于错误消息的文件附加器。 我希望错误日志文件在每次启动后都为空 - 通常不会出现错误。 - Gerhard Presser
我可以问一下,调整maxFileSize的目的是什么? - Mykro
嗨Ceki,这需要是线程安全的吗?isTriggeringEvent函数会从多个线程中被调用吗? - Iker Jimenez
不幸的是,这似乎不起作用。请问2015年修复此问题的推荐方法是什么? - Christoffer Hammarström

2

我终于明白了。我可以按大小、时间和启动来滚动。以下是解决方案:

第一步,创建您自己的类。

@NoAutoStart
public class StartupSizeTimeBasedTriggeringPolicy<E> extends SizeAndTimeBasedFNATP<E> {

    private boolean started = false;

    @Override
    public boolean isTriggeringEvent( File activeFile, E event ) {
        if ( !started ) {
            nextCheck = 0L;
            return started = true;
        }

        return super.isTriggeringEvent( activeFile, event );
    };
}

第二步:配置Logback

<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>${LOGS_DIR}/${FILE_NAME}.log</file>
    <encoder>
        <pattern>%d [%thread] %-5level %logger{50} - %msg%n</pattern>
    </encoder>
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
        <fileNamePattern>${LOGS_DIR}/${FILE_NAME}.%d{yyyy-MM-dd}_%d{HHmmss,aux}.%i.log.zip</fileNamePattern>
        <maxHistory>30</maxHistory>
        <TimeBasedFileNamingAndTriggeringPolicy class="my.StartupSizeTimeBasedTriggeringPolicy">
            <MaxFileSize>250MB</MaxFileSize> 
        </TimeBasedFileNamingAndTriggeringPolicy>
    </rollingPolicy>
</appender>

2
Ceki的解决方案似乎对我无效,但至少看起来已经有了一部分进展。它会崩溃,因为在启动TimeBasedFileNamingAndTriggeringPolicyBase时无法看到滚动策略。通过一些技巧,我让它进行了一些日志记录,并通过更多技巧观察了触发器,但随后又因为无法解析其中一个文件名属性而再次崩溃...该包是logback的一个包,因此我可以访问一些内部信息,以复制SizeAndTimeBasedFNATP#isTriggeringEvent中的一些逻辑并调用computeCurrentPeriodsHighestCounterValue。我认为沿着这些线路做些事情可能有效,只是还没有找到魔法组合。我真的希望我在做傻事,否则我认为这将意味着要打开一些细节进行子类化,或者将其直接放入logback作为另一个滚动/触发策略。logback.xml:尝试了各种顺序的触发策略、TimeBasedFileNamingAndTriggeringPolicy以及滚动策略的内外。
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>${LOG_DIR}/${LOG_FILE_BASE}.log</file>
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
        <fileNamePattern>${LOG_DIR}/${LOG_FILE_BASE}.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
        <MaxHistory>7</MaxHistory>

        <TimeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.RollOnStartupPolicy" />
    </rollingPolicy>

    <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
        <level>INFO</level>
    </filter>

    <encoder>
        <pattern>%msg%n</pattern>
    </encoder>
</appender>

触发策略:
(注:该段内容已经是中文,无需翻译)
package ch.qos.logback.core.rolling;
public class RollOnStartupPolicy<E> extends SizeAndTimeBasedFNATP<E> {
private final AtomicBoolean firstTime = new AtomicBoolean(true);

    @Override
    public boolean isTriggeringEvent(File activeFile, E event) {
        if (!firstTime.get()) { // fast path
            return false;
        }

        if (firstTime.getAndSet(false)) {
            return true;
        }
        return false;
    }
}

这里涉及到一个异常:

java.lang.NullPointerException
at  at ch.qos.logback.core.rolling.TimeBasedFileNamingAndTriggeringPolicyBase.start(TimeBasedFileNamingAndTriggeringPolicyBase.java:46)
at  at ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP.start(SizeAndTimeBasedFNATP.java:36)
at  at ch.qos.logback.core.joran... [snip joran config]

2
API已更改(例如,setMaxFileSize不再存在),上面的很多东西似乎都不起作用,但是我有一些针对logback 1.1.8(此时最新版本)的工作内容。
我想在启动时滚动并按大小滚动,但不是按时间滚动。这样做:
public class RollOnStartupAndSizeTriggeringPolicy<E> extends SizeBasedTriggeringPolicy<E> {
    private final AtomicBoolean firstTime = new AtomicBoolean();

    public boolean isTriggeringEvent(final File activeFile, final E event) {
        if (firstTime.compareAndSet(false, true) && activeFile != null && activeFile.length() > 0) {
            return true;
        }
        return super.isTriggeringEvent(activeFile, event);
    }
}

这样你还需要一个滚动策略。FixedWindowRollingPolicy可能会起作用,但我不喜欢它,因为我想保留大量的文件,但对于那些来说它效率非常低下。一些按数字递增的东西(而不是像FixedWindow那样滑动)可能有效,但这不存在。只要我在编写自己的内容,我决定使用时间而不是计数。我想扩展当前的logback代码,但对于基于时间的内容,滚动和触发策略通常结合成一个类,并且有很多嵌套和循环的东西以及没有getter的字段,所以我发现这相当不可能。因此,我必须从头开始做很多工作。我将它保持简单,没有实现压缩等功能-虽然我很想拥有它们,但我只是试图保持简单。
public class TimestampRollingPolicy<E> extends RollingPolicyBase {
    private final RenameUtil renameUtil = new RenameUtil();
    private String activeFileName;
    private String fileNamePatternStr;
    private FileNamePattern fileNamePattern;

    @Override
    public void start() {
        super.start();
        renameUtil.setContext(this.context);
        activeFileName = getParentsRawFileProperty();
        if (activeFileName == null || activeFileName.isEmpty()) {
            addError("No file set on appender");
        }
        if (fileNamePatternStr == null || fileNamePatternStr.isEmpty()) {
            addError("fileNamePattern not set");
            fileNamePattern = null;
        } else {
            fileNamePattern = new FileNamePattern(fileNamePatternStr, this.context);
        }
        addInfo("Will use the pattern " + fileNamePattern + " to archive files");
    }

    @Override
    public void rollover() throws RolloverFailure {
        File f = new File(activeFileName);
        if (!f.exists()) {
            return;
        }
        if (f.length() <= 0) {
            return;
        }
        try {
            String archiveFileName = fileNamePattern.convert(new Date(f.lastModified()));
            renameUtil.rename(activeFileName, archiveFileName);
        } catch (RolloverFailure e) {
            throw e;
        } catch (Exception e) {
            throw new RolloverFailure(e.toString(), e);
        }
    }

    @Override
    public String getActiveFileName() {
        return activeFileName;
    }

    public void setFileNamePattern(String fnp) {
        fileNamePatternStr = fnp;
    }
}

然后配置看起来像这样
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
  <encoder>
    <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
  </encoder>
  <file>/tmp/monitor.log</file>
  <rollingPolicy class="my.log.TimestampRollingPolicy">
    <fileNamePattern>/tmp/monitor.%d{yyyyMMdd-HHmmss}.log</fileNamePattern>
  </rollingPolicy>
  <triggeringPolicy class="my.log.RollOnStartupAndSizeTriggeringPolicy">
    <maxFileSize>1gb</maxFileSize>
  </triggeringPolicy>
</appender>

如果您感到沮丧,因为这个问题还没有得到本地解决,请在以下网址投票支持它:

http://jira.qos.ch/browse/LOGBACK-204

http://jira.qos.ch/browse/LOGBACK-215

多年过去了,对我来说这是绝对关键的功能,尽管我知道许多其他框架也无法做到这一点。

1
这个解决方案确实有效,非常感谢。 然而,还有一个恼人的故障:当您第一次运行程序时,日志在创建后立即滚动,当它为空或几乎为空时。 因此,我建议修复一下:在调用该方法时检查日志文件是否存在且不为空。 另外,还有一个美化修复:重命名“started”变量,因为它隐藏了具有相同名称的继承成员。
@NoAutoStart
public class StartupSizeTimeBasedTriggeringPolicy<E> extends     SizeAndTimeBasedFNATP<E> {

    private boolean policyStarted;

    @Override
    public boolean isTriggeringEvent(File activeFile, E event) {
        if (!policyStarted) {
            policyStarted = true;
            if (activeFile.exists() && activeFile.length() > 0) {
                nextCheck = 0L;
                return true;
            }
        }
        return super.isTriggeringEvent(activeFile, event);
    }
}

此外,我相信它可以正常地与logback版本1.1.4-SNAPSHOT一起使用(我得到了源代码并自己编译),但是它不能完全与1.1.3版本一起使用。在1.1.3中,它会使用指定的时区正确命名文件,但是仍然会在默认时区午夜进行翻转。

我看到版本1.1.7已经发布了,但这个功能还没有加进去。 有计划将其添加到标准包中吗?它看起来相当简单。 显然,我仍在使用自己的StartupSizeTimeBasedTriggeringPolicy,但是保留这样一个小的补充很烦人。 - Leonid Ilyevsky

1
创建自己的子类,继承ch.qos.logback.core.rolling.TimeBasedRollingPolicy并重写其start方法。
public class MyPolicy
    extends ch.qos.logback.core.rolling.TimeBasedRollingPolicy
{

    public void start ( )
    {
        super.start( );
        rollover( );
    }
}

可惜这不起作用,因为触发策略从getElapsedPeriodsFileName()返回null,然后导致了rollover()失败。 - Mike Q

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接