SLF4J - 根据标签记录到不同的文件

SLF4J - Logging to different files based on a tag

我有一个要求,我需要创建日志文件,以便所有基于标签(最好是文本)的日志都需要记录到相应的日志文件中。

例如,

我有关于苹果、橙子和芒果的日志。

logger.info("Apples: They are red in color");
logger.info("Oranges: They are orange in color");
logger.info("Mangoes: They are yellowish in color");

按照我的要求,第一条记录应该记录到Apples.log,第二条记录到Oranges.log 第三个到 Mangoes.log

应动态创建日志文件。

下图是我的logback.xml文件

    <statusListener class="ch.qos.logback.core.status.NopStatusListener"/>
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <Pattern>
                %d %-5p - %marker%m%n
            </Pattern>
        </encoder>
    </appender>
  <appender name="SIFT" class="ch.qos.logback.classic.sift.SiftingAppender">
    <discriminator>
      <key>fruitName</key>
      <defaultValue>Common_logs</defaultValue>
    </discriminator>
    <sift>
      <appender name="FILE-${instanceName}" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">  
            <fileNamePattern>./Logs/${fruitName}/${instanceName}.log</fileNamePattern>
            <maxHistory>50</maxHistory>
            <cleanHistoryOnStart>false</cleanHistoryOnStart>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
            <maxFileSize>10MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
        <encoder>
            <pattern>%d %-5p - %m%n</pattern>
        </encoder>
        </appender>
    </sift>
  </appender>

  <logger name="AssetInstanceService" level="info" additivity="false">
    <appender-ref ref="CONSOLE"/>
    <appender-ref ref="FILE"/>
    <appender-ref ref="SIFT" />
  </logger>

  <root level="info">
    <appender-ref ref="SIFT" />
    <appender-ref ref="CONSOLE"/>
    <appender-ref ref="FILE"/>
  </root>
</configuration>

下面是我的 LogManager.java 文件

package Data;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;

import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.qos.logback.core.joran.spi.JoranException;

public class LogManager {

    static String configFile = "./logback.xml";

    private static Logger _logger = LoggerFactory.getLogger(LogManager.class);
    private static boolean _isInitialized = false;

    private LogManager() {

    }

    public static Logger Instance() {
        if (_logger == null) {
            _logger = LoggerFactory.getLogger(LogManager.class);
        }
        return _logger;
    }

    public static Logger Instance(String instanceName) {
        if (!_isInitialized) {
            if (!CommonMethods.CheckFileExist(configFile)) {
                configFile = "../logback.xml";
                if (!CommonMethods.CheckFileExist(configFile)) {
                    return null;
                }
            }
            LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
            JoranConfigurator configurator = new JoranConfigurator();
            lc.reset();
            configurator.setContext(lc);
            try {
                configurator.doConfigure(configFile);
                MDC.put("fruitName", instanceName);
            } catch (JoranException e) {
                System.out.println(e);
            }
            _isInitialized = true;
        }
        if (_logger == null) {
            _logger = LoggerFactory.getLogger(LogManager.class);
        }
        return _logger;
    }
}

这里我的日志是在 jar 文件是 运行 时创建的,因为我在我的主要方法中初始化了记录器。

但我的需要是根据日志中的标签或 ID 创建不同名称的不同日志文件。

SLF4J 和 Log4j-API 都提供标记来做你想做的事。在 SLF4J 中,您将创建您的标记:

Marker apples = MarkerFactory.getMarker("Apples");
Marker oranges = MarkerFactory.getMarker("Oranges");
Marker mangos = MarkerFactory.getMarker("Mangos");

此外,标记可以有一个 parent 所以你可以这样做:

Marker fruit = MarkerFactory.getMarker("Fruit");
Marker apples = MarkerFactory.getMarker("Apples");
apples.add(fruit);
Marker oranges = MarkerFactory.getMarker("Oranges");
apples.add(fruit);
Marker mangos = MarkerFactory.getMarker("Mangos");
apples.add(fruit);

