irpas技术客

Kubernetes下SpringBoot应用日志解决方案(JSON日志&Loki)_k8s deployment springboot 日志_rele

未知 7142

应用容器化部署已经成为一个趋势,依托容器云自动调度平台(如k8s)能够快速实现应用的扩容和发布,本文简要介绍了在Kubernetes平台上,SpringBoot应用日志的一种解决方案。方案依托平台优势,优雅、简介、快速的实现应用日志的采集和分析。同时,对生产环境下日志的输出,详细介绍了生产环境下采用JSON格式输出日志配置全过程。

一、目标 依托Kubernetes平台日志采集管理能力(Loki + Promtail的云原生日志收集方案),将应用日志也纳入综合管理。生产环境采用JSON输出简化日志解析,使得日志的后续处理、分析或查询变得方便高效,开发测试环境仍然扁平化输出自定义JSON日志输出内容,微服务环境下日志包含链路信息。 二、应用日志架构设计

2.1 概要

本设计方案是在Kubernetes环境下,通过集成日志工具Loki+Promtail,使得容器云环境能够自动化采集集群内各Pod日志。Grafana作为可视化终端,通过链接Loki数据源,能够对采集的日志进行搜索和分析。其中:

Promtail: 日志收集工具,类比ELK中的LogstashLoki: 日志聚合工具,类似ELK中ElasticsearchGrafana:可视化工具,类比ELK中Kibana

应用通过输出日志到控制台,Promtail实时采集应用输出到控制台的日志,并发送至Loki。

这种方案

2.2 方案优势 轻量化

与ELK相比,大大减少了硬件资源的使用。适合中小集群监控。

与k8s原生结合

日志搜索可以通过k8s中资源label标签进行筛选。

三、实施 3.1 前置条件 3.1.1 环境准备 Kubernetes集群环境Loki+Promtail+Grafana已集成到Kubernetes,并且能够采集到Pod日志Spring Boot应用已部署到Kubernetes 3.2 日志JSON处理

**Logstash Logback Encoder **开源项目提供了Logback JSON encoder 和 appenders,这个类库最新详细用法参考项目文档介绍

FormatProtocolFunctionLoggingEventAccessEventLogstash JSONSyslog/UDPAppenderLogstashUdpSocketAppenderLogstashAccessUdpSocketAppenderLogstash JSONTCPAppenderLogstashTcpSocketAppenderLogstashAccessTcpSocketAppenderanyanyAppenderLoggingEventAsyncDisruptorAppenderAccessEventAsyncDisruptorAppenderLogstash JSONanyEncoderLogstashEncoderLogstashAccessEncoderLogstash JSONanyLayoutLogstashLayoutLogstashAccessLayoutGeneral JSONanyEncoderLoggingEventCompositeJsonEncoderAccessEventCompositeJsonEncoderGeneral JSONanyLayoutLoggingEventCompositeJsonLayoutAccessEventCompositeJsonLayout

根据文档说明,我们使用 LoggingEventCompositeJsonEncoder 来自定义Json Encoder。下面开始实战配置。

集成maven依赖

来源文档 https://github.com/logfellow/logstash-logback-encoder#including-it-in-your-project

<dependency> <groupId>net.logstash.logback</groupId> <artifactId>logstash-logback-encoder</artifactId> <version>7.2</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-access</artifactId> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> </dependency> 配置logback

在资源文件夹中创建logback-spring.xml文件,默认情况下将所有日志从控制台输出。

<?xml version="1.0" encoding="UTF-8"?> <configuration scan="true" scanPeriod="5 seconds"> <springProperty scope="context" name="appName" source="spring.application.name" defaultValue="unknown" /> <!-- ConsoleAppender:把日志输出到控制台 --> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder"> <!--关键输出配置到这里--> ... </encoder> </appender> <!-- 控制台输出日志级别 --> <root level="INFO"> <appender-ref ref="STDOUT"/> </root> </configuration> 自定义LoggingEventCompositeJsonEncoder

配置说明:https://github.com/logfellow/logstash-logback-encoder#composite-encoderlayout

<encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder"> <providers> <timestamp> <timeZone>UTC+8</timeZone> </timestamp> <pattern> <omitEmptyFields>true</omitEmptyFields> <pattern> { "timestamp": "%date{ISO8601}", "service": "${appName}", "level": "%level", "pid": "${PID:-}", "thread": "%thread", "class": "%logger{60}", "method": "%method", "line": "%line", "message": "#tryJson{%message}" } </pattern> </pattern> <stackTrace> <throwableConverter class="net.logstash.logback.stacktrace.ShortenedThrowableConverter"> <maxDepthPerThrowable>100</maxDepthPerThrowable> <maxLength>20480</maxLength> <rootCauseFirst>true</rootCauseFirst> </throwableConverter> </stackTrace> </providers> </encoder>

示例说明:

omitEmptyFields:在日志中省略空的字符,详细官方配置说明pattern:json日志输出模版,详细官方配置说明stackTrace:异常堆栈信息输出配置,详细官方配置说明#tryJson{%message}: 对于message可以进行json转译,则输出json,否则该字段输出文本串,详细官方配置说明

配置完成后,从控制台打印的日志

3.3 自定义日志信息

除了默认的日志输出内容外,在web应用场景下,我们希望将用户请求时来源IP和请求编号记录到日志中。

Mapped Diagnostic Context (MDC) 是Slf4j提供的一个API,主要功能就是在多线程环境下进行日志调用链路跟踪,使用起来也简单。

