log4j2自定义appender插件源码、配置及采坑说明

本篇为扩展appender标签,如果需要扩展filer、marker等其他的log4j2提供的扩展方式,可以查看相关的文档:
中文文档:https://www.docs4dev.com/docs/zh/log4j2/2.x/all/javadoc.html
英文文档:http://logging.apache.org/log4j/2.x/
api:http://logging.apache.org/log4j/2.x/log4j-api/apidocs/index.html
通过本篇,你可以看到我在扩展插件时所遇到的问题以及解决方案,根据ApplicationContext获取bean的工具类源码等。

不再是千篇一律的抄袭,真正将解决自己所遇到的问题的代码分享出来。

目录

自定义Appender插件类

关键代码

代码说明:

1.引入log4j2的插件注解,并声明相关属性的值

2.如何自定义变量

3.如何引入原生的插件,如rollingfile和console

4.为何本地自测有效,一到服务器就不生效?

5.我的bean注入是null?明明配置都对

log4j2.xml配置


 

自定义Appender插件类

关键代码

package com.xxx.util;
import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.*;
import org.apache.logging.log4j.core.appender.*;
import org.apache.logging.log4j.core.config.AppenderControl;
import org.apache.logging.log4j.core.config.AppenderRef;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.Property;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginElement;
import org.apache.logging.log4j.core.config.plugins.PluginFactory;
import org.apache.logging.log4j.core.layout.PatternLayout;
import java.io.Serializable;

@Slf4j
@Plugin(name = "MyAppender", category = "Core", elementType = "appender", printObject = true)
public class MyAppender extends AbstractAppender {
    private  Configuration configuration;
    private Property[] properties;

    protected MyAppender(String name, Filter filter, Layout layout,
                                  boolean ignoreExceptions, Property[] properties, AppenderRef[] appenderRefs) {
        super(name, filter, layout, ignoreExceptions, properties);
        this.appenderRefs = appenderRefs;
        this.properties = properties;
    }

    @Override
    public void append(LogEvent event) {
        try {
            // 可以增加处理业务日志逻辑
            try {
                callAppender(event);
            } catch (Exception e) {
                log.error("自定义异常信息", e);
            }
            try {
                // 可以根据日志级别处理日志-如写入数据库
                if (event.getLevel().intLevel() <= Level.ERROR.intLevel()) {}
            } catch (Exception e) {
                log.error("自定义异常信息", e);
            }
        } catch (Exception ex) {
            if (!ignoreExceptions()) {
                throw new AppenderLoggingException(ex);
            }
        }
    }

    @PluginFactory
    public static MyAppender createAppender(@PluginAttribute("name") String name,
                                            @PluginElement("Filter") Filter filter,
                                            @PluginElement("Layout") Layout layout,
                                            @PluginAttribute("ignoreExceptions") boolean ignoreExceptions,
                                            @PluginElement("property") Property[] properties,
                                            @PluginElement("AppenderRef") AppenderRef[] appenderRefs) {
        if (name == null) {
            log.error("No name provided for MyAppenderImpl");
            return null;
        }
        if (layout == null) {
            layout = PatternLayout.createDefaultLayout();
        }
        return new MyAppender(name, filter, layout, ignoreExceptions, properties, appenderRefs);
    }

    private void callAppender(LogEvent event) {
        if (null == configuration) {
            refreshLogcontext();
        }
        for (AppenderRef appenderRef : appenderRefs) {
            if (null == configuration.getAppender(appenderRef.getRef())) {
                log.info("appenderRef null {}", appenderRef.getRef());
                continue;
            }
            recursion(event, appenderRef.getRef());
        }
    }

    private void refreshLogcontext() {
        LoggerContext loggerContext = (LoggerContext) LogManager.getContext(false);
        this.configuration = loggerContext.getConfiguration();
    }

    private void recursion(LogEvent event, String ref) {
        if (configuration.getAppender(ref) instanceof RollingFileAppender) {
            Appender appender = configuration.getAppender(ref);
            AppenderControl control = new AppenderControl(appender, null, null);
            control.callAppender(event);
        } else if (configuration.getAppender(ref) instanceof AsyncAppender) {
            AsyncAppender asyncAppender = configuration.getAppender(ref);
            if (null != asyncAppender.getAppenderRefStrings()) {
                for (int j = 0; j < asyncAppender.getAppenderRefStrings().length; j++) {
                    // 递归
                    recursion(event, asyncAppender.getAppenderRefStrings()[j]);
                }
            }
        } else if (configuration.getAppender(ref) instanceof ConsoleAppender) {
            Appender appender = configuration.getAppender(ref);
            AppenderControl control = new AppenderControl(appender, null, null);
            control.callAppender(event);
        }
    }
}

代码说明:

1.引入log4j2的插件注解,并声明相关属性的值

@Plugin(name = "MyAppender", category = "Core", elementType = "appender", printObject = true)

  • name:插件的名称。请注意,此名称不区分大小写
  • category:用于放置插件的类别。类别名称区分大小写
  • elementType:此插件所属元素的相应类别的名称,当前扩展所属元素的类别是appender。
  • printObject:指示插件类是否实现Object.toString()方法,消息中使用。

