【问题标题】:How to customize log4j2 RollingFileAppender?如何自定义 log4j2 RollingFileAppender?
【发布时间】:2014-12-03 14:41:42
【问题描述】:

我们使用 log4j 1.2.x 登录我们的产品,并希望在不久的将来迁移到 log4j 2.x。我们实现的功能之一是在生成的每个新的翻转日志文件上记录系统信息和其他重要参数。我们在 log4j 1.2.x 中实现的方式是我们扩展了 log4j 的 RollingFileAppender 类并覆盖了 rollOver() 方法,下面是实现的部分 sn-p

@Override
public void rollOver() {

    super.rollOver(); //We are not modifying it's default functionality but as soon as rollOver happens we apply our logic 

    // 
    // Logic to log required system properties and important parameters.
    //

}

现在我们想迁移到 log4j2,我们正在寻找一种新的解决方案来实现相同的功能。但是当我看到 log4j2 的源代码时,它与旧的源代码有很大不同。 RollingFileAppender 类不包含 rollover() 方法,因为它已移至 RollingManagerhelper 并且也已设置为 private

开发一个完整的新包并从 log4j2 扩展/实现一些抽象/帮助类是我们可能的解决方案之一,但这需要大量的编码/复制,因为我们不修改 RollingFileAppender 所做的,而是我们只需要对其进行小的扩展。有一个简单的解决方案吗?

更新

我根据答案中的建议创建了一个自定义查找,以下是我创建它的方式;

@Plugin(name = "property", category = StrLookup.CATEGORY)
public class CustomLookup extends AbstractLookup {

private static AtomicLong aLong = new AtomicLong(0);

@Override
public String lookup(LogEvent event, String key) {

    if (aLong.getAndIncrement() == 0) {
        return "this was first call";
    }
    if (key.equalsIgnoreCase("customKey")) {
        return getCustomHeader();
    } else {
        return "non existing key";
    }
}

private static String getCustomHeader() {

    // Implementation of custom header
    return "custom header string";

}}

但这并没有像上面提到的那样工作;这总是在标题中打印this was first call。我还尝试将断点放在第一个 if 条件上,我注意到它只被调用一次。所以我担心的是 customLookup 类只有在 log4j2 从 xml 配置初始化它的属性时才会在启动时被初始化。我不知道我还能如何实现这个自定义查找类。

更新 2

在上面的实现之后,我尝试了一些不同的方式,如下所示;

private static AtomicLong aLong = new AtomicLong(0);

@Override
public String lookup(LogEvent event, String key) {
    return getCustomHeader(key);
}

private static String getCustomHeader(final String key) {

    if (aLong.getAndIncrement() == 0) {
        return "this was first call";
    }
    if (key.equalsIgnoreCase("customKey")) {
        // Implementation for customKey
        return "This is custom header";
    } else {
        return "non existing key";
    }
}

但这也做同样的事情。 log4j2 在从其 xml 配置文件初始化时创建标头,然后使用内存中的标头。被覆盖的lookup() 方法的return 值不能动态更改,因为它仅在初始化期间被调用。任何更多的帮助将不胜感激。

【问题讨论】:

  • 如果要使用运行时变量替换,请在 log4j2 属性文件中指定变量键时使用双 $$。

标签: java logging log4j log4j2


【解决方案1】:

使用内置查找的替代方法是创建自定义查找。这可以使用 log4j2 插件在几行代码中完成。然后,您的自定义查找会提供您希望在每次翻转时在文件标题中显示的确切值。

插件代码如下所示:

package com.mycompany;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.lookup.AbstractLookup;
import org.apache.logging.log4j.core.lookup.StrLookup;

/**
 * Looks up keys from a class SomeClass which has access to all
 * information you want to provide in the log file header at rollover.
 */
@Plugin(name = "setu", category = StrLookup.CATEGORY)
public class SetuLookup extends AbstractLookup {

    /**
     * Looks up the value of the specified key by invoking a
     * static method on SomeClass.
     *
     * @param event The current LogEvent (ignored by this StrLookup).
     * @param key  the key to be looked up, may be null
     * @return The value of the specified key.
     */
    @Override
    public String lookup(final LogEvent event, final String key) {
        return com.mycompany.SomeClass.getValue(key);
    }
}

然后,在您的配置中,您可以使用模式布局的标题在每次翻转时输出:

