本篇为扩展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<? extends Serializable> layout,boolean ignoreExceptions, Property[] properties, AppenderRef[] appenderRefs) {super(name, filter, layout, ignoreExceptions, properties);this.appenderRefs = appenderRefs;this.properties = properties;}@Overridepublic 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);}}}@PluginFactorypublic static MyAppender createAppender(@PluginAttribute("name") String name,@PluginElement("Filter") Filter filter,@PluginElement("Layout") Layout<? extends Serializable> 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

<MyAppender name="MyAppenderTest"></Realtimeval>会取标签内name的值

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

<MyAppender name="MyAppenderTest">
        <AppenderRef ref="AsyncMqLog"/>
        <AppenderRef ref="AsyncCONSOLE"/>
    </MyAppender>

会获取标签下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属性没有设置或者没有设置成该插件所在的包,注意可以设置多个,用逗号隔开。<configuration monitorInterval="5" status="WARN" name="${app_name}"    packages="com.xxx.log4j2.pattern,com.xxx.util">

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

  • 如果上两个问题都排查过了,而且你也和我一样在代码中用到了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;@Overridepublic 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> T getBean(Class<T> clazz){return getApplicationContext().getBean(clazz);}public static <T> T getBean(String name, Class<T> clazz){return getApplicationContext().getBean(name, clazz);}public static <T> T getBean(Class<? extends T> clazz, Class<T> targetClazz){return (T)getBean(clazz);}
}

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

log4j2.xml配置

<?xml version="1.0" encoding="utf-8"?>
<configuration monitorInterval="5" status="WARN" name="${app_name}"packages="com.xxx.log4j2.pattern,com.xxx.util"><properties><property name="LOG_HOME" value="log" /><property name="LOG_LEVEL" value="info" /><property name="LOG_PATTERN">%d{yyyy-MM-dd HH:mm:ss.SSS} |-%-5level [%thread] %c [%L] -| %msg%n</property><property name="FILE_PATTERN">his/app-%d{MM-dd-yyyy}-%i.log</property><property name="FILE_DEADLINE" value="7d" /></properties><appenders><Console name="CONSOLE" target="system_out"><PatternLayout pattern="${LOG_PATTERN}" /></Console><!-- 滚动文件生成 fileName日志文件名称,filePattern压缩文件路径 --><RollingFile name="MqLog" fileName="${LOG_HOME}/logs/rabbitmq/rabbitmq.log"<!-- 过滤日志级别 info及以上 --><Filters><ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY" /></Filters><!-- 指定日志输出格式 --><PatternLayout pattern="${LOG_PATTERN}" /><!-- 新文件生成策略,每100M生成一个新文件 --><Policies><TimeBasedTriggeringPolicy /><SizeBasedTriggeringPolicy size="100 MB" /></Policies><!-- 默认过度策略,不设置,则默认为最多同一文件夹下7个文件 大于此值会删除旧的日志文件 --><DefaultRolloverStrategy><!-- 文件删除策略,basePath扫描路径,maxDepth目录的最大级别数,IfFileName文件名称,IfLastModified指定持续时间(7天前)--><Delete basePath="${LOG_HOME}/logs/rabbitmq" maxDepth="2"><IfFileName glob="*.log" /><IfLastModified age="${FILE_DEADLINE}" /></Delete><Delete basePath="${LOG_HOME}/logs/rabbitmq" maxDepth="2"><IfFileName glob="*.log" /><IfAccumulatedFileSize exceeds="${FILE_MAX_SIZE}" /></Delete></DefaultRolloverStrategy></RollingFile><Async name="AsyncCONSOLE"><AppenderRef ref="CONSOLE"/></Async><Async name="AsyncMqLog"><AppenderRef ref="MqLog"/></Async><MyAppender name="MyAppenderTest"><AppenderRef ref="AsyncMqLog"/><AppenderRef ref="AsyncCONSOLE"/><property name="logType" value="1"/></MyAppender ></appenders><loggers><AsyncLogger name="com.xxx.mq" level="info" additivity="false"><AppenderRef ref="MyAppenderTest" /></AsyncLogger><root level="${LOG_LEVEL}"><AppenderRef ref="AsyncCONSOLE" /></root></loggers>
</configuration>

配置说明:

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

<MyAppender name="MyAppenderTest">
            <AppenderRef ref="AsyncMqLog"/>
            <AppenderRef ref="AsyncCONSOLE"/>
            <property name="logType" value="1"/>
        </MyAppender >

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

