Java Log4j2 RollingFile Appender - 在每个日志文件的开头添加自定义信息

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/20819376/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-13 04:34:54  来源:igfitidea点击:

Log4j2 RollingFile Appender - add custom info at the start of each logfile

javalog4j2

提问by Joe

Using log4j2 (beta9) with java 1.7.

在 java 1.7 中使用 log4j2 (beta9)。

My complete log4j2.xml:

我完整的 log4j2.xml:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
  <Properties>
    <Property name="projectPrefix">Tts</Property>
    <Property name="rawPattern">%d %-5p [%t] %C{2} (%F:%L) - %m%n</Property>
    <Property name="coloredPattern">%d %highlight{%-5p}{FATAL=bright red, ERROR=red, WARN=yellow, INFO=cyan, DEBUG=green, TRACE=bright blue} %style{[%t] %C{2} (%F:%L) -}{bright,black} %m%n</Property>
    <Property name="fileName">Log/${projectPrefix}.log</Property>
    <Property name="filePattern">Log/${projectPrefix}-%i.log</Property>
  </Properties>
  <Appenders>
    <Console name="Stdout" target="SYSTEM_OUT">
      <PatternLayout pattern="${coloredPattern}"/>
    </Console>
    <RollingFile name="Logfile" fileName="${fileName}" filePattern="${filePattern}">
      <PatternLayout pattern="${rawPattern}"/>
      <Policies>
        <SizeBasedTriggeringPolicy size="16 MB"/>
      </Policies>
      <DefaultRolloverStrategy fileIndex="min" max="16"/>
    </RollingFile>
  </Appenders>
  <Loggers>
    <Root level="info">
      <AppenderRef ref="Stdout"/>
      <AppenderRef ref="Logfile"/>
    </Root>
  </Loggers>
</Configuration>

I want to add some custom info at the top of each logfile, like the version string of my application, the application uptime and the system uptime. And even writing some ?bye, bye / eof? to the bottom of the just closed logfile would also be fine.

我想在每个日志文件的顶部添加一些自定义信息,例如我的应用程序的版本字符串、应用程序正常运行时间和系统正常运行时间。甚至写一些?再见,再见/ eof?到刚刚关闭的日志文件的底部也可以。

Is there something like a hook or callback to get notified when the RollingFileAppander has created a new file, so that I can put my things at first into these new logfile (or any other suggestion)?

当 RollingFileAppander 创建一个新文件时,是否有类似钩子或回调的东西得到通知,以便我可以首先将我的东西放入这些新的日志文件(或任何其他建议)中?

采纳答案by Joe

Ok, there is a working solution for this problem by extending DefaultRolloverStrategy like described here. But

好的,通过像这里描述的那样扩展 DefaultRolloverStrategy ,有一个解决这个问题的有效解决方案。但

  • it needs about 150 lines of code (incl. wrapping RolloverDescription and appender.rolling.helper.Action) and
  • smells a bit because the need to completely copy DefaultRolloverStrategy's factory method (makes this solution maintenance-unfriendly eg. if DefaultRolloverStrategy gets more config parameters in future versions)
  • 它需要大约 150 行代码(包括包装 RolloverDescription 和 appender.rolling.helper.Action)和
  • 有点味道,因为需要完全复制 DefaultRolloverStrategy 的工厂方法(使该解决方案维护不友好,例如,如果 DefaultRolloverStrategy 在未来版本中获得更多配置参数)

To let log4j2 call our factory method, the root tag of log4j2.xml must be attributed with the package of our class, e.g.:

要让 log4j2 调用我们的工厂方法,log4j2.xml 的根标签必须归属于我们类的包,例如:

<Configuration packages="de.jme.toolbox.logging">
...
</Configuration>

and within our own RolloverStrategy we have to deal with @Pluginand @PluginFactoryas described here.

和我们自己的RolloverStrategy之内,我们不得不面对@Plugin@PluginFactory描述在这里

