全局日志是一个什么概念呢? 其实理解起来比较简单,类似于我们平时一直在使用的logback
、log4j
这种的日志框架的其中一个功能部分,minbox-logging
分布式日志框架目前独立于api-boot-plugins
,已经加入了minbox-projects
开源组织,之前博客有一系列的文章来讲解了ApiBoot Logging
(内部是集成的minbox-logging
)日志组件的使用以及极简的配置方式,可以访问ApiBoot 组件系列文章使用汇总 了解日志组件的使用详情。
什么是全局日志? 在之前ApiBoot Logging
分布式日志组件可以实现日志采集
、日志上报
、日志统一存储
、集成Spring Security
、集成Openfeign
等功能,随着ApiBoot Logging 2.2.1.RELEASE
版本的发布引入了一个新的概念,那就是GlobalLog
。
用过ApiBoot Logging
日志组件的同学应该都有了解,它只会记录每一次发送请求
相关的一些信息,如:请求参数、请求地址、请求头信息、响应内容等,并没有提供业务代码中的debug
、info
、error
等级日志的采集方式,就不要提上报这种日志到Logging Admin
了。
新版本的发布给我们带来了春天,ApiBoot Logging
为了方便代码的调试,执行时业务数据的监控,支持了采集业务代码内的不同等级的日志,而且还支持采集Exception
的堆栈信息,方便定位错误,及时纠正。
了解GlobalLogging接口 为了支持业务全局日志
,新版本中引入了GlobalLogging
接口,我们先来看看这个接口的源码,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 public interface GlobalLogging { void debug (String msg) ; void debug (String format, Object... arguments) ; void info (String msg) ; void info (String format, Object... arguments) ; void error (String msg) ; void error (String msg, Throwable throwable) ; void error (String format, Object... arguments) ; }
在GlobalLogging
接口内提供了三种类型的日志采集方法,分别是:debug
、info
、error
,这个版本只是对日志的等级进行了划分,并没有添加限制或者过滤机制。
GlobalLogging
当前版本有一个实现类org.minbox.framework.logging.client.global.support.GlobalLoggingMemoryStorage
,该类实现了GlobalLogging
内的全部方法,并将采集到的日志保存到了GlobalLoggingThreadLocal
,方便统一上报到Logging Admin
。
为了方便后期修改Global Log
的存储方式,ApiBoot Logging
提供了一个配置参数api.boot.logging.global-logging-storage-away
,该配置的默认值为memory
,对应GlobalLoggingMemoryStorage
实现类。
GlobalLogging自动化配置 ApiBoot Logging
根据api.boot.logging.global-logging-storage-away
配置参数,条件判断自动化配置了GlobalLogging
接口的实现类,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package org.minbox.framework.api.boot.autoconfigure.logging;import ...@Configuration @ConditionalOnClass(GlobalLogging.class) public class ApiBootLoggingGlobalLogStorageAutoConfiguration { @Bean @ConditionalOnProperty(prefix = API_BOOT_LOGGING_PREFIX, name = "global-logging-storage-away", havingValue = "memory", matchIfMissing = true) public GlobalLogging globalLogging () { return new GlobalLoggingMemoryStorage (); } }
根据globalLogging()
方法上的条件注入注解@ConditionalOnProperty
配置内容可以了解到,当我们在application.yml
配置文件内添加api.boot.logging.global-logging-storage-away=memory
时或者缺少该配置
时,该方法会被调用并且创建一个GlobalLoggingMemoryStorage
对象实例,并将该实例对象写入到IOC
容器内,这样我们在使用GlobalLogging
实例时,只需要注入就可以了,如下所示:
1 2 3 4 5 6 7 8 @Autowired private GlobalLogging logging;
使用GlobalLogging 采集的方式很简单,我们只需要注入GlobalLogging
对象,使用该接口提供的方法即可,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 @RestController @RequestMapping(value = "/user") public class UserController { @Autowired private GlobalLogging logging; @GetMapping(value = "/name") public String getUserName () { logging.debug("这是一条debug级别的日志" ); logging.info("这是一条info级别的日志" ); return "用户名:恒宇少年" ; } }
当我们调用GlobalLogging
提供的不同日志等级的方法时,会自动将日志相关信息写入到GlobalLoggingThreadLocal
的集合内,等到上报请求日志时一并提交给Logging Admin
,由Logging Admin
进行保存。
GlobalLogging表结构 GlobalLogging
相关的全局日志采集到Logging Admin
需要进行保存,所有对应添加了一个名为logging_global_logs
信息表,结构如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 CREATE TABLE `logging_global_logs` ( `lgl_id` varchar (36 ) COLLATE utf8mb4_general_ci NOT NULL COMMENT '日志主键' , `lgl_request_log_id` varchar (36 ) COLLATE utf8mb4_general_ci NOT NULL COMMENT '请求日志编号,关联logging_request_logs主键' , `lgl_level` varchar (20 ) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '日志等级' , `lgl_content` mediumtext COLLATE utf8mb4_general_ci COMMENT '日志内容' , `lgl_caller_class` varchar (200 ) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '日志输出的类名' , `lgl_caller_method` varchar (50 ) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '日志输出的方法名称' , `lgl_caller_code_line_number` int (11 ) DEFAULT NULL COMMENT '日志输出的代码行号' , `lgl_exception_stack` mediumtext COLLATE utf8mb4_general_ci COMMENT 'error等级的日志异常堆栈信息' , `lgl_create_time` mediumtext COLLATE utf8mb4_general_ci COMMENT '日志发生时间' , PRIMARY KEY (`lgl_id`), KEY `logging_global_logs_logging_request_logs_lrl_id_fk` (`lgl_request_log_id`), CONSTRAINT `logging_global_logs_logging_request_logs_lrl_id_fk` FOREIGN KEY (`lgl_request_log_id`) REFERENCES `logging_request_logs` (`lrl_id`) ) ENGINE= InnoDB DEFAULT CHARSET= utf8mb4 COLLATE = utf8mb4_general_ci COMMENT= '全局日志信息表' ;
采集Exception堆栈信息 使用GlobalLogging
可以采集遇到异常的详细堆栈信息,可以让我们直接定位到问题出现的位置,在第一时间解决出现的问题,具体使用如下所示:
1 2 3 4 5 try { int a = 5 / 0 ; } catch (Exception e) { logging.error(e.getMessage(), e); }
运行测试 我们来运行本章的源码,看下日志采集的效果。
输出的采集日志 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 { "endTime" : 1576561372604 , "globalLogs" : [ { "callerClass" : "org.minbox.chapter.user.service.UserController" , "callerCodeLineNumber" : 33 , "callerMethod" : "getUserName" , "content" : "这是一条debug级别的日志,发生时间:{}" , "createTime" : 1576561372585 , "level" : "debug" } , { "callerClass" : "org.minbox.chapter.user.service.UserController" , "callerCodeLineNumber" : 34 , "callerMethod" : "getUserName" , "content" : "这是一条info级别的日志,发生时间:1576561372586" , "createTime" : 1576561372586 , "level" : "info" } , { "callerClass" : "org.minbox.chapter.user.service.UserController" , "callerCodeLineNumber" : 43 , "callerMethod" : "aa" , "content" : "出现了异常." , "createTime" : 1576561372586 , "exceptionStack" : "java.lang.ArithmeticException: / by zero\n\tat org.minbox.chapter.user.service.UserController.aa(UserController.java:41)\n\tat org.minbox.chapter.user.service.UserController.getUserName(UserController.java:35)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)\n\t...." , "level" : "error" } ] , "httpStatus" : 200 , "requestBody" : "" , "requestHeaders" : { "accept" : "*/*" , "host" : "localhost:10000" , "user-agent" : "curl/7.64.1" } , "requestIp" : "0:0:0:0:0:0:0:1" , "requestMethod" : "GET" , "requestParam" : "{}" , "requestUri" : "/user/name" , "responseBody" : "用户名:恒宇少年" , "responseHeaders" : { } , "serviceId" : "user-service" , "serviceIp" : "127.0.0.1" , "servicePort" : "10000" , "spanId" : "41a0c852-812b-4a2e-aa4a-228b87ce52f6" , "startTime" : 1576561372577 , "timeConsuming" : 27 , "traceId" : "42ca9f5a-5977-49cf-909d-a23614a47a6b" }
上面是控制台输出的一个请求日志的详细内容,其中globalLogs
字段就是我们采集的全局日志列表。
存储的采集日志 我们来确认下采集日志上报到Logging Admin
后是否保存到了logging_global_logs
日志表内,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 mysql> select * from logging_global_logs order by lgl_create_time asc \G; * * * * * * * * * * * * * * * * * * * * * * * * * * * 1. row * * * * * * * * * * * * * * * * * * * * * * * * * * * lgl_id: 112e36 ff- e781-4 f11-8 f21-2196823 cde38 lgl_request_log_id: f91382e2-2 d79-499 e- b1df-4757 c1ffdbc5 lgl_level: info lgl_content: 这是一条info级别的日志,发生时间:1576561856333 lgl_caller_class: org.minbox.chapter.user.service.UserController lgl_caller_method: getUserName lgl_caller_code_line_number: 34 lgl_exception_stack: NULL lgl_create_time: 1576561856333 * * * * * * * * * * * * * * * * * * * * * * * * * * * 2. row * * * * * * * * * * * * * * * * * * * * * * * * * * * lgl_id: f1d172a6-5 cce-4 df0- bc29- fe27ac441089 lgl_request_log_id: f91382e2-2 d79-499 e- b1df-4757 c1ffdbc5 lgl_level: debug lgl_content: 这是一条debug级别的日志,发生时间:{} lgl_caller_class: org.minbox.chapter.user.service.UserController lgl_caller_method: getUserName lgl_caller_code_line_number: 33 lgl_exception_stack: NULL lgl_create_time: 1576561856333 * * * * * * * * * * * * * * * * * * * * * * * * * * * 3. row * * * * * * * * * * * * * * * * * * * * * * * * * * * lgl_id: d95d850d-3 bc9-4689 -928 a-32 c6089ff7a2 lgl_request_log_id: f91382e2-2 d79-499 e- b1df-4757 c1ffdbc5 lgl_level: error lgl_content: 出现了异常. lgl_caller_class: org.minbox.chapter.user.service.UserController lgl_caller_method: getUserName lgl_caller_code_line_number: 38 lgl_exception_stack: java.lang.ArithmeticException: / by zero at org.minbox.chapter.user.service.UserController.getUserName(UserController.java:36 ) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method ) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62 ) ... lgl_create_time: 1576561856334 3 rows in set (0.01 sec)
这里异常的堆栈信息比较多,做出了省略。
敲黑板,划重点 本章把GlobalLog
全局日志的概念进行了详细的描述,建议将一些重要逻辑判断性质的GlobalLog
进行采集上报,防止logging_global_logs
表内的数据量过大。
详细的使用方式请参考本章的源码。
代码示例 如果您喜欢本篇文章请为源码仓库点个Star
,谢谢!!! 本篇文章示例源码可以通过以下途径获取,目录为apiboot-logging-use-global-log
: