SpringBoot | 第二十五章:日志管理之自定义Appender
前言
前面两章节我们介绍了一些日志框架的常见配置及使用实践。一般上,在开发过程中,像
log4j2、logback日志框架都提供了很多Appender,基本上可以满足大部分的业务需求了。但在一些特殊需求或者需要将日志进行集中管理(集群部署时,日志是分拆到不同服务器上的,不可能去每一台服务器上去下载文件的,也不便于日志检索)时,就需要自定义Appender,将日志集中输出或者其他一些特殊需求。所以本章节就来简单介绍下关于log4j2和logback的自定义Appender知识。
一点知识
编写自定义
Appender时,我们先来看看log4j2和logback自带了哪些Appender,了解下是否可以满足我们的个性化需求,避免重复制造轮子。
log4j2自带Appender
先看一张官网提供的Appender说明:

| 名称 | 描述 |
|---|---|
| AsyncAppender | 使用一个单独线程记录日志,实现异步处理日志事件。 |
| CassandraAppender | 将日志信息输出到一个Apache的Cassandra数据库 |
| ConsoleAppender | 将日志信息输出到控制台 |
| FailoverAppender | 包含其他appenders,按顺序尝试,直至成功或结尾 |
| FileAppender | 一个OutputStreamAppender,将日志输出到文件 |
| FlumeAppender | 将日志输出到Apache Flume系统 |
| JDBCAppender | 将日志通过JDBC输出到关系型数据库 |
| JMS Appender | 将日志输出到JMS(Java Message Service) |
| JPAAppender | 将日志输出到JPA框架 |
| HttpAppender | 通过HTTP输出日志 |
| KafkaAppender | 将日志输出到Apache Kafka |
| MemoryMappedFileAppender | 将日志输出到一块文件关联的内存 |
| OutputStreamAppender | 将日志输出到一个OutputStream |
| RandomAccessFileAppender | 性能比FileAppender高20%~200%的文件输出Appender |
| RewriteAppender | 允许对日志信息进行加工 |
| RollingFileAppender | 按log文件最大长度限度生成新文件 |
| RollingRandomAccessFA | 添加了缓存的RollingFileAppender |
| RoutingAppender | 将日志事件分类,按条件分配给子appender |
| SMTPAppender | 将日志输出到邮件 |
| SocketAppender | 将日志输出到一个Socket |
| SyslogAppender | 是一个SocketAppender,将日志输出到远程系统日志 |
| ZeroMQ/JeroMQ Appender | 使用JeroMQ库将日志输出到ZeroMQ终端 |
基本上已经覆盖了百分之九十的业务场景了。相关的详细说明或者配置大家自行搜索或者查看官网说明。
官网地址:http://logging.apache.org/log4j/2.x/manual/appenders.html
logback自带Appender
和log4j2一样,自带的都差不多了。
| 名称 | 描述 |
|---|---|
| ConsoleAppender | 将日志输出到控制台 |
| FileAppender | 将日志输出到文件 |
| RollingFileAppender | 滚动文件生成,按条件生成不同文件,配合TriggeringPolicy使用 |
| SocketAppender | 输出日志到远程实例中,明文传输 |
| SSLSocketAppender | 输出日志到远程实例中,密文传输 |
| SMTPAppender | 将日志输出到邮件 |
| DBAppender | 日志事件插入数据库中,需要提前创建表 |
| SyslogAppender | 是一个SocketAppender,将日志输出到远程系统日志 |
| SiftingAppender | 可基于任何给定的实时属性分开(或者筛选)日志,如基于用户会话分开日志事件 |
| AmqpAppender | 将日志输出到MQ服务中 |
具体可查看:https://blog.csdn.net/tianyaleixiaowu/article/details/73327752 很详细!
或者查看官网:https://logback.qos.ch/manual/appenders.html
自定义Appender
自定义
Appender时,可以按实现的功能,适当的继承(log4j2的appender类基本上被设置成了final无法继承)或者参考一些已有的功能,当然了也可以直接继承其基类接口的。以下就简单的示例下,没有实现特定的功能,⊙﹏⊙‖∣
log4j2自定义Appender
按官网的扩展说明,我们来简单实现一个appender。

