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

前言

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

一点知识

编写自定义Appender时,我们先来看看log4j2和logback自带了哪些Appender,了解下是否可以满足我们的个性化需求,避免重复制造轮子。

log4j2自带Appender

先看一张官网提供的Appender说明:

AAffA0nNPuCLAAAAAElFTkSuQmCC

名称

描述

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终端

logback自带Appender

和log4j2一样,自带的都差不多了。

名称

描述

ConsoleAppender

将日志输出到控制台

FileAppender

将日志输出到文件

RollingFileAppender

滚动文件生成,按条件生成不同文件,配合TriggeringPolicy使用

SocketAppender

输出日志到远程实例中,明文传输

SSLSocketAppender

输出日志到远程实例中,密文传输

SMTPAppender

将日志输出到邮件

DBAppender

日志事件插入数据库中,需要提前创建表

SyslogAppender

是一个SocketAppender,将日志输出到远程系统日志

SiftingAppender

可基于任何给定的实时属性分开(或者筛选)日志,如基于用户会话分开日志事件

AmqpAppender

将日志输出到MQ服务中

自定义Appender

自定义Appender时,可以按实现的功能,适当的继承(log4j2的appender类基本上被设置成了final无法继承)或者参考一些已有的功能,当然了也可以直接继承其基类接口的。以下就简单的示例下,没有实现特定的功能,⊙﹏⊙‖∣

log4j2自定义Appender

按官网的扩展说明,我们来简单实现一个appender。

AAffA0nNPuCLAAAAAElFTkSuQmCC

0.编写自定义appender类,继承AbstractAppender抽象实现类:

MyLog4j2Appender.java

/**

* 自定义log4j2输出源,简单的输出到控制台

* @author oKong

*

*/

//这里的 MyLog4j2 对应就是 xml中,

/**

*

*

*

*

*

*

*/

@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值,如 这里的name 就是 attribute

* @PluginElement:表示xml子节点的元素,

* 如

*

*

*

* 其中,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

这里需要注意,需要在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两个抽象类(同步和非同步),所以我们自定义时,只需要看实际业务继承其中的一个即可。先看下其类继承结构:

AAffA0nNPuCLAAAAAElFTkSuQmCC

0.编写自定义appender类。

MyLogbackAppender.java

@Getter

@Setter

public class MyLogbackAppender extends UnsynchronizedAppenderBase{

Layout 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

class="cn.lqdev.learning.springboot.chapter25.config.MyLogbackAppender">

INFO

class="ch.qos.logback.classic.PatternLayout">

%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n

一枚趔趄的猿(logback)

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中配置:

也是可以的。再或者在启动类手动注册这个DelayingShutdownHook也是可以的

这里有个坑,log4j2而言,配置失效了。谷歌了一圈也没有发现解决方法,网上的方案试了一遍都是不行。。很尴尬。要是使用log4j2的话,可以取巧下,在start()方法里面,注册钩子之后调用stop方法。希望有知道的大神分享下如何解决!

参考资料

最后

目前互联网上很多大佬都有SpringBoot系列教程,如有雷同,请多多包涵了。原创不易,码字不易,还希望大家多多支持。若文中有所错误之处,还望提出,谢谢。

老生常谈

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值