3.3.1 实现思路 通过在SpringBoot中定义拦截器,获取web请求的IP,初始化请求编号在logback中定义日志输出,打印mdc附带的信息 3.3.2 代码实现 拦截器配置MDC

在Spring中定义拦截器的过程较为简单

import lombok.extern.slf4j.Slf4j; import org.slf4j.MDC; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.UUID; @Slf4j @Component public class LogInterceptor implements HandlerInterceptor { private final static String REQUEST_ID = "requestId"; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String xForwardedForHeader = request.getHeader("X-Forwarded-For"); String remoteIp = request.getRemoteAddr(); String uuid = UUID.randomUUID().toString(); log.info("put requestId ({}) to logger", uuid); log.info("request id:{}, client ip:{}, X-Forwarded-For:{}", uuid, remoteIp, xForwardedForHeader); MDC.put(REQUEST_ID, uuid); MDC.put("remoteIp", remoteIp); return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { String uuid = MDC.get(REQUEST_ID); log.info("remove requestId ({}) from logger", uuid); MDC.remove(REQUEST_ID); HandlerInterceptor.super.postHandle(request, response, handler, modelAndView); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { HandlerInterceptor.super.afterCompletion(request, response, handler, ex); } }

在preHandle方法中,获取remoteIp并放入MDC中,同时初始化了请求ID,这里使用的是uuid。

注册拦截器到Spring

SpringMvc注册拦截器,不多解释,主要代码如下:

@Configuration @RequiredArgsConstructor public class WebMvcConfig implements WebMvcConfigurer { private final LogInterceptor logInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(logInterceptor); } } 配置MDC输出到日志

修改logback-spring.xml配置输出模版, 通过添加输出项"requestId": "%mdc{requestId}" 和 "remoteIP": "%mdc{remoteIp}"到模版

<pattern> { "timestamp": "%date{ISO8601}", ... "requestId": "%mdc{requestId}", "remoteIP": "%mdc{remoteIp}", ... "message": "#tryJson{%message}" } </pattern>

重新部署应用,观察日志输出:

可以看到,日志输出中,已经包含了我们在mdc中自定义的属性。

3.4 日志多环境配置

使用多环境配置,在当前解决方案下,主要用来实现,生产环境日志输出JSON格式,开发环境日志输出采用默认的行日志,多环境配置比较简单,通过定义springProfile标签,name属性为环境名称,配置如下:

<springProfile name="default"> <root level="INFO"> <appender-ref ref="STDOUT"/> </root> </springProfile> <springProfile name="kubernetes"> <root level="INFO"> <appender-ref ref="PROD-STDOUT"/> </root> </springProfile> 3.5 链路信息输出

在分布式环境下,应用之间的调用链路信息,我们希望也集成到JSON日志输出中,例如是使用Spring-Cloud-Sleuth,需要新增额外的链路信息到模版中,需要注意的是在Sleuth 3.0中,属性名称已经发生了一些变化。参考文档: https://github.com/spring-cloud/spring-cloud-sleuth/wiki/Spring-Cloud-Sleuth-3.0-Migration-Guide#x-b3–mdc-fields-names-are-no-longer-set

3.6 日志采集监控

上图是Grafana集成Loki后日志查询的搜索页面,支持JSON格式化输出。

3.7 日志搜索与分析

LogQL是Grafana Loki的promql启发的查询语言。https://grafana.com/docs/loki/latest/logql/

它提供了2种查询能力:

查询返回的日志行对查询结果进行统计计算 四、完整配置

本方案 logback-spring.xml

<?xml version="1.0" encoding="UTF-8"?> <configuration scan="true" scanPeriod="5 seconds"> <springProperty scope="context" name="appName" source="spring.application.name" defaultValue="unknown"/> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %green(%-5level) %blue(%property{PID}) --- [%thread] %cyan(%-50logger{50}) : %msg%n</pattern> </encoder> </appender> <appender name="PROD-STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder"> <providers> <timestamp> <timeZone>UTC+8</timeZone> </timestamp> <pattern> <omitEmptyFields>true</omitEmptyFields> <pattern> { "timestamp": "%date{ISO8601}", "requestId": "%mdc{requestId}", "remoteIP": "%mdc{remoteIp}", "service": "${appName}", "level": "%level", "pid": "${PID:-}", "trace": "%X{X-B3-TraceId:-}", "span": "%X{X-B3-SpanId:-}", "parent": "%X{X-B3-ParentSpanId:-}", "thread": "%thread", "class": "%logger{60}", "method": "%method", "line": "%line", "message": "#tryJson{%message}" } </pattern> </pattern> <stackTrace> <throwableConverter class="net.logstash.logback.stacktrace.ShortenedThrowableConverter"> <maxDepthPerThrowable>100</maxDepthPerThrowable> <maxLength>20480</maxLength> <rootCauseFirst>true</rootCauseFirst> </throwableConverter> </stackTrace> </providers> </encoder> </appender> <springProfile name="default"> <root level="INFO"> <appender-ref ref="STDOUT"/> </root> </springProfile> <springProfile name="kubernetes"> <root level="INFO"> <appender-ref ref="PROD-STDOUT"/> </root> </springProfile> </configuration>
参考 logstash-logback-encoder官方手册Spring Boot中实现logback多环境日志配置自定义MDCLogQL语法


1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,会注明原创字样,如未注明都非原创,如有侵权请联系删除!;3.作者投稿可能会经我们编辑修改或补充;4.本站不提供任何储存功能只提供收集或者投稿人的网盘链接。

标签: #k8s #deployment #springboot #日志