Finally here my complete log4j2.xml (you don't need all that properties - that's just the way how I like to configure my logging):

最后这里是我完整的 log4j2.xml(您不需要所有这些属性 - 这只是我喜欢配置日志记录的方式):

<?xml version="1.0" encoding="UTF-8"?>
<Configuration packages="de.jme.toolbox.logging">
  <Properties>
    <Property name="projectPrefix">Tts</Property>
    <Property name="rawPattern">%d %-5p [%t] %C{2} (%F:%L) - %m%n</Property>
    <Property name="coloredPattern">%d %highlight{%-5p}{FATAL=bright red, ERROR=red, WARN=yellow, INFO=cyan, DEBUG=green, TRACE=bright blue} %style{[%t] %C{2} (%F:%L) -}{bright,black} %m%n</Property>
    <Property name="coloredShortPattern">%d %highlight{%-5p}{FATAL=bright red, ERROR=red, WARN=yellow, INFO=cyan, DEBUG=green, TRACE=bright blue} %style{[%t] -}{bright,black} %m%n</Property>
    <Property name="fileName">Log/${projectPrefix}.log</Property>
    <Property name="filePattern">Log/${projectPrefix}-%i.log</Property>
  </Properties>
  <Appenders>
    <Console name="Stdout" target="SYSTEM_OUT">
      <PatternLayout pattern="${coloredPattern}"/>
    </Console>
    <RollingFile name="Logfile" fileName="${fileName}" filePattern="${filePattern}">
      <PatternLayout pattern="${rawPattern}"/>
      <Policies>
        <SizeBasedTriggeringPolicy size="1 MB"/>
      </Policies>
      <MyRolloverStrategy fileIndex="min" max="16"/>
    </RollingFile>
  </Appenders>
  <Loggers>
    <Root level="info">
      <AppenderRef ref="Stdout"/>
      <AppenderRef ref="Logfile"/>
    </Root>
  </Loggers>
</Configuration>

And here MyRolloverStrategy.java:

这里是 MyRolloverStrategy.java:

package de.jme.toolbox.logging;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.zip.Deflater;

import org.apache.logging.log4j.core.appender.rolling.DefaultRolloverStrategy;
import org.apache.logging.log4j.core.appender.rolling.RollingFileManager;
import org.apache.logging.log4j.core.appender.rolling.RolloverDescription;
import org.apache.logging.log4j.core.appender.rolling.helper.Action;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
import org.apache.logging.log4j.core.config.plugins.PluginFactory;
import org.apache.logging.log4j.core.helpers.Integers;
import org.apache.logging.log4j.core.lookup.StrSubstitutor;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.status.StatusLogger;

/**
 * Own RolloverStrategy to hook the DefaultRolloverStrategy's rollover events
 *
 * Siehe auch:
 * - https://issues.apache.org/jira/browse/LOG4J2-486
 * - http://apache-logging.6191.n7.nabble.com/log4j2-getting-started-amp-rolling-files-tt8406.html#a42402
 * - http://stackoverflow.com/questions/20819376/log4j2-rollingfile-appender-add-custom-info-at-the-start-of-each-logfile
 *
 * @author Joe Merten
 */
@org.apache.logging.log4j.core.config.plugins.Plugin(name="MyRolloverStrategy", category="Core", printObject=true)
public class MyRolloverStrategy extends DefaultRolloverStrategy {

    protected static final Logger logger = StatusLogger.getLogger();

    // ==============================
    // ↓↓↓ Some stuff copied from ↓↓↓
    // https://svn.apache.org/repos/asf/logging/log4j/log4j2/trunk/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/DefaultRolloverStrategy.java r1556050
    // Just changed ?DefaultRolloverStrategy? to ?MyRolloverStrategy?

    private static final int MIN_WINDOW_SIZE = 1;
    private static final int DEFAULT_WINDOW_SIZE = 7;

