Logback日志异步打印接入指南,输出自定义业务数据

背景

随着应用的请求量上升,日志输出量也会成线性比例的上升,给磁盘IO带来压力与性能瓶颈。应用也遇到了线程池满,是因为大量线程卡在输出日志。为了缓解日志同步打印,会采取异步打印日志。这样会引起日志中的追踪id丢失,不能基于追踪id查询相关日志,给问题解决带来新的挑战。

目标

  • 业务数据传递
    • 在日志输出中,业务可以传递用户自定义数据并输出到日志中,并自动构建字段索引,便于快速查询。(包含同步输出)
  • 轻量级接入

技术方案

基于SLF4J日志事件LoggingEvent和映射诊断上下文MDC

  • 在Logback日志事件LoggingEvent implements ILoggingEvent进入日志异步追加器AsyncAppender extends AsyncAppenderBase<ILoggingEvent>的队列blockingQueue之前,把数据状态临时存储到MDC适配器LogbackMDCAdaptermdcPropertyMap线程本地变量副本中。
  • 在组装日志数据前从其取出这些临时的内存数据状态,并组装到最终的日志文本数据中。

具体实现

XxxJsonLayout

package com.xxx.logback;import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.contrib.json.classic.JsonLayout;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.Map;import org.apache.skywalking.apm.toolkit.trace.TraceContext;/*** @author hadesy*/
public class XxxJsonLayout extends JsonLayout {/*** 零时区 UTC 0* 协调世界时(UTC)*/private static final ZoneId ZONE_ID_0 = ZoneId.ofOffset("UTC", ZoneOffset.UTC);/*** 东八区 UTC+8*/private static final ZoneId ZONE_ID_8 = ZoneId.of("Asia/Shanghai");private static final String AT_TIMESTAMP_ATTR_NAME = "@timestamp";@Overrideprotected void addCustomDataToJsonMap(Map<String, Object> map, ILoggingEvent event) {String timestampFormat = Instant.ofEpochMilli(event.getTimeStamp()).atZone(ZONE_ID_8).format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);map.put(TIMESTAMP_ATTR_NAME, timestampFormat);String atTimestampFormat = Instant.ofEpochMilli(event.getTimeStamp()).atZone(ZONE_ID_0).format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);// ES record create timestampmap.put(AT_TIMESTAMP_ATTR_NAME, atTimestampFormat);// log async appender print, app data pass by MDC// 日志异步打印,应用日志数据从MDC传递if (this.isIncludeMDC()) {Map<String, String> mdcPropertyMap = event.getMDCPropertyMap();map.putAll(MdcUtil.applyAsMap(mdcPropertyMap));}String traceId = TraceContext.traceId();// 日志异步打印时,追踪id为空,需要从MDC传递if (!isEmptyTraceId(traceId)) {map.put(MdcUtil.TRACE_ID_KEY, traceId);}}/*** 空的追踪身份*/private static final String EMPTY_TRACE_CONTEXT_ID = "N/A";/*** 忽略的追踪*/private static final String IGNORE_TRACE = "Ignored_Trace";private static boolean isEmptyTraceId(String traceId) {return traceId == null || traceId.isEmpty()|| "N/A".equals(traceId);}
}

MdcUtil

package com.xxx.logback;import java.util.HashMap;
import java.util.Map;
import java.util.Set;import com.google.common.collect.Sets;
import lombok.extern.slf4j.Slf4j;
import org.apache.skywalking.apm.toolkit.trace.TraceContext;
import org.slf4j.MDC;/*** Proxy of {@link MDC}.** @since 2024/4/13*/
@Slf4j
public final class MdcUtil {/*** 追踪身份*/static final String TRACE_ID_KEY = "traceId";public static void setTraceId() {MDC.put(TRACE_ID_KEY, TraceContext.traceId());}public static void setTraceId(String traceId) {MDC.put(TRACE_ID_KEY, traceId);}// 业务过程数据private static final String USER_ID = "userId";private static final String COACH_ID = "coachId";private static final String ADMIN_ID = "adminId";private static final String RESPONSE_TIME = "rt";private static final String RESPONSE_CODE = "code";private static final String API = "api";private static final String REMOTE_APP = "remoteApp";public static void setUserId(Long userId) {MDC.put(USER_ID, "" + userId);}public static void setCoachId(Long coachId) {MDC.put(COACH_ID, "" + coachId);}public static void setAdminId(Long adminId) {MDC.put(ADMIN_ID, "" + adminId);}public static void setResponseTime(long responseTime) {MDC.put(RESPONSE_TIME, Long.toString(responseTime));}public static void setResponseTime(int responseTime) {MDC.put(RESPONSE_TIME, Integer.toString(responseTime));}public static void setResponseCode(int responseCode) {MDC.put(RESPONSE_CODE, Integer.toString(responseCode));}public static void setResponseCode(String responseCode) {MDC.put(RESPONSE_CODE, responseCode);}public static void setApi(String api) {MDC.put(API, api);}public static void setRemoteApp(String remoteApp) {MDC.put(REMOTE_APP, remoteApp);}public static void clear() {MDC.clear();}/*** ES long data type*/private static final Set<String> LONG_DATA_KEY_SET = Sets.newHashSet(USER_ID, COACH_ID, ADMIN_ID, RESPONSE_TIME);public static Map<String, Object> applyAsMap(Map<String, String> mdcPropertyMap) {Map<String, Object> result = new HashMap<>(mdcPropertyMap.size());mdcPropertyMap.forEach((key, value) -> {if (LONG_DATA_KEY_SET.contains(key)) {result.put(key, toLong(value, Long.MIN_VALUE));} else {result.put(key, value);}});return result;}private static long toLong(String str, long defaultValue) {if (str == null) {return defaultValue;} else {try {return Long.parseLong(str, 10);} catch (NumberFormatException e) {log.warn("parse string to long error, str={}", str);return defaultValue;}}}
}

