您的位置:首页 > 编程语言 > Java开发

SpringBoot | 第二十五章:日志管理之自定义Appender

2018-08-25 23:53 676 查看

前言


前面两章节我们介绍了一些日志框架的常见配置及使用实践。一般上,在开发过程中,像
log4j2
logback
日志框架都提供了很多
Appender
,基本上可以满足大部分的业务需求了。但在一些特殊需求或者需要将日志进行集中管理(集群部署时,日志是分拆到不同服务器上的,不可能去每一台服务器上去下载文件的,也不便于日志检索)时,就需要自定义
Appender
,将日志集中输出或者其他一些特殊需求。所以本章节就来简单介绍下关于
log4j2
logback
的自定义
Appender
知识。



一点知识

log4j2自带Appender

logback自带Appender

自定义Appender

log4j2自定义Appender

logback自定义Appender

关于ShutdownHook

一点知识

参考资料

总结

最后

老生常谈

一点知识


编写自定义
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/
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: