乐趣区

关于微服务:微服务开发系列如何打印好日志

日志对一个零碎来说是十分重要的。

我遇到过很多问题,找日志齐全是无迹可寻,甚至前端很多申请发现非常耗时,最初一通查下来,都不晓得工夫到底耗在哪里,而后问题也无奈复现了,只能通过一直的亡羊补牢加日志判断问题到底出在哪里。

尽管有 arthas 这样很有想法的工具,然而这种工具你不肯定能用,个别是用来排查比拟诡异的问题,还有就是工夫很长了之后,很难通过 arthas 去还原进去过后的状况,而且就算你能还原,网络稳定的问题能还原吗?

所以间接通过日志去排查是最好的,最间接不便,个别不会有人阻止你去看日志,作为一个开发人员,要想不加班,肯定要多加日志。

当然增加日志也要荒诞不经,出现异常问题才去报告,日志要有法则有迹可循,管制日志的输入放弃在正当的范畴之内,有些开发人员的日志,多细节的货色都输入,后果造成了日志文件非常微小,因而最好还是能只打印最无效的日志。

只打印最无效的日志,在一直测试的过程中优化对日志输出,不仅仅体现了对日志的器重,还可能帮忙开发者在开发的过程中梳理逻辑思路,使表白更为通顺,并且在将来对这段代码优化重构时,也能加深本人的了解,重构出更优良的模块。

1 日志输入

框架中的日志输入全都是应用日志文件,并没有应用带有第三方服务的日志框架,因为不想在多加任何服务了。

也没有应用任何数据库去记录日志,因为感觉文件就能够了,只有纯熟使用 linux 的过滤命令,查问题是很快的。

2 框架日志配置

框架的日志应用的是 spring boot 自身的配置,并没有做相似用 spring-logback.xml 这种配置。

一个是因为感觉麻烦,还有一个就是 spring 提供的日志配置曾经齐全足够应用了。

日志的配置对立应用 nacos config 中配置的 logging.yml

logging:
  pattern:
    console: '%clr(%d{${LOG_DATEFORMAT_PATTERN:yyyy-MM-dd HH:mm:ss.SSS}}){faint} %clr(${LOG_LEVEL_PATTERN:%5p}) %clr(${PID:}){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint}%clr(%X{REQUEST_ID}){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:%wEx}'
    file: '%d{${LOG_DATEFORMAT_PATTERN:yyyy-MM-dd HH:mm:ss.SSS}} ${LOG_LEVEL_PATTERN:%5p} ${PID:} --- [%t] %-40.40logger{39} :%X{REQUEST_ID} %m%n${LOG_EXCEPTION_CONVERSION_WORD:%wEx}'
  file:
    name: ${SERVER_COMMON.BASE:${user.home}/server-common}/logs/server/${spring.application.name}/${spring.application.name}.log
    max-history: 20
    max-size: 50MB
  level:
    root: warn
    cn.framework: debug
    cn.gateway: debug
    cn.business: debug

外面的配置项,全都是 spring 自带的,该配置任何我的项目都必须援用,全副的日志门路都对立在一个地位。

3 日志打包格局

spring boot 默认的日志框架配置,日志在打包时,格局为 gz 格局,十分举荐这种格局。

应用 gz 的益处是,linux 蕴含了自带的 zgrep 命令能够间接搜寻打包后的文件,如果你应用 zip 格局打包,那么在你想查找历史日志时,你必须把 zip 文件解压。

因而 gz 格局能很不便的让运维人员查找问题。

4 日志 pattern

下面的配置应用了 logging.pattern.console 和 logging.pattern.file 配置了日志的输入格局,间接拷贝过去的,除了多了一个 REQUEST_ID 的占位符,其它齐全与原来的格局雷同。

4.1 Request-Id

Request-Id  是 spring cloud gateway 给每个申请都产生的一个惟一 ID,相似于主键一样。

框架外面受到了这个的启发,把 Request-Id 传递给了两个中央

  1. 上游零碎
  2. http response header 中

4.1.1 传到上游零碎

传给上游零碎就是为了让下面日志配置的占位符失效,具体的实现形式在 framework:cn.framework.config.logging.MvcLoggingConfigurer 类中。

这样配合 gateway:cn.gateway.framework.filter.LoggingFilter 就能把一个申请所有的日志都集齐了。

LoggingFilter.java 打印了一个申请最实在的耗时工夫,从发动申请到返回后果,这样就能立马判断出一个申请耗时高到底是哪里出了问题。

MvcLoggingConfigurer.java 就让下面的占位符失效,只有是单线程打印的日志,都会带上 Request-Id,如果开发人员灵便一些,也能把 Request-Id 带上放在并发申请外面,更近一步打印 Request-Id+ThreadId,这样问题就很好查找了。

4.1.2 传到前端

这样的目标是,不便在前端应用接口处问题时,可能通过 network 申请中看到的 Request-Id,给到后端,外面查到所有相干的日志。

前端能够抉择在判断申请呈现问题时,就把 Request-Id 打印在 console 外面,这样不必等到再次关上 F12 浏览器控制台去复现谬误,万一复现不进去就很有用了。

因为 console 的输入不会因为浏览器控制台的关上敞开而隐没。

5 日志输入标准

在框架中打印日志,请尽量恪守上面几点:

  1. 日志中不能应用中文,这是因为零碎面临的环境是不可控的,随时可能让中文日志变成乱码,应用英文和数字是最稳当的
  2. 尽量不要应用过多符号,简单过多的符号在生产环境并不能无效的进步浏览效率,反而造成 linux 下文件管道过滤时的麻烦
  3. 一个重要的办法内尽量蕴含惟一标识,比方性能名称,这样过滤日志更不便
  4. 审慎抉择输入日志,防止打印大量、反复的日志,造成空间节约和排查艰难

6 HTTP 申请耗时

gateway:cn.gateway.framework.filter.LoggingFilter 除了打印了所有申请的耗时,也将申请的耗时,传给了前端的 http response header 中,参数名是 Cost

这样做有一个益处,就是前端在发现申请较慢时,就能通过 network 的申请耗时和 Cost 作比照,如果 network >> Cost,阐明网络自身就慢。

前端最好在判断耗时过长时,把 Cost 打印在 F12 浏览器控制台中。

7 Log 对象生成

因为 kotlin 中不能再应用任何 lombok 的注解,因而 Log 变量也不能通过注解的形式间接生成。

于是通过了一段时间在网上的摸索,有一种更加不便的获取 Log 变量的形式,实现在类 cn.vte.framework.common.log.Slf4k 中。

实现的逻辑是 kotlin 的变量扩大加上 reified 泛型固定,想要获取 Log 变量时,只须要引入即可。

Log 对象并不是应用的惯例 Slf4jLog,而是应用的 [KotlinLogging](https://github.com/MicroUtils/kotlin-logging) 封装后的 Log,相比于个别 Log 对象,多反对了一些 kotlin 的个性。

8 http body 获取

框架中提供了一种获取 requestBodyresponseBody 的形式。

可能在不影响申请流的状况下,获取的申请的 body。

实现形式参考了 spring cloud 的 ModifyResponseBodyGatewayFilterFactory ModifyRequestBodyGatewayFilterFactory

开启形式,是须要再 gateway 下的 bootstrap.yml 中,路由配置下减少 filter。

LoggingFilter 中,尽管获取到了,然而并没有打印,如果有须要能够自行打印进去。

本文参加了思否技术征文,欢送正在浏览的你也退出。

退出移动版