spring-boot下使用LogBack,使用HTTP协议将日志推送到日志服务器

当项目上线发生错误或是异常后,我们总是期望能够在第一时间内收到用户的详细反馈。当然,这也无疑会是一个非常好的提升软件质量的方法。但如果用户不愿意反馈呢?此时,我们便可以借助日志系统,比如:每隔一小时,服务器自动向我们报告一下当前的服务情况。当有错误或是警告或是异常信息时,及时向我们的报告等。

在基于上述的需求上,我们结合spring-boot内置的LogBack,来给出将warn,error信息发送到远程服务器的示例。

项目地址

https://github.com/mengyunzhi/springBootSampleCode/tree/master/log-back
开发环境: java1.8 + spring-boot:2.1.2

实现步骤

引入相关的依赖

pom.xml



    4.0.0
    
        org.springframework.boot
        spring-boot-starter-parent
        2.1.2.RELEASE
         
    
    com.mengyunzhi.sample
    log-back
    0.0.1-SNAPSHOT
    log-back
    Demo project for Spring Boot

    
        1.8
    

    
        
            org.springframework.boot
            spring-boot-starter-web
        

        
            org.springframework.boot
            spring-boot-starter-test
            test
        
    

    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
            
        
    

启动项目

控制台打印信息如下:

 .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.1.2.RELEASE)

2019-01-16 10:35:04.999  INFO 1571 --- [           main] c.m.sample.logback.LogBackApplication    : Starting LogBackApplication on panjiedeMac-Pro.local with PID 1571 (/Users/panjie/github/mengyunzhi/sample/spring-boot/log-back/target/classes started by panjie in /Users/panjie/github/mengyunzhi/sample/spring-boot/log-back)
2019-01-16 10:35:05.002  INFO 1571 --- [           main] c.m.sample.logback.LogBackApplication    : No active profile set, falling back to default profiles: default
2019-01-16 10:35:05.913  INFO 1571 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2019-01-16 10:35:05.934  INFO 1571 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2019-01-16 10:35:05.935  INFO 1571 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.14]
2019-01-16 10:35:05.940  INFO 1571 --- [           main] o.a.catalina.core.AprLifecycleListener   : The APR based Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path: [/Users/panjie/Library/Java/Extensions:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java:.]
2019-01-16 10:35:06.008  INFO 1571 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2019-01-16 10:35:06.008  INFO 1571 --- [           main] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 968 ms
2019-01-16 10:35:06.183  INFO 1571 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2019-01-16 10:35:06.335  INFO 1571 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2019-01-16 10:35:06.338  INFO 1571 --- [           main] c.m.sample.logback.LogBackApplication    : Started LogBackApplication in 1.616 seconds (JVM running for 2.093)

配置logback

新建resources/logback-spring.xml,初始化以下信息:




启动项目,控制台打印信息如下:

10:33:41,053 |-INFO in ch.qos.logback.classic.joran.action.ConfigurationAction - End of configuration.
10:33:41,054 |-INFO in org.springframework.boot.logging.logback.SpringBootJoranConfigurator@55a1c291 - Registering current configuration as safe fallback point
10:33:41,067 |-WARN in Logger[org.springframework.boot.context.logging.ClasspathLoggingApplicationListener] - No appenders present in context [default] for logger [org.springframework.boot.context.logging.ClasspathLoggingApplicationListener].

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.1.2.RELEASE)

如何判断配置成功了?

我们比较上面两个日志,第一个是没有配置logback-spring.xml,第二个是配置logback-spring.xml了。是的,如果我们发现spring 大LOG打印前,在控制台中打印了ch.qos...输出的日志信息,则说明logback-spring.xml。同时,如果logback-spring.xml起作用的话,我们还发现spring 大LOG下面,一行日志也没有了。

是的,由于logback-spring.xml对日志输出进行了控制,而配置信息中,我们又没有写任何的信息,为空。所以spring 大LOG后面当然就不显示任何日志了信息了。

查看spring-boot的默认配置

我们使用IDEA的打开文件快捷键commod+shift+o,输入base.xml,然后再使用查看文件位置快捷键option+F1来查看文件位置。更来到了spring-boot的默认配置。

spring-boot下使用LogBack,使用HTTP协议将日志推送到日志服务器_第1张图片

上述文件,即为spring-boot的默认配置。下面,我们将以上配置引入到我们的logback-spring.xml中,来实现spring-boot的默认日志效果。

实现默认效果

复制相应的代码至logback-spring.xml中:




    
    

    
    

    
    

    
    

    
    
        
        

        
        
    

然后我们再次启动项目,会发现与原spring-boot相比较,在spring 大LOGO前多一些日志相关的配置信息输出,其它的信息是一致的。

实现http日志appender

appender

通过上面的注释,我们猜测:appender这个东西,能够把日志处理成我们想要的样子。

在进行官方文档的学习中,我们发现了很多已经存在的appender。与我们的需求比较相近的是SyslogAppender

liunx有标准的syslog服务,用于接收syslog日志。

通过查询相关资料,我们获悉,此syslog服务,一身作用于514端口上。直接使用UDPTCP协议发送MESSAGE。而我们此时想用更熟悉的http协议。所以暂时放弃。

小于1024的均为已知端口,可以通过端口号来查询对应的协议或服务名称。

第三方http appender

除了按官方的教程来写自己的http appender,还有一些比较好的第三方appender可以使用,比如:LogglyAppender。找到官方文档,并引入:

pom.xml

        
            org.logback-extensions
            logback-ext-loggly
            0.1.5
        

logback-spring.xml




    
    

    
    

    
    

    
    

    
    
        
        http://localhost:8081/log
    

    
    
        
        

        
        

        
        
    

测试

测试方法如图:

spring-boot下使用LogBack,使用HTTP协议将日志推送到日志服务器_第2张图片

  1. 使用浏览器来访问当前项目的'/send'地址
  2. send中我们加入logger
  3. 再新建一个新项目,用来接收http appender发送过来的日志。

建立测试方法

LogBackApplication.java

package com.mengyunzhi.sample.logback;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;

@SpringBootApplication
@RestController
public class LogBackApplication {
    private static final Logger logger = LoggerFactory.getLogger(LogBackApplication.class);

    public static void main(String[] args) {
        SpringApplication.run(LogBackApplication.class, args);
    }

    @RequestMapping("send")
    public void send() {
        logger.info("info");
        logger.warn("warn");
        logger.error("error");
    }
}

接收模块

新建一个spring boot项目,然后设置端口为8081。

spring-boot下使用LogBack,使用HTTP协议将日志推送到日志服务器_第3张图片
application.properties
server.port=8081

ServiceApplication.java

package com.mengyunzhi.sample.logback.service;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.http.HttpRequest;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;

@SpringBootApplication
@RestController
public class ServiceApplication {
    private final static Logger logger = LoggerFactory.getLogger(ServiceApplication.class);
    public static void main(String[] args) {
        SpringApplication.run(ServiceApplication.class, args);
    }

    @RequestMapping("log")
    public void log(HttpServletRequest httpServletRequest) {
        logger.info(httpServletRequest.toString());
    }
}

启动测试

使用debug模式来启动两个项目,项目启动后,打开浏览器,输入:http://localhost:8080/send,并在8081端口上的接收位置打断点。

clipboard.png

查看断点信息:

spring-boot下使用LogBack,使用HTTP协议将日志推送到日志服务器_第4张图片

此时我们发现两项信息,也证明数据的确是发送和接收成功了:

  1. 请求方法: POST
  2. 请求的协议:http

查看发送过来的MESSAGE


    @RequestMapping("log")
    public void log(HttpServletRequest httpServletRequest) throws IOException {
        logger.info(httpServletRequest.toString());
        BufferedReader bufferedReader = httpServletRequest.getReader();
        String str, wholeStr = "";
        while((str = bufferedReader.readLine()) != null) {
            wholeStr += str;
        }
        logger.info(wholeStr);
    }

如下:

2019-01-16T06:06:49.707Z INFO  [http-nio-8080-exec-1] org.apache.catalina.core.ContainerBase.[Tomcat].[localhost].[/]: Initializing Spring DispatcherServlet 'dispatcherServlet'

是的,正如你发现在的一样,一些本来打印在8081项目上面的info信息,被发送过来了。

格式化数据

传过来的字段串,并不友好,我们接下来将其进行格式化。格式化的方法有两种:1. 发送端格式化。2. 接收端格式化。接收端的格式化的思想是按空格将日志拆分,然后要传入到格式实体的不同的字段。这里不阐述,不实现。
我们重点放在第1种,发送端使用第三方库进行格式化。

pom.xml

        
        
            ch.qos.logback.contrib
            logback-jackson
            0.1.5
        

        
        
            ch.qos.logback.contrib
            logback-json-classic
            0.1.5
        

logback.xml

    
    
        
        http://localhost:8081/log
        
        
            
                true
            
            yyyy-MM-dd' 'HH:mm:ss.SSS
        
    

再次启动项目,访问:http://localhost:8080/send查看断点。

{  "timestamp" : "2019-01-16 14:17:54.783",  "level" : "ERROR",  "thread" : "http-nio-8080-exec-1",  "logger" : "com.mengyunzhi.sample.logback.LogBackApplication",  "message" : "error",  "context" : "default"}

我们发现,以前的字段串,变成的json字符串,此时我们便可以在接收端建立对应的实体,来轻易的接收了。

过滤掉INFO信息

当前虽然实现了将日志写入到第三方HTTP日志服务器,但是一些我们不想写入的,比如说INFO信息,也被写入了。下面,我们写一个过滤器,来实现只输出warnerror的信息。

新建过滤器Filter.java

package com.mengyunzhi.sample.logback.service;

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.spi.LoggingEvent;
import ch.qos.logback.core.filter.AbstractMatcherFilter;
import ch.qos.logback.core.spi.FilterReply;

import java.util.Arrays;
import java.util.List;

/**
 * @author panjie
 */
public class Filter extends AbstractMatcherFilter {
    @Override
    public FilterReply decide(Object event) {
        if (!isStarted()) {
            return FilterReply.NEUTRAL;
        }

        LoggingEvent loggingEvent = (LoggingEvent) event;

        // 当级别为warn或error,时触发日志。
        List eventsToKeep = Arrays.asList(Level.WARN, Level.ERROR);
        if (eventsToKeep.contains(loggingEvent.getLevel())) {
            return FilterReply.NEUTRAL;
        } else {
            return FilterReply.DENY;
        }

    }
}

设置过滤器:
logback-spring.xml

    
    
        
        http://localhost:8081/log
        
        
        
        
            
                true
            
            yyyy-MM-dd' 'HH:mm:ss.SSS
        
    

测试:

spring-boot下使用LogBack,使用HTTP协议将日志推送到日志服务器_第5张图片

只接收了warnerror的数据。

统一配置

我们在logback-spring.xml定义了http://localhost:8081/log,如何可以将此项配置搬迁到application.properties中呢?

定义变量

application.properties

yunzhi.log.url=http://localhost:8081/log

引用变量

logback-spring.xml

    
    
    
    
        
        ${logUrl}

此时,我们便可以对logUrl在application.properties中进行统一管理了,当然了,不止如此,我们还可以在启动项目的时候,使用--yunzhi.log.url=xxx来轻松的改变日志接收地址。

总结

在整体实现的过程中,我们的解决思路仍然是:看官方文档,学官方文档,照抄官方文档。欲速则不达,有学习一门新的知识时,优先学习官方sample code,其次是官方文档。在学习的过程中,还要特别的注意版本号的问题;如何正确的快速的高效率测试的问题。

每次有日志就进行一次请求,对网络资源是种浪费。是否可以定时统一的将日志发送给服务器呢?请继续阅读spring-boot下使用LogBack,使用HTTP协议将日志推送到日志服务器(二)

你可能感兴趣的