官网地址:http://logging.apache.org/log4j/2.x/manual/extending.html#Appenders
0.编写自定义appender类,继承AbstractAppender抽象实现类:
MyLog4j2Appender.java
/**
* 自定义log4j2输出源,简单的输出到控制台
* @author oKong
*
*/
//这里的 MyLog4j2 对应就是 xml中,
/**
*
* <appenders>
* <MyLog4j2 name="customAppender" printString="一枚趔趄的猿">
* </MyLog4j2>
* </appenders>
*
*/
@Plugin(name = "MyLog4j2", category = "Core", elementType = "appender", printObject = true)
public class MyLog4j2Appender extends AbstractAppender {
String printString;
/**
*构造函数 可自定义参数 这里直接传入一个常量并输出
*
*/
protected MyLog4j2Appender(String name, Filter filter, Layout<? extends Serializable> layout,String printString) {
super(name, filter, layout);
this.printString = printString;
}
@Override
public void append(LogEvent event) {
if (event != null && event.getMessage() != null) {
// 此处自定义实现输出
// 获取输出值:event.getMessage().toString()
// System.out.print(event.getMessage().toString());
// 格式化输出
System.out.print(printString + ":" + getLayout().toSerializable(event));
}
}
/** 接收配置文件中的参数
*
* @PluginAttribute 字面意思都知道,是xml节点的attribute值,如<oKong name="oKong"></oKong> 这里的name 就是 attribute
* @PluginElement:表示xml子节点的元素,
* 如
* <oKong name="oKong">
* <PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
* </oKong>
* 其中,PatternLayout就是 的 Layout,其实就是{@link Layout}的实现类。
*/
@PluginFactory
public static MyLog4j2Appender createAppender(
@PluginAttribute("name") String name,
@PluginElement("Filter") final Filter filter,
@PluginElement("Layout") Layout<? extends Serializable> layout,
@PluginAttribute("printString") String printString) {
if (name == null) {
LOGGER.error("no name defined in conf.");
return null;
}
//默认使用 PatternLayout
if (layout == null) {
layout = PatternLayout.createDefaultLayout();
}
return new MyLog4j2Appender(name, filter, layout, printString);
}
@Override
public void start() {
System.out.println("log4j2-start方法被调用");
super.start();
}
@Override
public void stop() {
System.out.println("log4j2-stop方法被调用");
super.stop();
}
}
简单说明下,相关注意点:
@Plugin注解:这个注解,是为了在之后配置log4j2-spring.xml时,指定的Appender Tag。- 构造函数:除了使用父类的以外,也可以增加一些自己的配置。
- 重写
append()方法:这里面需要实现具体的逻辑,日志的去向。 createAppender()方法:主要是接收log4j2-spring.xml中的配置项。
1.使用自定义的appender。
log4j2-spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration status="WARN" monitorInterval="30" packages="cn.lqdev.learning">
<!--定义appenders-->
<appenders>
<MyLog4j2 name="oKong" printString="一枚趔趄的猿(log4j2)">
<!--输出日志的格式-->
<PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
</MyLog4j2>
</appenders>
<!--然后定义logger,只有定义了logger并引入的appender,appender才会生效-->
<loggers>
<!--过滤掉spring和mybatis的一些无用的DEBUG信息-->
<logger name="org.springframework" level="INFO"></logger>
<logger name="org.mybatis" level="INFO"></logger>
<!-- 自定义包下设置为INFO,则可以看见输出的日志不包含debug输出了 -->
<logger name="cn.lqdev.learning" level="INFO"/>
<root level="all">
<appender-ref ref="oKong"/>
</root>
</loggers>
</configuration>
这里需要注意,需要在configuration中,加入属性packages为自定类所在包名cn.lqdev.learning才会被扫描生效,不知道是否还有其他方法。
2.启动后,就可以看见相关输出了。
...部分省略...
一枚趔趄的猿(log4j2):[14:47:43:751] [INFO] - org.apache.juli.logging.DirectJDKLog.log(DirectJDKLog.java:180) - Using a shared selector for servlet write/read
一枚趔趄的猿(log4j2):[14:47:43:761] [INFO] - org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainer.start(TomcatEmbeddedServletContainer.java:216) - Tomcat started on port(s): 8080 (http)
一枚趔趄的猿(log4j2):[14:47:43:764] [INFO] - org.springframework.boot.StartupInfoLogger.logStarted(StartupInfoLogger.java:57) - Started Chapter25Application in 2.03 seconds (JVM running for 3.164)
一枚趔趄的猿(log4j2):[14:47:43:764] [INFO] - cn.lqdev.learning.springboot.chapter25.Chapter25Application.main(Chapter25Application.java:14) - Chapter25启动!
不知道如何整合log4j2的,可以查看:《第二十三章:日志管理之整合篇》
logback自定义Appender
logback的自定义,也是类似的,都是基于一个基类appender来实现。本身logback提供了AppenderBase和UnsynchronizedAppenderBase两个抽象类(同步和非同步),所以我们自定义时,只需要看实际业务继承其中的一个即可。先看下其类继承结构:

0.编写自定义appender类。
MyLogbackAppender.java
@Getter
@Setter
public class MyLogbackAppender extends UnsynchronizedAppenderBase<ILoggingEvent>{
Layout<ILoggingEvent> layout;
//自定义配置
String printString;
@Override
public void start(){
//这里可以做些初始化判断 比如layout不能为null ,
if(layout == null) {
addWarn("Layout was not defined");
}
//或者写入数据库 或者redis时 初始化连接等等
super.start();
}
@Override
public void stop()
{
//释放相关资源,如数据库连接,redis线程池等等
System.out.println("logback-stop方法被调用");
if(!isStarted()) {
return;
}
super.stop();
}
@Override
public void append(ILoggingEvent event) {
if (event == null || !isStarted()){
return;
}
// 此处自定义实现输出
// 获取输出值:event.getFormattedMessage()
// System.out.print(event.getFormattedMessage());
// 格式化输出
System.out.print(printString + ":" + layout.doLayout(event));
}
}
也简单说明下,相关注意点:
start方法:初始时调用。故在编写如数据库入库,连接缓存或者mq时,可以在这个方法里面进行初始化操作。stop:当停止时,调用。可做些资源释放操作。
1.使用自定义appender:
logback-spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
<!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径 -->
<property name="LOG_HOME" value="/home" />
<!-- 控制台输出 -->
<appender name="MyLogback"
class="cn.lqdev.learning.springboot.chapter25.config.MyLogbackAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<!-- 日志收集最低日志级别 -->
<level>INFO</level>
</filter>
<layout
class="ch.qos.logback.classic.PatternLayout">
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符 -->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
</layout>
<!-- 自定义参数 -->
<printString>一枚趔趄的猿(logback)</printString>
</appender>
<!-- 自定义包下设置为INFO,则可以看见输出的日志不包含debug输出了 -->
<logger name="cn.lqdev.learning" level="INFO" />
<!-- 日志输出级别 -->
<root level="INFO">
<appender-ref ref="MyLogback" />
</root>
</configuration>
2.应用启动,查看控制台输出,效果是一样的:
...部分省略...
一枚趔趄的猿(logback):2018-08-25 15:01:57.486 [main] INFO org.apache.coyote.http11.Http11NioProtocol - Starting ProtocolHandler ["http-nio-8080"]
一枚趔趄的猿(logback):2018-08-25 15:01:57.497 [main] INFO org.apache.tomcat.util.net.NioSelectorPool - Using a shared selector for servlet write/read
一枚趔趄的猿(logback):2018-08-25 15:01:57.520 [main] INFO o.s.b.c.e.tomcat.TomcatEmbeddedServletContainer - Tomcat started on port(s): 8080 (http)
一枚趔趄的猿(logback):2018-08-25 15:01:57.523 [main] INFO c.l.l.springboot.chapter25.Chapter25Application - Started Chapter25Application in 54.349 seconds (JVM running for 55.377)
一枚趔趄的猿(logback):2018-08-25 15:01:57.524 [main] INFO c.l.l.springboot.chapter25.Chapter25Application - Chapter25启动!
关于ShutdownHook
当你运行了以上的自定义
appender后,停止应用时,你会发现定义的stop方法并没有被执行。还需要配置一个ShutdownHook系统钩子,使得在jvm在退出时之前会调用。
一点知识
我们知道,在java中,注册一个关闭钩子是很简单的,使用Runtime类即可,具体用法如下:
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
@Override
public void run() {
// 执行资源释放操作
}
}));
而在SpringBoot中,只需要配置logging.register-shutdown-hook为true即可。
logging.register-shutdown-hook=true
对于logback而言,也可以在logback-spring.xml中配置:
<shutdownHook class="ch.qos.logback.core.hook.DelayingShutdownHook"/>
也是可以的。再或者在启动类手动注册这个DelayingShutdownHook也是可以的
这里有个坑,log4j2而言,配置失效了。谷歌了一圈也没有发现解决方法,网上的方案试了一遍都是不行。。很尴尬。要是使用log4j2的话,可以取巧下,在start()方法里面,注册钩子之后调用stop方法。希望有知道的大神分享下如何解决!
参考资料
- https://blog.csdn.net/zhoucheng05_13/article/details/78494458
- http://logging.apache.org/log4j/2.x/manual/appenders.html
- http://logging.apache.org/log4j/2.x/manual/extending.html#Appenders
- https://logback.qos.ch/manual/appenders.html
- https://blog.csdn.net/hupoling/article/details/75353854
总结
本文主要是简单介绍了
log4j2和logback自定义appender相关知识。实现起来是相对简单的,需要注意当涉及需要关闭释放相关资源时,需要确认下关闭前是否有被调用,不然可能造成连接未关闭等行为,避免不必要的问题。关于最后使用log4j2关闭钩子未生效问题,由于现在都默认使用logback了,这个问题就不深究了,还望有知道的同学分享下解决方案!谢谢!同时由于没有对两个框架有过多的深入了解,只能点到为止了,若文中有误,还望指出!
最后
目前互联网上很多大佬都有
SpringBoot系列教程,如有雷同,请多多包涵了。原创不易,码字不易,还希望大家多多支持。若文中有所错误之处,还望提出,谢谢。
老生常谈
- 个人QQ:
499452441 - 公众号:
lqdevOps

个人博客:http://blog.lqdev.cn
完整示例:https://github.com/xie19900123/spring-boot-learning/tree/master/chapter-25
原文地址:http://blog.lqdev.cn/2018/08/25/springboot/chapter-twenty-five/
SpringBoot | 第二十五章:日志管理之自定义Appender的更多相关文章
- Gradle 1.12用户指南翻译——第二十五章. Scala 插件
其他章节的翻译请参见: http://blog.csdn.net/column/details/gradle-translation.html 翻译项目请关注Github上的地址: https://g ...
- “全栈2019”Java多线程第二十五章:生产者与消费者线程详解
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...
- “全栈2019”Java第二十五章:流程控制语句中循环语句while
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...
- 第二十五章 springboot + hystrixdashboard
注意: hystrix基本使用:第十九章 springboot + hystrix(1) hystrix计数原理:附6 hystrix metrics and monitor 一.hystrixdas ...
- 【第二十五章】 springboot + hystrixdashboard
注意: hystrix基本使用:第十九章 springboot + hystrix(1) hystrix计数原理:附6 hystrix metrics and monitor 一.hystrixdas ...
- SpringBoot | 第二十八章:监控管理之Spring Boot Admin使用
前言 上一章节,我们介绍了Actuator的使用,知道了可通过访问不同的端点路径,获取相应的监控信息.但使用后也能发现,返回的监控数据都是以JSON串的形式进行返回的,对于实施或者其他人员来说,不是很 ...
- SpringBoot | 第二十六章:邮件发送
前言 讲解了日志相关的知识点后.今天来点相对简单的,一般上,我们在开发一些注册功能.发送验证码或者订单服务时,都会通过短信或者邮件的方式通知消费者,注册或者订单的相关信息.而且基本上邮件的内容都是模版 ...
- SpringBoot | 第二十四章:日志管理之AOP统一日志
前言 上一章节,介绍了目前开发中常见的log4j2及logback日志框架的整合知识.在很多时候,我们在开发一个系统时,不管出于何种考虑,比如是审计要求,或者防抵赖,还是保留操作痕迹的角度,一般都会有 ...
- 第二十五章 ansible基础
一.Ansible概述 1.什么是Ansible Ansible是一个自动化统一配置管理工具,自动化主要体现在Ansible集成了丰富模块以及功能组件,可以通过一个命令完成一系列的操作,进而能减少重复 ...
随机推荐
- ES6学习之Set和Map
一.Set 1.Set 定义:Set类似于数组,但成员的值都是唯一的,没有重复的值 let s = new Set([1,2,3,4,5,2,4]); //Set { 1, 2, 3, 4, 5 } ...
- Android源码中添加APP
参考罗升阳<Android系统源代码情景分析> 在Android源码中,我们通常把实验性质的Android APP放在packages/experimental目录下.对于一个简单的应用程 ...
- cadence spb 16.5 破解过程实例和使用感受_赤松子耶_新浪博客
cadence spb 16.5 破解过程实例和使用感受_赤松子耶_新浪博客 Cadence Allegro16.5详细安装具体的步骤 1.下载SPB16.5下来后,点setup.exe,先安装第一项 ...
- 【机器学习】主题模型(二):pLSA和LDA
-----pLSA概率潜在语义分析.LDA潜在狄瑞雷克模型 一.pLSA(概率潜在语义分析) pLSA: -------有过拟合问题,就是求D, Z, W pLSA由LSA发展过来,而早期L ...
- 子元素应该margin-top影响父元素的解决办法
在子元素设置margin-top,有时会带着父元素一起移动. 原因: Outer Div [margin: 0 auto] Inner Div [margin-top: 10px] 根据CSS2.1盒 ...
- 20169219《linux内核原理与分析》第六周作业
网易云课堂学习 1.intel x86 CPU有四种不同的执行级别0-3,linux只使用了其中的0级和3级分贝来表示内核态和用户态. 2.一般来说在linux中,地址空间是一个显著的标志:0xc00 ...
- 实用掌中宝--HTML&CSS常用标签速查手册 PDF扫描版
实用掌中宝--HTML&CSS常用标签速查手册 内容推荐: 本书第一篇以语法和实例相结合的形式,详细讲解了HTML语言中各个元素及其属性的作用.语法和显示效果:第二篇从CSS基本概念开始,分别 ...
- Python开发【第五篇】:函数
1. 函数 函数是组织好的,可重复使用的,用来实现单一,或相关功能的代码块. 函数分为 Python 程序内置函数,用户自定义的函数.将代码定义为函数,有如下好处: 代码重用(某个特定功能复用 ...
- Unity中限制轴向移动范围Mathf.Clamp
Mathf.Clamp 在游戏中,为了限制玩家的某一轴向的移动不超过一定的范围,可以用Mathf.Clamp来解决 Mathf.Clamp(float value,float min,float ...
- 历届试题_log大侠
标题:Log大侠 atm参加了速算训练班,经过刻苦修炼,对以2为底的对数算得飞快,人称Log大侠. 一天,Log大侠的好友 drd 有一些整数序列需要变换,Log大侠正好施展法力... ...