<RollingFile name="RollingFile" fileName="logs/app.log"
             filePattern="logs/$${date:yyyy-MM}/app-%d{MM-dd-yyyy}.log.gz">

  <!-- use custom lookups to access arbitrary internal system info -->
  <PatternLayout header="${setu:key1} ${setu:key2}">
    <Pattern>%d %m%n</Pattern>
  </PatternLayout>
  <Policies>
    <TimeBasedTriggeringPolicy />
  </Policies>
</RollingFile>

log4j2 手册有关于构建/部署custom plugins 的详细信息。小结:

最简单的方法是使用 Maven 构建您的 jar;这将导致 log4j2 注释处理器在 jar 中生成一个二进制索引文件,以便 log4j2 可以快速找到您的插件。

另一种方法是在 log4j2.xml 配置的 packages 属性中指定插件类的包名称:

<Configuration status="warn" packages="com.mycompany">
  ...

更新:请注意,在您的查找实现中,您可以根据需要获得尽可能多的创意。例如:

package com.mycompany;

public class SomeClass {
    private static AtomicLong count = new AtomicLong(0);

    public static String getValue(final String key) {
        if (count.getAndIncrement() == 0) { // is this the first call?
            return ""; // don't output a value at system startup
        }
        if ("FULL".equals(key)) {
            // returns info to shown on rollover, nicely formatted
            return fullyFormattedHeader();
        }
        return singleValue(key);
    }
    ....
}

【讨论】:

  • 当然可以随意使用与setu 不同的插件名称。 :-)
  • 看起来很有希望。会试一试
  • 我试了一下,但有两个问题,一个是标题属性中的键需要完全格式化才能记录,例如。如果在标题中使用多个键,则无法在不同的行中分隔。其次是主要问题,要求是在新的翻转文件开始时打印系统属性,而不是在程序执行开始时打印。在每次执行开始时和每次翻转时都会记录标头,这根本没有意义。
  • 我更新了我的答案,提出了如何解决您提出的问题的建议。至于替代解决方案,正如您在问题中指出的那样,尝试将RollingFileAppender 子类化将是一种非常麻烦的方法。从长远来看,在 Log4j2 Jira 问题跟踪器上提出更可扩展/可定制的翻转机制的功能请求可能是一个好主意。现在,我确实相信在标题中使用查找是最简单的解决方案。应该可以解决SomeClass.getValue() 实现中的任何问题。
  • 鉴于这是最好的解决方案(但还不够),我会将赏金奖励给您,但请保持问题的开放性。很遗憾,如此简单的功能在非常流行的库中不可用。不过谢谢你的帮助..
【解决方案2】:

这可以通过配置来完成。您可以使用图案布局的标题来输出信息。这将包含在每次翻转时。

<RollingFile name="RollingFile" fileName="logs/app.log"
             filePattern="logs/$${date:yyyy-MM}/app-%d{MM-dd-yyyy}.log.gz">

  <!-- use built-in lookups for system info or system properties -->
  <PatternLayout header="${java:runtime} - ${java:vm} - ${java:os}">
    <Pattern>%d %m%n</Pattern>
  </PatternLayout>
  <Policies>
    <TimeBasedTriggeringPolicy />
  </Policies>
</RollingFile>

【讨论】:

  • 感谢@Remko 的回复,但有一个问题。我们拥有的产品非常庞大,我们在运行时设置了多个系统属性,基本上它是一个在服务器上连续运行并与后端的 web 服务、gui 工具和 db 交互的引擎。使用这种方法,我们需要在标题中精确地写入所有属性名称。有什么方法可以使用某种循环将所有系统属性包含在标题中。
  • 您可以在标头中配置单个系统属性,并通过附加您感兴趣的所有其他系统属性的值以编程方式更新该属性的值,这可行吗?
  • 感谢您的建议;我将不得不试一试,但这意味着每当在我的代码中设置任何系统属性时,我都必须将其添加到此系统属性中。听起来它会起作用,但我认为这将被管理层视为解决方法,而不是实际的解决方案。无论如何感谢您的建议
猜你喜欢
  • 2021-02-11
  • 2014-01-25
  • 1970-01-01
  • 2020-09-23
  • 2017-10-15
  • 2014-08-03
  • 2015-07-26
  • 1970-01-01
相关资源
最近更新 更多