当项目上线发生错误或是异常后,咱们老是指望可以在第一时间内收到用户的详细反馈。固然,这也无疑会是一个很是好的提高软件质量的方法。但若是用户不肯意反馈呢?此时,咱们即可以借助日志系统,好比:每隔一小时,服务器自动向咱们报告一下当前的服务状况。当有错误或是警告或是异常信息时,及时向咱们的报告等。html
在基于上述的需求上,咱们结合spring-boot
内置的LogBack
,来给出将warn
,error
信息发送到远程服务器的示例。java
https://github.com/mengyunzhi/springBootSampleCode/tree/master/log-back
开发环境: java1.8 + spring-boot:2.1.2git
pom.xmlgithub
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.2.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.mengyunzhi.sample</groupId> <artifactId>log-back</artifactId> <version>0.0.1-SNAPSHOT</version> <name>log-back</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
控制台打印信息以下:web
. ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: 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)
新建resources/logback-spring.xml
,初始化如下信息:spring
<?xml version="1.0" encoding="UTF-8"?> <!--开启debug模式--> <configuration debug="true"> </configuration>
启动项目,控制台打印信息以下:apache
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
下面,一行日志也没有了。json
是的,因为logback-spring.xml
对日志输出进行了控制,而配置信息中,咱们又没有写任何的信息,为空。因此spring 大LOG
后面固然就不显示任何日志了信息了。segmentfault
spring-boot
的默认配置咱们使用IDEA的打开文件快捷键commod+shift+o
,输入base.xml
,而后再使用查看文件位置快捷键option+F1
来查看文件位置。更来到了spring-boot
的默认配置。浏览器
上述文件,即为spring-boot
的默认配置。下面,咱们将以上配置引入到咱们的logback-spring.xml
中,来实现spring-boot
的默认日志效果。
复制相应的代码至logback-spring.xml
中:
<?xml version="1.0" encoding="UTF-8"?> <!--启用debug模式后,将在`spring-boot 大LOG`上方打印中logBack的配置信息--> <configuration debug="true"> <!--包含配置文件 org/springframework/boot/logging/logback/defaults.xml--> <include resource="org/springframework/boot/logging/logback/defaults.xml" /> <!--定义变量LOG_FILE,值为${LO...}--> <property name="LOG_FILE" value="${LOG_FILE:-${LOG_PATH:-${LOG_TEMP:-${java.io.tmpdir:-/tmp}}}/spring.log}"/> <!--包含配置文件,该配置文件中,定义了 控制台日志是按什么规则,什么形式输出的--> <include resource="org/springframework/boot/logging/logback/console-appender.xml" /> <!--包含配置文件,该配置文件中,定义了 文件日志是按什么规则,什么形式输出的--> <include resource="org/springframework/boot/logging/logback/file-appender.xml" /> <!--定义日志等级--> <root level="INFO"> <!--启用第一个appender为CONSOLE, 该名称定义于org/springframework/boot/logging/logback/console-appender.xml中--> <appender-ref ref="CONSOLE" /> <!--启用第二个appender为FILE, 该名称定义于org/springframework/boot/logging/logback/file-appender.xml中--> <appender-ref ref="FILE" /> </root> </configuration>
而后咱们再次启动项目,会发现与原spring-boot
相比较,在spring 大LOGO
前多一些日志相关的配置信息输出,其它的信息是一致的。
经过上面的注释,咱们猜想:appender
这个东西,可以把日志处理成咱们想要的样子。
在进行官方文档的学习中,咱们发现了不少已经存在的appender
。与咱们的需求比较相近的是SyslogAppender。
liunx
有标准的syslog服务,用于接收syslog日志。
经过查询相关资料,咱们获悉,此syslog
服务,一身做用于514端口上。直接使用UDP
或TCP
协议发送MESSAGE
。而咱们此时想用更熟悉的http
协议。因此暂时放弃。
小于1024的均为已知端口,能够经过端口号来查询对应的协议或服务名称。
除了按官方的教程来写本身的http appender
,还有一些比较好的第三方appender
能够使用,好比:LogglyAppender。找到官方文档,并引入:
pom.xml
<dependency> <groupId>org.logback-extensions</groupId> <artifactId>logback-ext-loggly</artifactId> <version>0.1.5</version> </dependency>
logback-spring.xml
<?xml version="1.0" encoding="UTF-8"?> <!--启用debug模式后,将在`spring-boot 大LOG`上方打印中logBack的配置信息--> <configuration debug="true"> <!--包含配置文件 org/springframework/boot/logging/logback/defaults.xml--> <include resource="org/springframework/boot/logging/logback/defaults.xml" /> <!--定义变量LOG_FILE,值为${LO...}--> <property name="LOG_FILE" value="${LOG_FILE:-${LOG_PATH:-${LOG_TEMP:-${java.io.tmpdir:-/tmp}}}/spring.log}"/> <!--包含配置文件,该配置文件中,定义了 控制台日志是按什么规则,什么形式输出的--> <include resource="org/springframework/boot/logging/logback/console-appender.xml" /> <!--包含配置文件,该配置文件中,定义了 文件日志是按什么规则,什么形式输出的--> <include resource="org/springframework/boot/logging/logback/file-appender.xml" /> <!--引入第三方appender, 起名为http--> <appender name="HTTP" class="ch.qos.logback.ext.loggly.LogglyAppender"> <!--请求的地址--> <endpointUrl>http://localhost:8081/log</endpointUrl> </appender> <!--定义日志等级--> <root level="INFO"> <!--启用第一个appender为CONSOLE, 该名称定义于org/springframework/boot/logging/logback/console-appender.xml中--> <appender-ref ref="CONSOLE" /> <!--启用第二个appender为FILE, 该名称定义于org/springframework/boot/logging/logback/file-appender.xml中--> <appender-ref ref="FILE" /> <!--启用第三个appender为HTTP--> <appender-ref ref="HTTP" /> </root> </configuration>
测试方法如图:
send
中咱们加入logger
。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。
application.propertiesserver.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
端口上的接收位置打断点。
查看断点信息:
此时咱们发现两项信息,也证实数据的确是发送和接收成功了:
@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
<!--log to json--> <dependency> <groupId>ch.qos.logback.contrib</groupId> <artifactId>logback-jackson</artifactId> <version>0.1.5</version> </dependency> <!--log to json--> <dependency> <groupId>ch.qos.logback.contrib</groupId> <artifactId>logback-json-classic</artifactId> <version>0.1.5</version> </dependency>
logback.xml
<!--引入第三方appender, 起名为http--> <appender name="HTTP" class="ch.qos.logback.ext.loggly.LogglyAppender"> <!--请求的地址--> <endpointUrl>http://localhost:8081/log</endpointUrl> <!--定义输出格式JSON--> <layout class="ch.qos.logback.contrib.json.classic.JsonLayout"> <jsonFormatter class="ch.qos.logback.contrib.jackson.JacksonJsonFormatter"> <prettyPrint>true</prettyPrint> </jsonFormatter> <timestampFormat>yyyy-MM-dd' 'HH:mm:ss.SSS</timestampFormat> </layout> </appender>
再次启动项目,访问: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
字符串,此时咱们即可以在接收端创建对应的实体,来轻易的接收了。
当前虽然实现了将日志写入到第三方HTTP日志服务器,可是一些咱们不想写入的,好比说INFO
信息,也被写入了。下面,咱们写一个过滤器,来实现只输出warn
有error
的信息。
新建过滤器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<Level> eventsToKeep = Arrays.asList(Level.WARN, Level.ERROR); if (eventsToKeep.contains(loggingEvent.getLevel())) { return FilterReply.NEUTRAL; } else { return FilterReply.DENY; } } }
设置过滤器:
logback-spring.xml
<!--引入第三方appender, 起名为http--> <appender name="HTTP" class="ch.qos.logback.ext.loggly.LogglyAppender"> <!--请求的地址--> <endpointUrl>http://localhost:8081/log</endpointUrl> <!--定义过滤器--> <filter class="com.mengyunzhi.sample.logback.Filter"/> <!--定义输出格式JSON--> <layout class="ch.qos.logback.contrib.json.classic.JsonLayout"> <jsonFormatter class="ch.qos.logback.contrib.jackson.JacksonJsonFormatter"> <prettyPrint>true</prettyPrint> </jsonFormatter> <timestampFormat>yyyy-MM-dd' 'HH:mm:ss.SSS</timestampFormat> </layout> </appender>
测试:
只接收了warn
与error
的数据。
咱们在logback-spring.xml定义了<endpointUrl>http://localhost:8081/log</endpointUrl>
,如何能够将此项配置搬迁到application.properties
中呢?
application.properties
yunzhi.log.url=http://localhost:8081/log
logback-spring.xml
<!--引入application配置信息--> <springProperty scope="context" name="logUrl" source="yunzhi.log.url" defaultValue="localhost"/> <!--引入第三方appender, 起名为http--> <appender name="HTTP" class="ch.qos.logback.ext.loggly.LogglyAppender"> <!--请求的地址--> <endpointUrl>${logUrl}</endpointUrl>
此时,咱们即可以对logUrl在application.properties
中进行统一管理了,固然了,不止如此,咱们还能够在启动项目的时候,使用--yunzhi.log.url=xxx
来轻松的改变日志接收地址。
在总体实现的过程当中,咱们的解决思路仍然是:看官方文档,学官方文档,照抄官方文档。欲速则不达,有学习一门新的知识时,优先学习官方sample code,其次是官方文档。在学习的过程当中,还要特别的注意版本号的问题;如何正确的快速的高效率测试的问题。
每次有日志就进行一次请求,对网络资源是种浪费。是否能够定时统一的将日志发送给服务器呢?请继续阅读spring-boot下使用LogBack,使用HTTP协议将日志推送到日志服务器(二)