贵阳网站设计方案七牛做网站

张小明 2025/12/22 14:57:26
贵阳网站设计方案,七牛做网站,天津商业网站建设,服务于中小企业建网站排查线上日志时#xff0c;同一个 Pod 内多线程日志交错#xff0c;很难追踪每个请求对应的日志信息。 日志收集工具将多个 Pod 的日志收集到同一个数据库中后#xff0c;情况就更加混乱不堪了。 解决 TraceId MDC MDC#xff1a; https://logback.qos.ch/manual/mdc…排查线上日志时同一个 Pod 内多线程日志交错很难追踪每个请求对应的日志信息。日志收集工具将多个 Pod 的日志收集到同一个数据库中后情况就更加混乱不堪了。解决TraceId MDCMDChttps://logback.qos.ch/manual/mdc.html前端每次请求时添加X-App-Trace-Id请求头X-App-Trace-Id值的生成方式可以选择【时间戳 UUID】保证 traceId 的唯一性。后端在 TraceIdFilter 中取出X-App-Trace-Id的值String traceId httpServletRequest.getHeader(TRACE_ID_HEADER_KEY)。如果请求没有携带X-App-Trace-Id请求头后端服务可以使用 UUID 或者 Snowflake 算法生成一个 traceId。将 traceId 塞到 slf4j MDC 中MDC.put(MDC_TRACE_ID_KEY, traceId)在 logback pattern 中使用%X{traceId}占位符打印 traceId。public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpServletRequest (HttpServletRequest) request; String traceId httpServletRequest.getHeader(TRACE_ID_HEADER_KEY); if (StrUtil.isBlank(traceId)) { traceId UUID.randomUUID().toString(); } MDC.put(MDC_TRACE_ID_KEY, traceId); try { chain.doFilter(request, response); } finally { MDC.remove(MDC_TRACE_ID_KEY); } } ?xml version1.0 encodingUTF-8? configuration debugfalse property namepattern value[%d{yyyy-MM-dd HH:mm:ss.SSS}] %-5level [%thread] %logger %line [%X{traceId}] [%tid] - %msg%n/整合 Feign发起服务间调用时需要将 MDC 中的 traceId 传递到被调用服务。我们项目中统一使用 Feign Client实现服务间的 HTTP 远程调用在Feign RequestInterceptor中取出 MDC 中的 traceId塞到请求头中requestTemplate.header(TRACE_ID_HEADER_KEY, MDC.get(MDC_TRACE_ID_KEY));Override public void apply(RequestTemplate template) { template.header(TRACE_ID_HEADER_KEY, MDC.get(MDC_TRACE_ID_KEY)); }多线程适配Please note that MDC as implemented by logback-classic assumes that values are placed into the MDC with moderate frequency. Also note that a child thread does not automatically inherit a copy of the mapped diagnostic context of its parent.在子线程执行任务前将父线程的 MDC 内容设置到子线程的 MDC 中在子线程任务执行完成后清除子线程 MDC 中的内容。适配 JDK ThreadPoolExecutorpublic class MdcAwareThreadPoolExecutor extends ThreadPoolExecutor { Override public void execute(Runnable command) { MapString, String parentThreadContextMap MDC.getCopyOfContextMap(); super.execute(MdcTaskUtils.adaptMdcRunnable(command, parentThreadContextMap)); } }适配 Spring TaskDecoratorComponent public class MdcAwareTaskDecorator implements TaskDecorator { Override public Runnable decorate(Runnable runnable) { MapString, String parentThreadContextMap MDC.getCopyOfContextMap(); return MdcTaskUtils.adaptMdcRunnable(runnable, parentThreadContextMap); } }MdcTaskUtils#adaptMdcRunnable()采用装饰者模式装饰原生的Runnable runnable对象在原生 Runnable 对象执行前将父线程的 MDC 设置到子线程中在原生 Runnable 对象执行结束后清除子线程 MDC 中的内容。Slf4j public abstract class MdcTaskUtils { public static Runnable adaptMdcRunnable(Runnable runnable, MapString, String parentThreadContextMap) { return () - { log.debug(parentThreadContextMap: {}, currentThreadContextMap: {}, parentThreadContextMap, MDC.getCopyOfContextMap()); if (MapUtils.isEmpty(parentThreadContextMap) || !parentThreadContextMap.containsKey(MDC_TRACE_ID_KEY)) { log.debug(can not find a parentThreadContextMap, maybe task is fired using async or schedule task.); MDC.put(MDC_TRACE_ID_KEY, UUID.randomUUID().toString()); } else { MDC.put(MDC_TRACE_ID_KEY, parentThreadContextMap.get(MDC_TRACE_ID_KEY)); } try { runnable.run(); } finally { MDC.remove(MDC_TRACE_ID_KEY); } }; } }整合 SkywalkingSkywalking 官方提供了对 logback 1.x 版本的适配apm-toolkit-logback-1.x可以在 logback 中打印skywalking traceId可以将X-App-Trace-Id和skywalking traceId结合起来方便接口业务和性能问题的排查。layout 具体实现类选择TraceIdPatternLogbackLayout。在logback pattern中使用%tid打印skywalking traceId。?xml version1.0 encodingUTF-8? configuration debugfalse property namepattern value[%d{yyyy-MM-dd HH:mm:ss.SSS}] %-5level [%thread] %logger %line [%X{traceId}] [%tid] - %msg%n/ appender nameSTDOUT classch.qos.logback.core.ConsoleAppender encoder classch.qos.logback.core.encoder.LayoutWrappingEncoder layout classorg.apache.skywalking.apm.toolkit.log.logback.v1.x.TraceIdPatternLogbackLayout pattern${pattern}/pattern /layout /encoder /appenderTraceIdPatternLogbackLayout类初始化时添加了两个PatternConvertertid使用LogbackPatternConverter将%tid占位符转换为skywalking traceId。sw_ctx使用LogbackSkyWalkingContextPatternConverter将 %sw_ctx 占位符转换为skywalking context。public class TraceIdPatternLogbackLayout extends PatternLayout { public TraceIdPatternLogbackLayout() { } static { defaultConverterMap.put(tid, LogbackPatternConverter.class.getName()); defaultConverterMap.put(sw_ctx, LogbackSkyWalkingContextPatternConverter.class.getName()); } }LogbackPatternConverter#convert()方法写死了返回 TID: N/A这是怎么回事呢public class LogbackPatternConverter extends ClassicConverter { public LogbackPatternConverter() { } public String convert(ILoggingEvent iLoggingEvent) { return TID: N/A; } }启动 Java 应用时指定 java agent 启动参数-javaagent:-javaagent:/opt/tools/skywalking-agent.jar。skywalking agent会代理LogbackPatternConverter类重写convert()方法的逻辑。package org.apache.skywalking.apm.toolkit.log.logback.v1.x; import ch.qos.logback.classic.pattern.ClassicConverter; import ch.qos.logback.classic.spi.ILoggingEvent; import java.lang.reflect.Method; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstMethodsInter; import org.apache.skywalking.apm.toolkit.log.logback.v1.x.LogbackPatternConverter$auxiliary$pJ6Zrqzi; public class LogbackPatternConverter extends ClassicConverter implements EnhancedInstance { private volatile Object _$EnhancedClassField_ws; public static volatile /* synthetic */ InstMethodsInter delegate$mo3but1; private static final /* synthetic */ Method cachedValue$oeLgRjrq$u5j8qu3; public String convert(ILoggingEvent iLoggingEvent) { return (String)delegate$mo3but1.intercept(this, new Object[]{iLoggingEvent}, new LogbackPatternConverter$auxiliary$pJ6Zrqzi(this, iLoggingEvent), cachedValue$oeLgRjrq$u5j8qu3); } private /* synthetic */ String convert$original$T8InTdln(ILoggingEvent iLoggingEvent) { /*34*/ returnTID: N/A; } Override public void setSkyWalkingDynamicField(Object object) { this._$EnhancedClassField_ws object; } Override public Object getSkyWalkingDynamicField() { return this._$EnhancedClassField_ws; } static { ClassLoader.getSystemClassLoader().loadClass(org.apache.skywalking.apm.dependencies.net.bytebuddy.dynamic.Nexus).getMethod(initialize, Class.class, Integer.TYPE).invoke(null, LogbackPatternConverter.class, -1942176692); cachedValue$oeLgRjrq$u5j8qu3 LogbackPatternConverter.class.getMethod(convert, ILoggingEvent.class); } final /* synthetic */ String convert$original$T8InTdln$accessor$oeLgRjrq(ILoggingEvent iLoggingEvent) { return this.convert$original$T8InTdln(iLoggingEvent); } }MDC 原理MDC 在slf4j-api jar包中MDC 是 slf4j 的规范对 MDC 的所有操作都会落到MDCAdapter接口的方法上。public class MDC { static final String NULL_MDCA_URL http://www.slf4j.org/codes.html#null_MDCA; static final String NO_STATIC_MDC_BINDER_URL http://www.slf4j.org/codes.html#no_static_mdc_binder; static MDCAdapter mdcAdapter; public static void put(String key, String val) throws IllegalArgumentException { if (key null) { throw new IllegalArgumentException(key parameter cannot be null); } if (mdcAdapter null) { throw new IllegalStateException(MDCAdapter cannot be null. See also NULL_MDCA_URL); } mdcAdapter.put(key, val); } }MDCAdapter是 slf4j 提供的 MDC 适配器接口也就是 MDC 的规范。任何日志框架想要使用 MDC 功能需要遵守MDCAdapter接口接口规范实现接口中的方法。// This interface abstracts the service offered by various MDC implementations. public interface MDCAdapter { public void put(String key, String val); public String get(String key); }Logback 日志框架提供了对MDCAdapter的适配LogbackMDCAdapter底层采用 ThreadLocal 实现。public class LogbackMDCAdapter implements MDCAdapter { // The internal map is copied so as // We wish to avoid unnecessarily copying of the map. To ensure // efficient/timely copying, we have a variable keeping track of the last // operation. A copy is necessary on put or remove but only if the last // operation was a get. Get operations never necessitate a copy nor // successive put/remove operations, only a get followed by a put/remove // requires copying the map. // See http://jira.qos.ch/browse/LOGBACK-620 for the original discussion. // We no longer use CopyOnInheritThreadLocal in order to solve LBCLASSIC-183 // Initially the contents of the thread localin parent and child threads // reference the same map. However, as soon as a thread invokes the put() // method, the maps diverge as they should. final ThreadLocalMapString, String copyOnThreadLocal new ThreadLocalMapString, String(); private static final int WRITE_OPERATION 1; private static final int MAP_COPY_OPERATION 2; // keeps track of the last operation performed final ThreadLocalInteger lastOperation new ThreadLocalInteger(); public void put(String key, String val) throws IllegalArgumentException { if (key null) { throw new IllegalArgumentException(key cannot be null); } MapString, String oldMap copyOnThreadLocal.get(); Integer lastOp getAndSetLastOperation(WRITE_OPERATION); if (wasLastOpReadOrNull(lastOp) || oldMap null) { MapString, String newMap duplicateAndInsertNewMap(oldMap); newMap.put(key, val); } else { oldMap.put(key, val); } } public String get(String key) { final MapString, String map copyOnThreadLocal.get(); if ((map ! null) (key ! null)) { return map.get(key); } else { return null; } } }Logback 占位符PatternLayout类初始化时设置了 logback 常用占位符对应的 Converter。public class PatternLayout extends PatternLayoutBaseILoggingEvent { public static final MapString, String DEFAULT_CONVERTER_MAP new HashMapString, String(); public static final MapString, String CONVERTER_CLASS_TO_KEY_MAP new HashMapString, String(); /** * deprecated replaced by DEFAULT_CONVERTER_MAP */ public static final MapString, String defaultConverterMap DEFAULT_CONVERTER_MAP; public static final String HEADER_PREFIX #logback.classic pattern: ; static { DEFAULT_CONVERTER_MAP.putAll(Parser.DEFAULT_COMPOSITE_CONVERTER_MAP); DEFAULT_CONVERTER_MAP.put(d, DateConverter.class.getName()); DEFAULT_CONVERTER_MAP.put(date, DateConverter.class.getName()); CONVERTER_CLASS_TO_KEY_MAP.put(DateConverter.class.getName(), date); DEFAULT_CONVERTER_MAP.put(r, RelativeTimeConverter.class.getName()); DEFAULT_CONVERTER_MAP.put(relative, RelativeTimeConverter.class.getName()); CONVERTER_CLASS_TO_KEY_MAP.put(RelativeTimeConverter.class.getName(), relative); DEFAULT_CONVERTER_MAP.put(level, LevelConverter.class.getName()); DEFAULT_CONVERTER_MAP.put(le, LevelConverter.class.getName()); DEFAULT_CONVERTER_MAP.put(p, LevelConverter.class.getName()); CONVERTER_CLASS_TO_KEY_MAP.put(LevelConverter.class.getName(), level); DEFAULT_CONVERTER_MAP.put(t, ThreadConverter.class.getName()); DEFAULT_CONVERTER_MAP.put(thread, ThreadConverter.class.getName()); CONVERTER_CLASS_TO_KEY_MAP.put(ThreadConverter.class.getName(), thread); DEFAULT_CONVERTER_MAP.put(lo, LoggerConverter.class.getName()); DEFAULT_CONVERTER_MAP.put(logger, LoggerConverter.class.getName()); DEFAULT_CONVERTER_MAP.put(c, LoggerConverter.class.getName()); CONVERTER_CLASS_TO_KEY_MAP.put(LoggerConverter.class.getName(), logger); DEFAULT_CONVERTER_MAP.put(m, MessageConverter.class.getName()); DEFAULT_CONVERTER_MAP.put(msg, MessageConverter.class.getName()); DEFAULT_CONVERTER_MAP.put(message, MessageConverter.class.getName()); CONVERTER_CLASS_TO_KEY_MAP.put(MessageConverter.class.getName(), message); DEFAULT_CONVERTER_MAP.put(C, ClassOfCallerConverter.class.getName()); DEFAULT_CONVERTER_MAP.put(class, ClassOfCallerConverter.class.getName()); CONVERTER_CLASS_TO_KEY_MAP.put(ClassOfCallerConverter.class.getName(), class); DEFAULT_CONVERTER_MAP.put(M, MethodOfCallerConverter.class.getName()); DEFAULT_CONVERTER_MAP.put(method, MethodOfCallerConverter.class.getName()); CONVERTER_CLASS_TO_KEY_MAP.put(MethodOfCallerConverter.class.getName(), method); DEFAULT_CONVERTER_MAP.put(L, LineOfCallerConverter.class.getName()); DEFAULT_CONVERTER_MAP.put(line, LineOfCallerConverter.class.getName()); CONVERTER_CLASS_TO_KEY_MAP.put(LineOfCallerConverter.class.getName(), line); DEFAULT_CONVERTER_MAP.put(F, FileOfCallerConverter.class.getName()); DEFAULT_CONVERTER_MAP.put(file, FileOfCallerConverter.class.getName()); CONVERTER_CLASS_TO_KEY_MAP.put(FileOfCallerConverter.class.getName(), file); DEFAULT_CONVERTER_MAP.put(X, MDCConverter.class.getName()); DEFAULT_CONVERTER_MAP.put(mdc, MDCConverter.class.getName()); DEFAULT_CONVERTER_MAP.put(ex, ThrowableProxyConverter.class.getName()); DEFAULT_CONVERTER_MAP.put(exception, ThrowableProxyConverter.class.getName()); DEFAULT_CONVERTER_MAP.put(rEx, RootCauseFirstThrowableProxyConverter.class.getName()); DEFAULT_CONVERTER_MAP.put(rootException, RootCauseFirstThrowableProxyConverter.class.getName()); DEFAULT_CONVERTER_MAP.put(throwable, ThrowableProxyConverter.class.getName()); DEFAULT_CONVERTER_MAP.put(xEx, ExtendedThrowableProxyConverter.class.getName()); DEFAULT_CONVERTER_MAP.put(xException, ExtendedThrowableProxyConverter.class.getName()); DEFAULT_CONVERTER_MAP.put(xThrowable, ExtendedThrowableProxyConverter.class.getName()); DEFAULT_CONVERTER_MAP.put(nopex, NopThrowableInformationConverter.class.getName()); DEFAULT_CONVERTER_MAP.put(nopexception, NopThrowableInformationConverter.class.getName()); DEFAULT_CONVERTER_MAP.put(cn, ContextNameConverter.class.getName()); DEFAULT_CONVERTER_MAP.put(contextName, ContextNameConverter.class.getName()); CONVERTER_CLASS_TO_KEY_MAP.put(ContextNameConverter.class.getName(), contextName); DEFAULT_CONVERTER_MAP.put(caller, CallerDataConverter.class.getName()); CONVERTER_CLASS_TO_KEY_MAP.put(CallerDataConverter.class.getName(), caller); DEFAULT_CONVERTER_MAP.put(marker, MarkerConverter.class.getName()); CONVERTER_CLASS_TO_KEY_MAP.put(MarkerConverter.class.getName(), marker); DEFAULT_CONVERTER_MAP.put(property, PropertyConverter.class.getName()); DEFAULT_CONVERTER_MAP.put(n, LineSeparatorConverter.class.getName()); DEFAULT_CONVERTER_MAP.put(black, BlackCompositeConverter.class.getName()); DEFAULT_CONVERTER_MAP.put(red, RedCompositeConverter.class.getName()); DEFAULT_CONVERTER_MAP.put(green, GreenCompositeConverter.class.getName()); DEFAULT_CONVERTER_MAP.put(yellow, YellowCompositeConverter.class.getName()); DEFAULT_CONVERTER_MAP.put(blue, BlueCompositeConverter.class.getName()); DEFAULT_CONVERTER_MAP.put(magenta, MagentaCompositeConverter.class.getName()); DEFAULT_CONVERTER_MAP.put(cyan, CyanCompositeConverter.class.getName()); DEFAULT_CONVERTER_MAP.put(white, WhiteCompositeConverter.class.getName()); DEFAULT_CONVERTER_MAP.put(gray, GrayCompositeConverter.class.getName()); DEFAULT_CONVERTER_MAP.put(boldRed, BoldRedCompositeConverter.class.getName()); DEFAULT_CONVERTER_MAP.put(boldGreen, BoldGreenCompositeConverter.class.getName()); DEFAULT_CONVERTER_MAP.put(boldYellow, BoldYellowCompositeConverter.class.getName()); DEFAULT_CONVERTER_MAP.put(boldBlue, BoldBlueCompositeConverter.class.getName()); DEFAULT_CONVERTER_MAP.put(boldMagenta, BoldMagentaCompositeConverter.class.getName()); DEFAULT_CONVERTER_MAP.put(boldCyan, BoldCyanCompositeConverter.class.getName()); DEFAULT_CONVERTER_MAP.put(boldWhite, BoldWhiteCompositeConverter.class.getName()); DEFAULT_CONVERTER_MAP.put(highlight, HighlightingCompositeConverter.class.getName()); DEFAULT_CONVERTER_MAP.put(lsn, LocalSequenceNumberConverter.class.getName()); CONVERTER_CLASS_TO_KEY_MAP.put(LocalSequenceNumberConverter.class.getName(), lsn); DEFAULT_CONVERTER_MAP.put(prefix, PrefixCompositeConverter.class.getName()); } }ThreadConverter将%thread占位符转换为 logger 线程。public class ThreadConverter extends ClassicConverter { public String convert(ILoggingEvent event) { return event.getThreadName(); } )在PatternLayout中默认添加了对 MDC 的支持可以将%X{key}或者%mdc{key}占位符转换为MDC.get(key)。DEFAULT_CONVERTER_MAP.put(X, MDCConverter.class.getName()); DEFAULT_CONVERTER_MAP.put(mdc, MDCConverter.class.getName());MDCConverter为何可以将%X{traceId}或者%mdc{traceId}占位符转换为MDC.get(traceId)在程序启动时ch.qos.logback.core.pattern.parser.Compiler#compile()会解析用户配置的 pattern 表达式得到 pattern 中需要动态解析的占位符比如%d{yyyy-MM-dd HH:mm:ss.SSS}%X{traceId}。将这些动态占位符传递给DynamicConverter#optionList字段MDCConverter本质就是DynamicConverteroptionList 字段包含程序要处理的占位符名称比如 traceId。图片程序启动时logback 进行 Convert 初始化会调用MDCConverter#start()方法将成员变量private String key的值设置为traceIdMDCConverter#getFirstOption()返回用户配置的 traceId。图片在MDCConverter#convert()方法中将 traceId 占位符转换为MDC.get(key)String value mdcPropertyMap.get(key)。public class MDCConverter extends ClassicConverter { private String key; private String defaultValue ; Override public void start() { String[] keyInfo extractDefaultReplacement(getFirstOption()); key keyInfo[0]; if (keyInfo[1] ! null) { defaultValue keyInfo[1]; } super.start(); } Override public String convert(ILoggingEvent event) { MapString, String mdcPropertyMap event.getMDCPropertyMap(); if (mdcPropertyMap null) { return defaultValue; } if (key null) { return outputMDCForAllKeys(mdcPropertyMap); } else { String value mdcPropertyMap.get(key); if (value ! null) { return value; } else { return defaultValue; } } } }ILoggingEvent接口的实现类LoggingEvent中对MDCAdapter做了适配public class LoggingEvent implements ILoggingEvent { public MapString, String getMDCPropertyMap() { // populate mdcPropertyMap if null if (mdcPropertyMap null) { MDCAdapter mdc MDC.getMDCAdapter(); if (mdc instanceof LogbackMDCAdapter) mdcPropertyMap ((LogbackMDCAdapter) mdc).getPropertyMap(); else mdcPropertyMap mdc.getCopyOfContextMap(); } // mdcPropertyMap still null, use emptyMap() if (mdcPropertyMap null) mdcPropertyMap Collections.emptyMap(); return mdcPropertyMap; } }
版权声明:本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