    @PluginFactory
    public static MyRolloverStrategy createStrategy(
            @PluginAttribute("max") final String max,
            @PluginAttribute("min") final String min,
            @PluginAttribute("fileIndex") final String fileIndex,
            @PluginAttribute("compressionLevel") final String compressionLevelStr,
            @PluginConfiguration final Configuration config) {
        final boolean useMax = fileIndex == null ? true : fileIndex.equalsIgnoreCase("max");
        int minIndex;
        if (min != null) {
            minIndex = Integer.parseInt(min);
            if (minIndex < 1) {
                LOGGER.error("Minimum window size too small. Limited to " + MIN_WINDOW_SIZE);
                minIndex = MIN_WINDOW_SIZE;
            }
        } else {
            minIndex = MIN_WINDOW_SIZE;
        }
        int maxIndex;
        if (max != null) {
            maxIndex = Integer.parseInt(max);
            if (maxIndex < minIndex) {
                maxIndex = minIndex < DEFAULT_WINDOW_SIZE ? DEFAULT_WINDOW_SIZE : minIndex;
                LOGGER.error("Maximum window size must be greater than the minimum windows size. Set to " + maxIndex);
            }
        } else {
            maxIndex = DEFAULT_WINDOW_SIZE;
        }
        final int compressionLevel = Integers.parseInt(compressionLevelStr, Deflater.DEFAULT_COMPRESSION);
        return new MyRolloverStrategy(minIndex, maxIndex, useMax, compressionLevel, config.getStrSubstitutor());
    }
    // ↑↑↑ Some stuff copied from ↑↑↑
    // ==============================

    protected MyRolloverStrategy(int minIndex, int maxIndex, boolean useMax, int compressionLevel, StrSubstitutor subst) {
        super(minIndex, maxIndex, useMax, compressionLevel, subst);
    }

    // Wrapper class only for setting a hook to execute()
    static class MyAction implements Action {
        final Action delegate;
        final String fileName;

        public MyAction(final Action delegate, final String fileName) {
            this.delegate = delegate;
            this.fileName = fileName;
        }

        @Override public void run() {
            delegate.run();
        }

        @Override public boolean execute() throws IOException {
            try {
                BufferedWriter writer = null;
                try {
                    writer = new BufferedWriter(new FileWriter(new File(fileName), true));
                    writer.write("****************************\n");
                    writer.write("*** Bye, bye old logfile ***\n");
                    writer.write("****************************\n");
                } finally {
                    if (writer != null)
                        writer.close();
                }
            } catch (Throwable e) {
                logger.error("Writing to bottom of old logfile \"" + fileName + "\" with", e);
            }

            boolean ret = delegate.execute();

            try {
                BufferedWriter writer = null;
                try {
                    writer = new BufferedWriter(new FileWriter(new File(fileName), true));
                    writer.write("*************************\n");
                    writer.write("*** Hello new logfile ***\n");
                    writer.write("*************************\n");
                } finally {
                    if (writer != null)
                        writer.close();
                }
            } catch (Throwable e) {
                logger.error("Writing to top of new logfile \"" + fileName + "\" with", e);
            }

            return ret;
        }

        @Override public void close() {
            delegate.close();
        }

        @Override public boolean isComplete() {
            return delegate.isComplete();
        }
    }

    // Wrapper class only for setting a hook to getSynchronous().execute()
    static class MyRolloverDescription implements RolloverDescription {
        final RolloverDescription delegate;

        public MyRolloverDescription(final RolloverDescription delegate) {
            this.delegate = delegate;
        }

        @Override public String getActiveFileName() {
            return delegate.getActiveFileName();
        }

        @Override public boolean getAppend() {
            //return delegate.getAppend();
            // As long as we already put some data to the top of the new logfile, subsequent writes should be performed with "append".
            return true;
        }