XxxJsonLayoutEncoder

package com.xxx.logback;import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.contrib.jackson.JacksonJsonFormatter;
import ch.qos.logback.core.encoder.LayoutWrappingEncoder;import java.nio.charset.StandardCharsets;public class XxxJsonLayoutEncoder extends LayoutWrappingEncoder<ILoggingEvent> {@Overridepublic void start() {XxxJsonLayout jsonLayout = new XxxJsonLayout();jsonLayout.setContext(context);jsonLayout.setIncludeContextName(false);jsonLayout.setAppendLineSeparator(true);jsonLayout.setJsonFormatter(new JacksonJsonFormatter());jsonLayout.start();super.setCharset(StandardCharsets.UTF_8);super.setLayout(jsonLayout);super.start();}
}

应用如何接入

xxx-spring-boot-starter升级依赖版本

xxx-spring-boot-starter版本是2.7.18

<properties><xxx-spring-boot.version>2.7.18</xxx-spring-boot.version>
</properties><dependencyManagement><dependencies><dependency><groupId>com.spring.boot</groupId><artifactId>xxx-spring-boot-starter</artifactId><version>${xxx-spring-boot.version}</version></dependency></dependencies>
</dependencyManagement>

Logback日志配置

logback-spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration><springProperty scope="context" name="appName" source="spring.application.name"/><include resource="org/springframework/boot/logging/logback/defaults.xml"/><property name="STDOUT_PATTERN" value="%d [%t] %5p %c - %m%n"/><property name="log.name" value="${appName}"/><property name="log.path" value="/home/admin/logs"/><springProperty scope="context" name="appName" source="spring.application.name"/><appender class="ch.qos.logback.core.rolling.RollingFileAppender" name="BIZ_LOG"><encoder class="com.xxx.logback.XxxJsonLayoutEncoder"/><file>${log.path}/${log.name}.log</file><rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy"><fileNamePattern>${log.path}/${log.name}_%i.log</fileNamePattern><maxIndex>1</maxIndex></rollingPolicy><triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy"><maxFileSize>100MB</maxFileSize></triggeringPolicy></appender><!-- report日志异步打印appender --><appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender"><!-- 不丢失日志(默认discardingThreshold=queueSize/5,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 --><discardingThreshold>0</discardingThreshold><!-- 默认队列深度,该值会影响性能.默认值256 --><queueSize>256</queueSize><!-- 当队列满了之后,后面阻塞的线程想要打印的日志就直接被丢弃,从而线程不会阻塞,但有可能会丢失日志--><neverBlock>true</neverBlock><appender-ref ref="BIZ_LOG"/></appender><logger name="report" level="info" additivity="false"><appender-ref ref="ASYNC"/></logger><root level="INFO"><appender-ref ref="ASYNC"/></root></configuration>

传递业务自定义数据到日志

使用MdcUtil传递用户id、教练id、优惠券id、商品id、交易订单id、支付订单id、物流订单id、api、responseTime、responseCode、追踪id等,从用户、教练、营销、商品、交易、物流等维度观测用户的实操路径。

以Dubbo Filter举例

