微服务架构项目实战将日志输出至ELK编码实践

日志中心搭建好了,演示代码也编写完成。下面笔者拓展讲解一下在本书最终的微服务架构实战项目中进行配置的过程(实战项目的介绍和搭建将在第 14 章中详细介绍),把微服务架构项目中各个服务实例的日志信息输出到 ELK 日志中心,也就是在微服务实例和 ELK 日志中心之间搭建管道。

微服务架构项目中的日志输出配置

  1. 引入 logstash-logback-encoder 依赖。

    依次打开 newbee-mall-cloud-user-webnewbee-mall-cloud-recommend-webnewbee-mall-cloud-order-webnewbee-mall-cloud-shop-cart-webnewbee-mall-cloud-goods-web 五个微服务实例工程中的 pom.xml 文件,在 dependencies 节点下新增 logstash-logback-encoder 的依赖项,配置代码如下:

    <dependency>
      <groupId>net.logstash.logback</groupId>
      <artifactId>logstash-logback-encoder</artifactId>
      <version>7.0.1</version>
    </dependency>
  2. 添加 Logback 日志配置文件。

    依次打开 newbee-mall-cloud-user-webnewbee-mall-cloud-recommend-webnewbee-mall-cloud-order-webnewbee-mall-cloud-shop-cart-webnewbee-mall-cloud-goods-web 五个微服务实例工程,在 src/main/resources 目录下创建 logback.xml 配置文件,代码如下:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE configuration>
    <configuration>
        <include resource="org/springframework/boot/logging/logback/defaults.xml"/>
        <include resource="org/springframework/boot/logging/logback/console-appender.xml"/>
    
        <!-- 应用名称 -->
        <property name="APP_NAME" value="newbee - mall - cloud - order - service - log"/>
        <contextName>${APP_NAME}</contextName>
    
        <!-- 控制台的日志输出样式 -->
        <property name="CONSOLE_LOG_PATTERN"
                  value="%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}"/>
    
        <!-- 控制台输出 -->
        <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
            <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
                <level>INFO</level>
            </filter>
            <encoder>
                <pattern>${CONSOLE_LOG_PATTERN}</pattern>
                <charset>utf8</charset>
            </encoder>
        </appender>
    
        <!-- 输出到Logstash -->
        <appender name="LOGSTASH" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
            <destination>192.168.110.57:4560</destination>
            <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
                <level>INFO</level>
            </filter>
            <encoder charset="UTF-8" class="net.logstash.logback.encoder.LogstashEncoder"/>
        </appender>
    
        <root level="INFO">
            <appender-ref ref="CONSOLE"/>
            <appender-ref ref="LOGSTASH"/>
        </root>
    </configuration>

    配置信息基本一致,只是 APP_NAME 参数有差异。管道搭建好了,接下来启动项目测试一下,结果如图 13-23 所示。

    image 2025 04 18 15 40 28 080
    Figure 1. 图13-23 启动项目测试结果

    是的,又报错了,错误信息贴在下面了。

    23:45:46,970 |-ERROR in ch.qos.logback.classic.joran.action.ContextNameAction - Failed to rename context [newbee-mall-cloud-order-service-log] as [nacos]
    java.lang.IllegalStateException: Context has been already given a name
    	at java.lang.IllegalStateException: Context has been already given a name

    报错的原因并不复杂,依赖冲突导致的。

    Spring Boot 框架中已经集成了日志框架 Logback,而项目依赖 nacos-client 中也配置了 Logbacknacos-client 中的 Logback 加载要优先于项目自身的 Logback 框架),在一个项目中 context_name 只能定义一次,因此在项目启动时,nacos-client 中的 Logback 加载完成后,再加载项目本身的 Logback 就出现了冲突。

    这个报错并不影响使用,但是最好处理掉,只需要在启动类中增加如下代码:

    System.setProperty("nacos.logging.default.config.enabled", "false");

    只加载自定义的 Logback 配置,不使用 nacos-client 依赖中的配置,这样就不会冲突了。因此,需要在 newbee-mall-cloud-user-webnewbee-mall-cloud-recommend-webnewbee-mall-cloud-order-webnewbee-mall-cloud-shop-cart-webnewbee-mall-cloud-goods-web 五个微服务实例工程下的启动类中添加以上代码。

  3. 新增日志输出的测试代码。

    这里主要是为了模拟平时 Error 日志的输出,以及测试在 Kibana 中查询日志,在 newbee-mall-cloud-goods-web 工程的 NewBeeMallGoodsController 类中新增如下代码:

    @GetMapping("/test1")
    public Result<String> test1() throws BindException {
        throw new BindException(1, "BindException");
    }
    
    @GetMapping("/test2")
    public Result<String> test2() throws NewBeeMallException {
        NewBeeMallException.fail("NewBeeMallException");
        return ResultGenerator.genSuccessResult("test2");
    }
    
    @GetMapping("/test3")
    public Result<String> test3() throws Exception {
        int i = 1 / 0;
        return ResultGenerator.genSuccessResult("test2");
    }

    因为之前已经在全局异常处理类中配置了异常的拦截和日志输出,所以在浏览器的地址栏中访问上述代码中的地址,就会直接输出三条 Error 级别的测试日志。