        // The synchronous action is for renaming, here we want to hook
        @Override public Action getSynchronous() {
            Action delegateAction = delegate.getSynchronous();
            if (delegateAction == null) return null;
            return new MyAction(delegateAction, delegate.getActiveFileName());
        }

        // The asynchronous action is for compressing, we don't need to hook here
        @Override public Action getAsynchronous() {
            return delegate.getAsynchronous();
        }
    }

    public RolloverDescription rollover(final RollingFileManager manager) {
        RolloverDescription ret = super.rollover(manager);
        return new MyRolloverDescription(ret);
    }
}

Solving this requirement might be easier in future versions of log4j2, if my posted feature requestwould implemented.

如果我发布的功能请求能够实现,那么在 log4j2 的未来版本中解决这个需求可能会更容易。

回答by Remko Popma

Currently there is no callback hook for rollovers. May I suggest raising this as a feature request in the log4j2 issue tracker?

目前没有翻转的回调钩子。我可以建议将其作为 log4j2 问题跟踪器中的功能请求提出吗?

回答by emanciperingsivraren

New solution available

新的解决方案可用

Some time has passed since this question was asked, and now, when I wanted to do the same I have found out that it is possible to solve without messing with factories, but it was not easy to find. The log4j2 team have made it possible to do this by configuration. I hope my post will be of use and save time for someone else.

自从问这个问题以来已经过去了一段时间,现在,当我想做同样的事情时,我发现可以在不弄乱工厂的情况下解决,但是不容易找到。log4j2 团队通过配置使之成为可能。我希望我的帖子对其他人有用并节省时间。

They have hidden this feature in the PatternLayout element. Not where I first looked, but why complain when it works?

他们在 PatternLayout 元素中隐藏了这个功能。不是我第一次看到的地方,但为什么在它有效时抱怨?

This is my configuration(pay attention to header and footer, and the properties they use):

这是我的配置(注意页眉和页脚,以及它们使用的属性):

<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
    <Properties>
        <Property name="log-path">${sys:catalina.home}/logs/my-app</Property>
        <Property name="archive">${log-path}/archive</Property>
        <Property name="defaultPattern">[%d] [%-5p] [%t] %C{5} - %m%n</Property>
        <Property name="defaultRollOverMax">450</Property>
        <Property name="fileHeader">[%d] Start of log \n========================================================================\n