做产品网站要备案吗网站移动转换

Qt 5.14.2 Linux x64 开源版安装终极指南:从下载到配置完整教程 【免费下载链接】Qt5.14.2开源版Linuxx64安装文件下载 Qt 5.14.2 开源版 Linux x64 安装文件下载 项目地址: https://gitcode.com/Open-source-documentation-tutorial/3ce16 还在为Qt在Linux环…

张小明 2025/12/22 14:56:25 网站建设

福州市网站建设有限公司昆明房地产网站建设

摘要:还在一个个打开Word点击“另存为PDF”?本文教你使用Python的 docx2pdf 库,仅需3行代码,即可瞬间完成成百上千个文档的格式转换。无废话,直接上实战。0. 为什么写这篇文章?昨天快下班时,同事…

张小明 2025/12/22 14:55:24 网站建设

网站变灰色wordpress自定义数据库

第一章:MCP MS-720 Agent 调试工具概述MCP MS-720 Agent 是一款专为嵌入式设备远程调试与监控设计的轻量级代理程序,广泛应用于工业自动化、边缘计算及物联网场景中。该工具通过标准化通信协议与主控平台交互,支持实时日志采集、性能指标上报…

张小明 2025/12/22 14:54:23 网站建设

网站首页自动下拉广告关于网站的设计和制作

技术与系统综合指南 1. 系统基础与网络相关 系统登录与基本操作 :登录系统可通过文本控制台进行,步骤为输入用户名和密码,登录后能进行各类操作,如使用 ls 命令查看文件和目录信息, ls 命令常用参数及功能如下: | 参数 | 功能 | | — | — | | -l | 以长格式显…

张小明 2025/12/22 14:52:22 网站建设

技术支持 东莞网站建设母婴护理哪里提供邢台做网站

EmotiVoice能否用于宗教诵经语音生成?庄重感情绪模拟 在一座千年古寺的清晨,钟声未歇,僧人低沉而绵长的诵经声穿过薄雾,在殿宇间回荡。那种声音不是简单的朗读,它带着呼吸的节奏、胸腔的共鸣、语句间的留白——仿佛每一…

张小明 2025/12/22 14:51:21 网站建设

昆明北京网站建设地方网站推广

电气自动化技术作为智能制造与工业4.0的核心支撑领域,大专生需在夯实技术根基的同时,通过权威认证提升差异化竞争力。当前行业对“懂技术、通数据、会集成”的复合型人才需求显著,合理选择证书可作为能力佐证。以下基于行业趋势与认证价值&am…

张小明 2025/12/22 14:50:20 网站建设