通过Kibana查询日志

  1. 查看日志

    微服务工程中的实例启动后,日志都可以在 Kibana 中查看。如果想仔细验证,可以在搜索框中输入关键词进行更精准的匹配。比如,笔者分别搜索了订单微服务和用户微服务的启动类名称,搜索结果如图 13-24 和图 13-25 所示。

    另外,搜索时一定要注意时间区间,有时搜索不到可能是时间选择不对,页面右上角有时间选择器,单击即可切换。

  2. 日志定时刷新

    当然,有些读者问过笔者:页面里的日志怎么不刷新呢?明明输出了日志且时间选择正确,但是 Kibana 页面就是没有显示。这是因为在默认情况下,Kibana 中的 Discover 页面不会定时刷新,需要手动单击右上角的 “刷新” 按钮。想要设置自动刷新,可以按照图 13-26 中的示意设置 Discover 页面的自动刷新。

    image 2025 04 18 15 48 37 877
    Figure 2. 图13-24 搜索结果1
    image 2025 04 18 15 48 53 417
    Figure 3. 图13-25 搜索结果2
    image 2025 04 18 15 49 12 120
    Figure 4. 图13-26 设置自动刷新
  3. 常用的日志搜索条件

    除输入一些关键词外,还可以根据关键字段搜索日志。比如,直接搜索最近15分钟 Error 级别的日志,就可以在输入框中输入 “level:error” 来搜索,相关的信息就会显示出来,如图 13-27 所示。

    image 2025 04 18 15 49 44 208
    Figure 5. 图13-27 “level:error”搜索结果

    当然,如果想更精确,那就加上一些条件,如最近 15 分钟 GoodsServiceExceptionHandler 输出的 Error 日志就可以在输入框中输入 level:error andlogger_name:"ltd.goods.cloud.newbee.config.GoodsServiceExceptionHandler" 来搜索,相关的信息就会显示出来,如图 13-28 所示。

    image 2025 04 18 15 50 09 070
    Figure 6. 图13-28 精确搜索结果

    除此之外,还可以使用自己在代码中定义的一些字符,如 “mamimamihong”、“zhimakaimen” 等,都是一些自定义的信息,觉得哪里可能会出问题,就输出日志看一下,这样定位问题也更快一些。当然,也不要输出太多日志,没问题了就把一些没用的日志及时删掉。

    笔者在平时上班时,到工位后要做的事是查看邮件和查看负责的业务的错误日志。浏览器中一直开着 Kibana 页面,时不时地刷新一下,有问题赶紧定位并处理。Kibana 真的是企业开发中不可或缺的一个工具。

  4. 根据 traceId 搜索日志

    在前面的章节中,使用 Spring Cloud SleuthZikpin 完成了一套链路追踪系统,可以帮助开发人员串联调用链路中的上下游访问链路,快速定位线上异常出现在哪个环节。不过,仅仅只是日志打标和追踪还不够,想要得到更加详细的信息,还要借助程序中输出的日志信息。这样,链路追踪加上日志中心,整个链路追踪就闭环了,有日志打标、日志收集、日志索引、日志精确搜索、链路可视化和日志的统计报表等。

    这里简单举一个例子。比如,看到 Error 日志后,想要查看上下游服务实例的一些情况,就可以在 Kibana 中把 Error 日志中的 traceIdspanId 作为搜索条件进行查询,查询条件输入 “traceId:xxxxxxxx” 或 “spanId:xxxxxxxx”,或者直接搜索 traceIdspanId 的值也可以,如图 13-29 所示。

    image 2025 04 18 15 50 46 786
    Figure 7. 图13-29 查看上下游服务实例

    拔出萝卜带出泥,与之相关联的一些日志就都显示出来了。

为了课程演示需要及展现出更好的效果,笔者在代码里把一些日志级别调成了 Debug 级别。私下测试时可以这么做,但是在企业开发中千万不能这么做,因为 Debug 级别的日志真的太多了,超出想象的那种量级。ELK 日志中心的整合及配置讲解完成了,整个链路追踪过程和 ELK 日志中心的搭建及整合的知识点也完成了闭环。