在配置中,您可以检查特定的标记,或者如果您想检查所有属于水果的标记,您将检查该标记。

然后您在应用程序中使用标记作为:

logger.info(apples, "They are red in color");
logger.info(oranges, "They are orange in color");
logger.info(mangoes, "They are yellowish in color");

最后,在您的配置中,您可以使用涡轮过滤器来做:

  <turboFilter class="ch.qos.logback.classic.turbo.MarkerFilter">
    <Marker>Apples</Marker>
    <OnMatch>NEUTRAL</OnMatch>
    <OnMismatch>DENY</OnMismatch>
  </turboFilter>

在全局级别进行过滤,或者您可以使用评估器过滤器之一在 Appender 上进行过滤。如果您编写自定义鉴别器,您还可以使用 SiftingAppender 动态创建 Appenders。

使用 Log4j API 事情会更容易一些。创建不带 parent 的标记:

Marker apples = MarkerManager.getMarker("Apples");
Marker oranges = MarkerManager.getMarker("Oranges");
Marker mangos = MarkerManager.getMarker("Mangos");

或 parent:

Marker fruit = MarkerManager.getMarker("Fruit");
Marker apples = MarkerManager.getMarker("Apples").setParents(fruit);
Marker oranges = MarkerManager.getMarker("Oranges").setParents(fruit);
Marker mangos = MarkerManager.getMarker("Mangos").setParents(fruit);

在 Log4j 中使用它们的代码完全相同:

logger.info(apples, "They are red in color");
logger.info(oranges, "They are orange in color");
logger.info(mangoes, "They are yellowish in color");

最大的区别在于配置。 Log4j 只有一种 Filter,它可以在全局级别(如 turbo 过滤器)或 Logger、Appender 引用或 Appender 上使用。在你的情况下你会想要使用 MarkerFilter:

<MarkerFilter marker="Apples" onMatch="NEUTRAL" onMismatch="DENY"/>

并将其添加到每个 Appender 引用或每个 Appender。

与 SiftingAppender 类似,Log4j 还提供了一个 RoutingAppender. It uses Lookups to determine how to perform the routing. Out of the box Log4j doesn't provide one to retrieve data from the log event but it would be simple to write one or add one to Log4j,或者您可以使用脚本从事件中检索标记。

您应该知道使用 MarkerFilters 会产生一些开销,尽管并不多。这是我的 MacBook Pro 上 运行 4 线程时 log4j-perf 模块的基准测试。即使在最坏的情况下,Logback 正在针对包含 child 标记的事件检查 parent 标记,每次比较平均仍然只需要 17 纳秒。

Benchmark                                  Mode  Cnt   Score   Error  Units
MarkerFilterBenchmark.baseline             avgt   10   2.412 ± 0.088  ns/op
MarkerFilterBenchmark.log4jParentMarker    avgt   10   8.337 ± 0.186  ns/op
MarkerFilterBenchmark.log4jSimpleMarker    avgt   10   8.043 ± 0.145  ns/op
MarkerFilterBenchmark.log4jTooFine         avgt   10   2.825 ± 0.281  ns/op
MarkerFilterBenchmark.logbackParentMarker  avgt   10  17.865 ± 0.533  ns/op
MarkerFilterBenchmark.logbackSimpleMarker  avgt   10  10.471 ± 0.089  ns/op
MarkerFilterBenchmark.logbackTooFine       avgt   10   4.255 ± 0.014  ns/op

最后的想法。 Ceki Gulcu 在创建 SLF4J 时发明了标记的概念,他值得赞扬,因为这是一个绝妙的想法。

动态创建 RollingFileAppenders 并向每个 appender 添加过滤器。

package zinit;

import org.slf4j.LoggerFactory;
import org.slf4j.Marker;
import org.slf4j.MarkerFactory;

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.classic.spi.LoggingEvent;
import ch.qos.logback.core.filter.Filter;
import ch.qos.logback.core.rolling.RollingFileAppender;
import ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP;
import ch.qos.logback.core.rolling.TimeBasedRollingPolicy;
import ch.qos.logback.core.spi.FilterReply;