Will be archived in ${archive}\n\n</Property>
        <Property name="fileFooter">\n========================================================================\n[%d] End of log</Property>
    </Properties>
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout pattern="%highlight{[%d] [%-5p] [%t] %C{3} (%F:%L) - %m%n}" charset="UTF-8"/>
        </Console>

        <RollingFile name="Root"
                     fileName="${log-path}/root.log"
                     filePattern="${archive}/root.log.%d{yyyy-MM-dd}_%i.gz"
                     immediateFlush="true">
            <ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY"/>
            <Policies>
                <OnStartupTriggeringPolicy/>
                <SizeBasedTriggeringPolicy size="10 MB"/>
                <TimeBasedTriggeringPolicy/>
            </Policies>

            <PatternLayout pattern="${defaultPattern}"
                           charset="UTF-8"
                           header="This is the ROOT logger and it should be silent \n - define loggers when you see something in here"
                           footer="Closing"/>
            <DefaultRolloverStrategy max="${defaultRollOverMax}" fileIndex="max"/>
        </RollingFile>

        <RollingFile name="System"
                     fileName="${log-path}/system.log"
                     filePattern="${archive}/system.log.%d{yyyy-MM-dd}_%i.gz"
                     immediateFlush="true">
            <ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY" />
            <Policies>
                <OnStartupTriggeringPolicy/>
                <SizeBasedTriggeringPolicy size="10 MB"/>
                <TimeBasedTriggeringPolicy/>
            </Policies>
            <PatternLayout pattern="${defaultPattern}"
                           charset="UTF-8"
                           header="${fileHeader}"
                           footer="${fileFooter}"/>
            <DefaultRolloverStrategy max="${defaultRollOverMax}" fileIndex="max"/>
        </RollingFile>

        <RollingFile name="Error"
                     fileName="${log-path}/error.log"
                     filePattern="${archive}/error.log.%d{yyyy-MM-dd}_%i.gz"
                     immediateFlush="true">
            <ThresholdFilter level="WARN" onMatch="ACCEPT" onMismatch="DENY"/>
            <Policies>
                <OnStartupTriggeringPolicy/>
                <SizeBasedTriggeringPolicy size="10 MB"/>
                <TimeBasedTriggeringPolicy/>
            </Policies>
            <PatternLayout pattern="${defaultPattern}"
                           charset="UTF-8"
                           header="${fileHeader}"
                           footer="${fileFooter}"/>
            <DefaultRolloverStrategy max="${defaultRollOverMax}" fileIndex="max"/>
        </RollingFile>
    </Appenders>

    <Loggers>
        <Logger name="org.myOrganization.myApplication" additivity="false" level="INFO">
            <appender-ref ref="System"/>
            <appender-ref ref="Console"/>
            <appender-ref ref="Error"/>
        </Logger>

        <Logger name="org.myOrganization.myApplication.peculiarPackage" additivity="false" level="TRACE">
            <appender-ref ref="System"/>
            <appender-ref ref="Console"/>
            <appender-ref ref="Error"/>
        </Logger>

        <Logger name="org.springframework" additivity="false" level="WARN">
            <appender-ref ref="System"/>
            <appender-ref ref="Console"/>
            <appender-ref ref="Error"/>
        </Logger>

        <Logger name="javax" additivity="false" level="WARN">
            <appender-ref ref="System"/>
            <appender-ref ref="Console"/>
            <appender-ref ref="Error"/>
        </Logger>

        <!-- Root logger should be empty -->
        <Root level="all">
            <AppenderRef ref="Root"/>
            <AppenderRef ref="Console"/>
            <!--Make sure all errors are logged to the error log-->
            <appender-ref ref="Error"/>
        </Root>
    </Loggers>
</Configuration>

As you can see I have included a timestamp, and a property, that includes a system property. Log4j2 can show many different kinds of properties, with this you can do a lot of things of what you asked for.

如您所见,我包含了一个时间戳和一个包含系统属性的属性。Log4j2 可以显示许多不同类型的属性,通过它你可以做很多你要求的事情。

The log file looks like this:

日志文件如下所示:

[2016-08-09 17:00:43,924] Start of log 
========================================================================

Will be archived in /home/emanciperingsivraren/program/apache-tomcat-8.0.32/logs/my-app/archive

[2016-08-09 17:00:44,000] [INFO ] [RMI TCP Connection(2)-127.0.0.1]    [snip]

========================================================================
[2016-08-09 17:02:17,871] End of log

Do you need more custom information? - try to put that information in properties, system properties, or something else that log4j2 can read.

您需要更多自定义信息吗?- 尝试将该信息放在属性、系统属性或 log4j2 可以读取的其他内容中。

See Property Substitution in log4j2for details in what kind of properties you can have.

有关您可以拥有的属性类型的详细信息,请参阅log4j2 中的属性替换

Comments about the configuration

关于配置的评论

  • I use properties instead of repeating common settings
  • If something is written in the root logger, then it is unhandled, and a logger should be written to define what we want to do with it
  • Since errors/exceptions can be "hidden" among a lot of other log messages they are also printed there, and can easily be found
  • Messages printed on Console have different colours
  • 我使用属性而不是重复常见的设置
  • 如果在根记录器中写了一些东西,那么它是未处理的,应该编写一个记录器来定义我们想要用它做什么
  • 由于错误/异常可以“隐藏”在许多其他日志消息中,因此它们也打印在那里,并且很容易找到
  • 控制台上打印的消息有不同的颜色