@Activate(group = CommonConstants.PROVIDER, order = 1)
public class DubboAccessLogFilter implements Filter {private static final Logger REPORT_LOG = LoggerFactory.getLogger("report");@Overridepublic Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {JSONObject logInfo = new JSONObject();// ...try {// 在日志输出前设置过程数据到MDC// 异步输出日志时,才需要设置MdcUtil.setTraceId();// 可选-同步/异步MdcUtil.setUserId(userId);MdcUtil.setCoachId(coachId);MdcUtil.setApi(api);MdcUtil.setResponseTime(responseTime);MdcUtil.setResponseCode(responseCode);// ...Result result = invoker.invoke(invocation);// ...return result;} finally {REPORT_LOG.info(logInfo.toJSONString());// 资源清理,需要放在日志打印后面MdcUtil.clear();}}
}

使用案例

xxx-class日志异步打印

按追踪维度查询操作日志

按追踪维度查询操作日志

xxx-user日志同步打印

日志同步打印

按api维度查询统计数据

api:"com.xxx.user.client.UserTokenApi/decodeTokenForCoach" and code:"00000"

按api维度查询统计数据

按用户维度查询实操路径

按用户维度查询实操路径

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/3245956.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

2024 HNCTF PWN(close ezpwn idea what beauty)

文章目录 closeezpwn代码利用exp idea代码exp whatexp beauty libc 2.35IDA中文乱码解决代码思路exp close int __fastcall main(int argc, const char **argv, const char **envp) {puts("**********************************");puts("* Welcome to the H…

kubernetes k8s Deployment 控制器配置管理 k8s 红蓝部署 金丝雀发布

目录 1、Deployment控制器&#xff1a;概念、原理解读 1.1 Deployment概述 1.2 Deployment工作原理&#xff1a;如何管理rs和Pod&#xff1f; 2、Deployment资源清单文件编写技巧 3、Deployment使用案例&#xff1a;创建一个web站点 4、Deployment管理pod&#xff1a;扩…

数字化装备场革新:智能装备采集设备问世!

数字化装备场建设是一项跨学科的综合工程&#xff0c;它不仅涵盖了网络布线的整合、系统框架的构建&#xff0c;还包含了各类硬件设备的配置等丰富多样的内容。该工程的关键环节主要包括对装备场所的出入口进行现代化升级、完善车库车辆定位与管理体系&#xff0c;以及部署一体…

Maven学习——Maven的下载、安装与配置(详细攻略!)

目录 前言 1.下载与安装 2.配置Maven的环境变量 3.配置Maven的本地仓库 4. 配置Maven的镜像远程仓库 前言 我在之前写了一篇博客&#xff0c;是介绍Maven的基本概念和下载安装&#xff0c;但是由于篇幅过长&#xff0c;Maven的下载与安装写的并不详细&#x1f436;&#x…

kaggle 量化交易 比赛

作者: Raoul Malm 描述: 本笔记本演示了如何使用 TensorFlow 中的循环神经网络预测不同股票的未来价格。实现了带有基本单元、LSTM 或 GRU 单元的循环神经网络。 大纲: [库和设置][分析数据][操作数据][建模和验证数据][预测]参考: https://www.kaggle.com/benjibb/lstm-st…

图片压缩python

linux粘贴文本格式错乱&#xff1a; vi/vim :set paste然后再 insert &#xff0c;粘贴 centos安装pillow图像处理库&#xff1a; 引用&#xff1a;https://blog.csdn.net/newbieLCQ/article/details/125345335 linux赋予执行权限&#xff1a; # 创建文件 touch comperss.p…

C++解决:书本信息储存

书本信息储存 暂无标签 时间限制&#xff1a;C/C 1000MS&#xff0c;其他语言 2000MS 内存限制&#xff1a;C/C 256MB&#xff0c;其他语言 512MB 难度&#xff1a;简单 描述 创建一个储存书本信息的结构体&#xff0c;包括书号&#xff0c;名称&#xff0c;作者&#xff0…

日本IT-SE职位一览与未来职业发展路径规划

日本IT-SE 工作内容特点&#xff1a; 1. 软件开发&#xff1a;SE的主要任务是编写、测试和维护&#xff0c;以满足客户需求。他们需要使用多种编程语言和开发工具来完成任务。 2. 需求分析&#xff1a;SE负责与客户沟通&#xff0c;理解他们的需求&#xff0c;并将其转化为可…

给你的博客添加数据统计功能

20.数据统计 我们可以给博客添加统计功能&#xff0c;这里以百度统计和谷歌统计为例。 ‍ 百度统计 ‍ 添加网站 登陆百度统计后台&#xff1a;百度统计——一站式智能数据分析与应用平台&#xff0c;并登录&#xff0c;然后进入产品&#xff1a; ‍ 在使用设置–网站列…

Windows命令行(CMD)中,tasklist | findstr(搜索并显示包含特定字符串的进程信息)

文章目录 示例注意事项示例&#xff1a;使用 /FI 选项过滤进程 在Windows命令行&#xff08;CMD&#xff09;中&#xff0c; tasklist 命令用于显示当前运行的进程列表&#xff0c;而 findstr 命令则用于搜索字符串。当你将 tasklist 命令的输出通过管道&#xff08; |&…

单片机设计_自行车码表(AT89C51, LCD1602, DS1302,霍尔传感器)

想要更多项目私wo!!! 一、电路设计 系统采用51单片机LCD1602液晶DS1302时钟模块霍尔传感器电机按键模块蜂鸣器报警模块设计而成。 产品自带单片机上电复位电路、手动复位电路&#xff08;复位按键&#xff09;、晶振电路&#xff08;给单片机提供时钟周期&#xff09;。 …

OpenCV下的无标定校正(stereoRectifyUncalibrated)

OpenCV下的无标定校正(stereoRectifyUncalibrated) 文章目录 1. 杂话2. 无标定校正2.1 先看代码2.2 一点解释2.3 findFundamentalMat参数2.4 stereoRectifyUncalibrated参数 3. 矫正结果 1. 杂话 咱们在之前的帖子里面讲了一些比较常规的标定和校正OpenCV下的单目标定&#xff…

紫外测熔融实验结果温度和abs作折线图和求一阶导最大值

import numpy as np import pandas as pd from pyecharts.charts import Line from pyecharts import options as opts from matplotlib import pyplot as plt import xlrd # 读取 csv 文件 data pd.read_excel(F:/LJX/lc8.xls)# 假设 csv 文件中有两列数据&#xff1a;x 和 y…

iOS——编译链接

编译连接的过程 预处理编译汇编链接 预处理 clang -E main.m -o main.i“#define"删除并展开对应宏定义。处理所有的条件预编译指令。如#if/#ifdef/#else/#endif。”#include/#import"包含的文件递归插入到此处。删除所有的注释"//或/**/"。添加行号和文…

SpringCloud网关的实现原理与使用指南

Spring Cloud网关是一个基于Spring Cloud的微服务网关&#xff0c;它是一个独立的项目&#xff0c;可以对外提供API接口服务&#xff0c;负责请求的转发和路由。本文将介绍Spring Cloud网关的实现原理和使用指南。 一、Spring Cloud网关的实现原理 Spring Cloud网关基于Spring…

在 Windows 上开发.NET MAUI 应用_2.生成你的第一个应用

先决条件 Visual Studio 2022 17.8 或更高版本&#xff0c;并安装了 .NET Multi-platform App UI 工作负载。 可参考上一篇文章&#xff1a;http://t.csdnimg.cn/n38Yy 创建应用 1.启动 Visual Studio 2022。 在开始窗口中&#xff0c;单击“创建新项目”以创建新项目&#…

时间管理计划表怎么做 待办计划时间管理工具

每当夜深人静&#xff0c;我总会坐在书桌前&#xff0c;思考着如何更好地规划明天的时间。那一张张密密麻麻的时间管理计划表&#xff0c;就像是我生活的地图&#xff0c;指引着我前行。我知道&#xff0c;对于每一个在都市丛林中奔波的打工人来说&#xff0c;时间都是最宝贵的…

Java面试(持续更新)

Redis使用场景 缓存穿透 当有该数据的时候&#xff0c;redis中的数据已经是原来数据的null值了&#xff0c;可能会出现不一致的问题。 缓存击穿 跟钱相关的强一致用互斥锁。 用户高体验用逻辑过期。 缓存雪崩 ttl随机值

AI 绘画|Midjourney设计Logo提示词

你是否已经看过许多别人分享的 MJ 咒语&#xff0c;却仍无法按照自己的想法画图&#xff1f;通过学习 MJ 的提示词逻辑后&#xff0c;你将能够更好地理解并创作自己的“咒语”。本文将详细拆解使用 MJ 设计 Logo 的逻辑&#xff0c;让你在阅读后即可轻松上手&#xff0c;制作出…

2.I/O口

文章目录 I/O输出(点灯)分析电路代码编写 I/O输入(电平检测)代码编写 I/O内部电路分析51单片机STM32单片机输入输出 I/O输出(点灯) 分析电路 看电路图&#xff0c;元器件形成电压差&#xff0c;即可点亮LED灯 代码编写 使用不同操作进行LED控制 #include "reg52.h&quo…