2.如何自定义变量

createAppender方法,用于log4j2在扫描到插件之后根据配置文件中的配置穿件自定义的插件对象。

  • @PluginAttribute:是指插件的属性,如@PluginAttribute("name") String name

       会取标签内name的值

  • @PluginElement:是指插件的子元素,如@PluginElement("AppenderRef") AppenderRef[] appenderRefs

     
       
       
   

     会获取标签下AppenderRef 元素的值,如果是多个AppenderRef 子元素,将会获取都一个数组
可以根据业务需要自定义元素或者属性。


3.如何引入原生的插件,如rollingfile和console

本篇中实现了自定义的标签引入log4j2原生的插件rollingfile和console,并控制原生的rollingfile和console向文件写入和控制台打印。
这一点区分于网上大部分的给出的代码示例,记住生产业务系统不能使用System.out.print,而且要注重高效,最好能用到log4j2自己实现的异步机制来完成我们想要的效果。
其实引入原生的插件rollingfile和console,并使它们发挥作用很简单,只需要call一下就可以,关键代码:

Appender appender = configuration.getAppender(ref);
AppenderControl control = new AppenderControl(appender, null, null);
control.callAppender(event);

4.为何本地自测有效,一到服务器就不生效?

本功能在实现的时候遇到一个难以预料的问题,就是本地怎么测试都好用,但是一部署到服务器就不好用,查过很多资料,不得不说国内的资料千篇一律的居多,参考意义不大。无奈自己搭建了xx谷歌了一下,发现如果你的自定义插件失效,可以从三个方面排查

  • 配置文件configuration的packages属性没有设置或者没有设置成该插件所在的包,注意可以设置多个,用逗号隔开。

  • log4j2的插件是在编译期生成的缓存,而不是启动期间,所以,我们打包的插件需要讲生成的缓存插件.dat打包到jar包中才能生效。关于打包的问题可以自行百度一下,这个问题范围就小多了,一般来说,maven插件都会将插件缓存打包到jar中。用解压缩文件就可以打开jar并查看jar中的文件,该插件缓存在xx.jar\META-INF\org\apache\logging\log4j\core\config\plugins路径下。可以打开文件看到自定义的插件信息:

log4j2自定义appender插件源码、配置及采坑说明_第1张图片

  • 如果上两个问题都排查过了,而且你也和我一样在代码中用到了logcontext,那么可能的原因是本地的logcontext和服务器上的logcontext加载的顺序不一致,导致服务器上的插件类在加载的时候所获取到的logcontext中的信息是最基础的,只有一个DefaultConsole插件。我的解决思路是调用的时候再去取一次logcontext。

5.我的bean注入是null?明明配置都对

再说一个上下文的问题,log4j2在dao层service层初始化结束之前就已经初始化了,如果采用@Resource这种依赖注入的方式构建bean是行不通的,获取到的只能是null,但是ApplicationContext已经加载(已经扫描哪些带有注解的类),我们可以通过ApplicationContext手动获取bean。
SpringBeanUtil源码:

package com.xxx.util;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
@Component
@Configuration
public class SpringBeanUtils implements ApplicationContextAware {
    private static ApplicationContext applicationContext;
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        if (SpringBeanUtils.applicationContext == null) {
            SpringBeanUtils.applicationContext = applicationContext;
        }
    }
    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }
    public static Object getBean(String name){
        return getApplicationContext().getBean(name);
    }
    public static  T getBean(Class clazz){
        return getApplicationContext().getBean(clazz);
    }
    public static  T getBean(String name, Class clazz){
        return getApplicationContext().getBean(name, clazz);
    }
    public static  T getBean(Class clazz, Class targetClazz){
        return (T)getBean(clazz);
    }
}


用法:
注意第一个入参如果没有指定,默认是小写,这个应该都懂吧,spring的东西了
SpringBeanUtils.getBean("testAppenderDao", TestAppenderDao.class);

log4j2.xml配置



    
        
        
        %d{yyyy-MM-dd HH:mm:ss.SSS} |-%-5level [%thread] %c [%L] -| %msg%n
        his/app-%d{MM-dd-yyyy}-%i.log
        
    
    
        
            
        
        
        
            
                
            
            
            
            
            
                
                
            
            
            
                
                
                    
                    
                
                
                    
                    
                
            
        
        
            
        
        
            
        
        
            
            
            
        
    

    
        
            
        
        
            
        
    


配置说明:

  • 定义了两个appender:CONSOLE、MqLog
  • 异步:AsyncCONSOLE、AsyncMqLog
  • 引入到我们自己的标签中:

       
            
            
            
        

  • 引入到异步Loggers标签中,只对com.xxx.mq这里路径下的日志起作用:

       
            
        


我们可以使用自己扩展的标签引入不同的console和rollingfile等标签,可以配置不同的属性和元素,也可以监控不同的项目路径以达到业务需要的效果。

你可能感兴趣的:(日志,log4j2,log4j,java,spring,boot,spring)