<AsyncLogger name="com.xxx.mq" level="info" additivity="false">
            <AppenderRef ref="MyAppenderTest" />
        </AsyncLogger>

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

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

  1. 【Flutter】Flutter 拍照示例 ( Flutter 插件配置 | Flutter 插件源码示例 | iOS 应用配置 | Android 应用配置 )

    文章目录 一.Flutter 插件配置 二.Flutter 插件源码示例 三.iOS 应用配置 四.Android 应用配置 五.相关资源 一.Flutter 插件配置 Flutter 拍照示例中 , ...

  2. 基于webpack修改插件源码,使用自定义文件替换node_modules里面的源码文件

    基于webpack修改插件源码,使用自定义文件替换node_modules里面的源码文件 需求:插件不满足要求,需要修改源码,但又想永远保留自己修改的这份,不想被重新下载的覆盖 方法:在运行时执行你替 ...

  3. jQuery图片批量上传插件源码,支持批量上传、预览、删除、放大,可配置上传数量、上传大小、追加方式,含详细使用文档

    jQuery图片批量上传插件源码,支持批量上传.预览.删除.放大,可配置上传数量.上传大小.追加方式,含详细使用文档 程序包内含使用Demo 完整程序源代码:jQuery图片批量上传插件源码 上传前 ...

  4. 使用加密锁加密Unity工程插件源码

    使用加密锁加密Unity工程插件源码 最近在unity3D中开发了一款插件,需要卖给客户,但是公司需要隐藏插件的源码,而且保证客户只有指定的电脑才能使用该插件开发!针对这个问题,分为两个步骤:1.隐藏 ...

  5. QML 地图修改插件源码(三),Map在Plugin中设置加载地图类型

    常用的地图种类分为交通图,地形图,卫星图等等,在QML的Map(以OSM地图为例)中提供activeMapType属性用于读取当前显示的地图类型(注意:该属性为只读属性,不能用于赋值),QML中地图的 ...

  6. log4j日志改json格式自定义输出内容源码及说明

    log4j日志改json格式自定义输出内容源码及说明 最近项目需要接入日志管理平台,要求需要将项目log4j日志格式改为json,没系研究过log4j的我一时间还真被难住了,功夫不负有心人最后还是被我 ...

  7. es 修改ik和同义词插件源码连接mysql实现字典值同义词热更新

    问题描述: 上周运营反馈商城搜索词搜不到 排查发现es ik分词器的ik_smart对搜索词的分词结果不是ik_max_word对索引文档字段值分词结果的子集 即细粒度分词结果不完全包含粗粒度分词结果 ...

  8. QML 地图修改插件源码(四),Map根据目录作为索引加载地图瓦片

    QML中的地图(以OSM为例)在使用过程中会发现当地图层数很多时,特别是如果使用离线地图且地图层级较多时地图会变得很卡(在线地图加载的层级数多且不清除缓存时也会卡),原因在于QML地图插件对地图瓦片的 ...

  9. 开源Vue表格组件,表格插件源码

    开源Vue表格组件,表格插件源码 前言: 关于html里面原生的table,通常满足不了程序员的要求.所以开发了一款表格插件,其功 能有: 1 导入json格式数据后,自动填充表格.表格长宽自适应.排 ...

最新文章

  1. EEGNet: 神经网络应用于脑电信号
  2. [UE4]网游中角色Pawn的移动位置同步以及RTS多角色同时移动的解决方案
  3. ubuntu 设置root启动
  4. django关闭浏览器,怎样清除 cookies 和 session
  5. PHP遍历数组的几种方法
  6. 学习android 画板源代码,Android实现画画板案例
  7. [原创]css设置禁止中文换行
  8. 一个用户在同一时间只能登录一次
  9. 戴尔软件部门第一弹 收购备份公司AppAssure
  10. mysql日志文件的类型和作用_Mysql日志文件和日志类型介绍
  11. eclipse adt离线安装
  12. mysql导入mdb_mysql导入数据库.mdb
  13. mysql8忘记密码后重置密码
  14. 电力设备巡检管理系统
  15. 微慕WordPress小程序专业版v2.0
  16. 把图标变成圆形的html_css3 图片圆形显示 如何CSS将正方形图片显示为圆形图片布局...
  17. 解释下ArrayList集合为啥允许值为null
  18. 计算机显示器分辨率,现在电脑的主流显示器的分辨率一般是多大?
  19. word文档自动保存方法
  20. 学会“狼”的思维(一)

热门文章

  1. 紫光集团发布公有云战略 掘金企业级业务
  2. 到T-SQL DML 三级的阶梯:在SQL server中实现关系模型
  3. 如何实现从抖音内跳转到微信关注页面?
  4. Android App 防止抓包
  5. Microservices 微服务 [译]
  6. 关于Android读取SD卡存储的动态申请
  7. 【EI/Scopus双检索】国内多所高校协办,计算机多主题征稿,AHPCAI 2021诚邀您投稿参会!...
  8. 树莓派RaspberryPiB+Raspbian-jessie制作只读系统的python3脚本
  9. C++中四舍五入和保留整数
  10. 一名大专程序员的前端转型之路