添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
相关文章推荐
另类的汤圆  ·  FFmpeg之av_register_all ...·  1 年前    · 

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

  • log4j2自带Appender
  • logback自带Appender
  • 自定义Appender
  • log4j2自定义Appender
  • logback自定义Appender
  • 关于ShutdownHook

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

    log4j2自带Appender

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

    基本上已经覆盖了百分之九十的业务场景了。相关的详细说明或者配置大家自行搜索或者查看官网说明。
    官网地址: http://logging.apache.org/log4j/2.x/manual/appenders.html

    logback自带Appender

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

    具体可查看: 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-hooktrue即可。

    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
  • 本文主要是简单介绍了log4j2logback自定义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/