public class main {

    private static Logger logger = (Logger) LoggerFactory.getLogger(main.class);

    public static void main(String[] args) {

        LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
        String[] fruitList = { "Apple", "Orange", "Mango" };
        Marker apple = null;
        Marker orange = null;
        Marker mango = null;

        for (String instanceName : fruitList) {

            String fileName = "./Logs/" + instanceName + "/" + instanceName + "_%d{yyyy-MM-dd}-%i.log";
            apple = MarkerFactory.getMarker("Apple");
            orange = MarkerFactory.getMarker("Orange");
            mango = MarkerFactory.getMarker("Mango");
            logger = RollingFile(lc, fileName, instanceName);
        }

        for (int i = 0; i < 10000; i++) {

            logger.info(apple, "Apple is red");
            logger.info(apple, "An apple a day keeps the doctor away");
            logger.info(orange, "Oranges are orange");
            logger.info(orange, "Orange is a fruit and a color");
            logger.info(mango, "Mango is yellowish");
            logger.info(mango, "Color of mango is very much variying");
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
            }
        }
    }

    @SuppressWarnings({ "rawtypes", "unchecked" })
    private static Logger RollingFile(LoggerContext loggerContext, String filePath, String instanceName) {

        RollingFileAppender<ILoggingEvent> rollingFileAppender = new RollingFileAppender<>();
        rollingFileAppender.setName("ROLLINGFILE-" + instanceName);
        rollingFileAppender.setContext(loggerContext);

        TimeBasedRollingPolicy<ILoggingEvent> timeBasedRollingPolicy = new TimeBasedRollingPolicy<>();
        timeBasedRollingPolicy.setContext(loggerContext);
        timeBasedRollingPolicy.setMaxHistory(3);
        timeBasedRollingPolicy.setCleanHistoryOnStart(false);
        timeBasedRollingPolicy.setFileNamePattern(filePath);
        timeBasedRollingPolicy.setParent(rollingFileAppender);
        timeBasedRollingPolicy.start();

        SizeAndTimeBasedFNATP<ILoggingEvent> sizeAndTimeBasedFNATP = new SizeAndTimeBasedFNATP<>();
        sizeAndTimeBasedFNATP.setContext(loggerContext);
        sizeAndTimeBasedFNATP.setMaxFileSize("10KB");
        sizeAndTimeBasedFNATP.setTimeBasedRollingPolicy(timeBasedRollingPolicy);
        sizeAndTimeBasedFNATP.start();

        timeBasedRollingPolicy.setTimeBasedFileNamingAndTriggeringPolicy(sizeAndTimeBasedFNATP);
        timeBasedRollingPolicy.start();

        rollingFileAppender.setRollingPolicy(timeBasedRollingPolicy);

        PatternLayoutEncoder patternLayoutEncoder = new PatternLayoutEncoder();
        patternLayoutEncoder.setContext(loggerContext);
        patternLayoutEncoder.setPattern("%d %-5p - %m%n");
        patternLayoutEncoder.start();
        Filter filter = new Filter() {

            @Override
            public FilterReply decide(Object obj) {
                if (!isStarted()) {
                    return FilterReply.NEUTRAL;
                }
                LoggingEvent event = (LoggingEvent) obj;
                if (event.getMarker().getName().equals(instanceName)) {
                    return FilterReply.NEUTRAL;
                } else {
                    return FilterReply.DENY;
                }
            }

        };
        filter.start();
        rollingFileAppender.addFilter(filter);
        rollingFileAppender.setEncoder(patternLayoutEncoder);

        rollingFileAppender.start();

        logger.addAppender(rollingFileAppender);
        logger.setLevel(Level.DEBUG);
        logger.setAdditive(false);
        return logger;
    }

}