Spring中如何通过自定义用户事件管理日志文件切换?

Posted

技术标签:

【中文标题】Spring中如何通过自定义用户事件管理日志文件切换?【英文标题】:How to manage log file switching through custom user event in Spring? 【发布时间】:2020-04-30 20:43:46 【问题描述】:

假设我们使用 Logback 进行日志记录。

每次发生特定事件(即函数调用)时,都需要更改日志文件的路径。

例如,我们在某处调用函数。

startNewLogSegment("A")

在此事件之后,记录器应开始写入logs/mySegment_A.log 文件。 然后,再次调用:

startNewLogSegment("B")

在此事件之后,记录器应完成对前一个文件的写入并开始写入logs/mySegment_B.log 文件。

假设由startNewLogSegment 更改的状态应该在整个应用程序(所有线程)中可见。


我尝试将这种方法应用于 MDC:

logback.xml

...
<appender name="SIFTING_BY_ID" class="ch.qos.logback.classic.sift.SiftingAppender">
    <discriminator>
        <key>id</key>
        <defaultValue>initial</defaultValue>
    </discriminator>

    <sift>
        <appender name="FULL-$id" class="ch.qos.logback.core.FileAppender">
            <file>logs/mySegment_$id.log</file>
            <append>false</append>
            <encoder >
                <pattern>%ddd-MM-yyyy HH:mm:ss.SSS [%thread] [%-5level] %logger36.%M - %msg%n</pattern>
            </encoder>
        </appender>
    </sift>
</appender>
...

并在自定义事件出现时调用MDC.put("id", "A")

但它的工作方式与我需要的不同。

已知MDC manages contextual information on a per thread basis,所以至少我们需要a control over threads creation 来实现上述目标。

我想知道这种方法是否可以用于 Spring,尤其是 Spring Reactor 执行的异步操作。我没有找到关于将自定义线程池用于内部 Spring 活动的信息。

我希望,有一种更简单的方法可以在不滥用 Spring 内部结构的情况下调整日志记录。

【问题讨论】:

【参考方案1】:

我最终实现了鉴别器 AbstractDiscriminator&lt;ILoggingEvent&gt; 的自定义实现,允许 uasge 全局可见值。

GVC.java

/**
 * Global values context.
 * Allows to sift log files globally independent from a thread calling log operation.
 * <p>
 * Used API analogous to standard @link org.slf4j.MDC.
 */
public final class GVC 

    private static Map<String, String> STORED = new HashMap<>();

    private GVC() 
    

    public static synchronized void put(String key, String value) 
        STORED.put(key, value);
    

    public static synchronized String get(String key) 
        return STORED.get(key);
    

GVCBasedDiscriminator.java

/**
 * Customized analogue of MDCBasedDiscriminator.
 * <p>
 * GVCBasedDiscriminator essentially returns the value mapped to an GVC key.
 * If the value is null, then a default value is returned.
 * <p>
 * Both Key and the DefaultValue are user specified properties.
 */
public class GVCBasedDiscriminator extends AbstractDiscriminator<ILoggingEvent> 

    private String key;
    private String defaultValue;

    public String getDiscriminatingValue(ILoggingEvent event) 
        String value = GVC.get(key);

        if (value == null) 
            return defaultValue;
         else 
            return value;
        
    

    @Override
    public String getKey() 
        return key;
    

    @Override
    public void start() 
        int errors = 0;
        if (OptionHelper.isEmpty(key)) 
            errors++;
            addError("The \"Key\" property must be set");
        
        if (OptionHelper.isEmpty(defaultValue)) 
            errors++;
            addError("The \"DefaultValue\" property must be set");
        
        if (errors == 0) 
            started = true;
        
    

    /**
     * Key for this discriminator instance
     *
     * @param key
     */
    public void setKey(String key) 
        this.key = key;
    

    /**
     * The default GVC value in case the GVC is not set for
     * @link #setKey(String) mdcKey.
     * <p/>
     * <p> For example, if @link #setKey(String) Key is set to the value
     * "someKey", and the MDC is not set for "someKey", then this appender will
     * use the default value, which you can set with the help of this method.
     *
     * @param defaultValue
     */
    public void setDefaultValue(String defaultValue) 
        this.defaultValue = defaultValue;
    

logback.xml

<appender name="TRACES_PER_SESSION_FILE" class="ch.qos.logback.classic.sift.SiftingAppender">
  <!-- here the custom discriminator implementation is applied -->
  <discriminator class="internal.paxport.misc.logging.GVCBasedDiscriminator">             
    <key>id</key>
    <defaultValue>initial</defaultValue>
  </discriminator>

  <sift>
    <appender name="FULL-$id" class="ch.qos.logback.core.FileAppender">
      <file>logs/mySegment_$id.log</file>
      <append>false</append>
      <encoder>
        <pattern>%ddd-MM-yyyy HH:mm:ss.SSS [%thread] [%-5level] %logger36.%M - %msg%n</pattern>
      </encoder>
    </appender>
  </sift>
</appender>

【讨论】:

以上是关于Spring中如何通过自定义用户事件管理日志文件切换?的主要内容,如果未能解决你的问题,请参考以下文章

Spring AOP 实现写事件日志功能

使用Spring Aop自定义注解实现自动记录日志

AOP

Spring Boot的特性: 自定义日志配置

spring AOP自定义注解方式实现日志管理

如何在 logback-spring.xml 中包含自定义 xml?