Spring Boot应用OpenTelemetry结构化JSON日志问题及实现咨询
Spring Boot + OpenTelemetry 结构化日志解决方案(适配Loki)
一、修复OpenTelemetry JSON结构化日志输出
1. 依赖配置
添加OpenTelemetry JSON日志导出器和Logback适配依赖(以Maven为例):
<dependency> <groupId>io.opentelemetry</groupId> <artifactId>opentelemetry-exporter-logging-json</artifactId> <version>1.32.0</version> <!-- 建议使用最新稳定版本 --> </dependency> <dependency> <groupId>io.opentelemetry.instrumentation</groupId> <artifactId>opentelemetry-logback-appender-1.0</artifactId> <version>1.32.0</version> </dependency>
2. Logback配置
修改logback-spring.xml,替换默认控制台输出为OpenTelemetry JSON Appender:
<configuration> <!-- OpenTelemetry JSON日志输出Appender --> <appender name="OTEL-JSON" class="io.opentelemetry.instrumentation.logback.appender.v1_0.OpenTelemetryAppender"> <encoder class="io.opentelemetry.exporter.logging.json.JsonLogEncoder"/> </appender> <!-- 根日志级别配置 --> <root level="INFO"> <appender-ref ref="OTEL-JSON"/> </root> </configuration>
3. OpenTelemetry基础配置
在application.yml中指定日志导出器和服务标识:
opentelemetry: logs: exporter: logging resource: attributes: service.name: your-service-name # 替换为你的服务名称
二、请求/响应/请求头的结构化日志采集
方式1:基于Servlet Instrumentation自动采集
通过OpenTelemetry Servlet工具包自动捕获请求上下文,同时自定义Filter补充请求头、响应信息:
添加依赖
<dependency> <groupId>io.opentelemetry.instrumentation</groupId> <artifactId>opentelemetry-servlet-3.0</artifactId> <version>1.32.0</version> <scope>runtime</scope> </dependency>
自定义日志Filter
创建Filter捕获请求头、响应状态等信息,并附加到OpenTelemetry Span属性中(最终会被结构化日志包含):
import io.opentelemetry.api.trace.Span; import jakarta.servlet.*; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; public class RequestResponseLoggingFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; HttpServletResponse res = (HttpServletResponse) response; // 绑定当前请求的Span Span currentSpan = Span.current(); // 追加请求属性 currentSpan.setAttribute("request.method", req.getMethod()); currentSpan.setAttribute("request.path", req.getRequestURI()); currentSpan.setAttribute("request.user_agent", req.getHeader("User-Agent")); currentSpan.setAttribute("request.content_type", req.getHeader("Content-Type")); // 按需添加其他需要采集的请求头 chain.doFilter(request, response); // 追加响应属性 currentSpan.setAttribute("response.status_code", res.getStatus()); currentSpan.setAttribute("response.content_length", res.getContentLength()); } }
注册Filter
在Spring配置类中注册该Filter:
import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class FilterConfig { @Bean public FilterRegistrationBean<RequestResponseLoggingFilter> requestResponseLoggingFilter() { FilterRegistrationBean<RequestResponseLoggingFilter> registrationBean = new FilterRegistrationBean<>(); registrationBean.setFilter(new RequestResponseLoggingFilter()); registrationBean.addUrlPatterns("/*"); // 匹配所有请求路径 return registrationBean; } }
方式2:手动构建结构化日志事件
如果需要更灵活的日志内容控制,直接使用OpenTelemetry Logger API手动创建包含请求/响应信息的JSON日志:
import io.opentelemetry.api.logs.Logger; import io.opentelemetry.api.logs.LoggerProvider; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.stereotype.Component; import java.util.HashMap; import java.util.Map; @Component public class RequestResponseLogger { private final Logger logger; public RequestResponseLogger(LoggerProvider loggerProvider) { this.logger = loggerProvider.getLogger("RequestResponseLogger"); } public void logRequestDetails(HttpServletRequest request, HttpServletResponse response) { Map<String, Object> logAttributes = new HashMap<>(); logAttributes.put("request.method", request.getMethod()); logAttributes.put("request.path", request.getRequestURI()); logAttributes.put("request.user_agent", request.getHeader("User-Agent")); logAttributes.put("response.status_code", response.getStatus()); // 按需添加其他字段 logger.logBuilder() .setBody("Request processed successfully") .addAttributes(logAttributes) .emit(); } }
之后可在Controller、Interceptor或Filter中调用该Logger输出日志。
三、适配Loki与Grafana可视化
Loki原生支持JSON日志解析,只需在Promtail采集配置中开启JSON解析规则,即可将结构化日志字段转为Loki标签或可查询字段:
修改promtail.yml的采集配置:
scrape_configs: - job_name: "spring-boot-service" static_configs: - targets: ["localhost"] labels: job: "spring-boot-service" __path__: "/var/log/your-service/*.log" # 替换为你的服务日志路径 pipeline_stages: # 解析JSON日志字段 - json: expressions: timestamp: timestamp message: body service_name: service.name request_method: attributes.request.method request_path: attributes.request.path response_status: attributes.response.status_code # 将关键字段转为Loki标签 - labels: service_name: request_method: response_status: # 配置时间戳解析 - timestamp: source: timestamp format: RFC3339Nano
配置完成后,在Grafana中即可通过Loki数据源,基于标签和字段进行日志查询、过滤与可视化。
内容的提问来源于stack exchange,提问作者Senthilkumar Dhanapal
相关产品推荐
相关产品推荐

