dubbo traceId透传实现日志链路追踪(基于Filter和RpcContext实现)
一、要解决什么问题:
使用elk的过程中发现如下问题:
1.无法准确定位一个请求经过了哪些服务
2.多个请求线程的日志交替打印,不利于查看按时间顺序查看一个请求的日志。
二、期望效果
能够查看一个请求完整的链路日志,不受其它请求日志的干扰。
三、动手实现
消费端需要做什么:
1.新建一个拦截器,拦截所有请求,在调用接口前生成一个链路id(traceId)并放入log4j的MDC和dubbo的RpcContext的attachment,此处拦截器是基于jfinal实现,spring mvc可用其它拦截器或aop方案代替,只要实现生成traceId放入内存即可。
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.log4j.MDC;
import com.alibaba.dubbo.rpc.RpcContext;
import com.jfinal.aop.Interceptor;
import com.jfinal.aop.Invocation;
import com.jfinal.handler.Handler;
import com.jfinal.kit.StrKit;
import com.ttxn.frameworks.utils.CommonUtil;
public class TraceHandler extends Handler {
@Override
public void handle(String target, HttpServletRequest request, HttpServletResponse response, boolean[] isHandled) {
String token = request.getParameter("token");
String userId = StrKit.notBlank(token) ? CommonUtil.getUserId(token) : "VISITOR";
String uuid = CommonUtil.getUuid();
String traceId = userId + "-" + uuid;
RpcContext.getContext().setAttachment("traceId", traceId);
MDC.put("traceId", traceId);
next.handle(target, request, response, isHandled);
}
}
2.修改log4j配置文件,在打印日志的开头加上链路id变量(%X{traceId}),用于log4j打印日志时自动打印MDC中的链路id,此时消费端所有log4j日志都会输出链路id。
# log4j.rootLogger=WARN, stdout, file
log4j.rootLogger=INFO, stdout , file
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%X{traceId}%n%-d{yyyy-MM-dd HH:mm:ss}%n[%p]-[Thread: %t]-[%C.%M()]: %m%n
# Output to the File
log4j.appender.file=org.apache.log4j.DailyRollingFileAppender
log4j.appender.file.DatePattern='_'yyyy-MM-dd'.log'
log4j.appender.file.File=${catalina.base}/logs/ttxn_log.log
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%X{traceId}%n%-d{yyyy-MM-dd HH:mm:ss}%n[%p]-[Thread: %t]-[%C.%M()]: %m%n
配置后的消费端log4j输出效果:
Para: [APP公告]
VISITOR-1e2f6d11ca594ea7af3118567d00f004
2019-07-31 17:37:23
[INFO]-[Thread: DubboServerHandler-192.168.5.15:20884-thread-17]-[com.ttxn.frameworks.annotation.redis.RedisCacheInterceptor.intercept()]: cache not hit key=DATA_CACHE_common.getCfg ,filed=5bm/5pKt55S15Y+w
3.自定义一个dubbo filter,为什么消费端要定义这个filter呢?原因是一个接口可能调用多个服务或调用一个服务多次,这样会涉及到多次rpc调用,而RpcContext 的 attachment只在一次rpc会话有效,所以我们需要在每次rpc调用之前放入一次traceId,才能保证多次rpc调用的服务端都能获取到这个traceId,在业务代码中每rpc一次就setAttachment一次太麻烦,所以我选择放入过滤器,对应用开发人员透明。src/main/resources目录下新建META-INF/dubbo/com.alibaba.dubbo.rpc.Filter文件,文件内容如下。
dubboRpcFilter=com.ttxn.api.filter.TraceFilter
修改consumer.xml文件,应用过滤器
<dubbo:consumer timeout="30000" filter="dubboRpcFilter"/>
import com.alibaba.dubbo.rpc.Filter;
import com.alibaba.dubbo.rpc.Invocation;
import com.alibaba.dubbo.rpc.Invoker;
import com.alibaba.dubbo.rpc.Result;
import com.alibaba.dubbo.rpc.RpcContext;
import com.alibaba.dubbo.rpc.RpcException;
import com.jfinal.kit.StrKit;
import com.ttxn.frameworks.utils.trace.TraceIdUtil;
public class TraceFilter implements Filter {
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
String traceId = RpcContext.getContext().getAttachment("traceId");
if ( !StrKit.isBlank(traceId) ) {
// *) 从RpcContext里获取traceId并保存
TraceIdUtil.setTraceId(traceId);
} else {
// *) 交互前重新设置traceId, 避免信息丢失
traceId = TraceIdUtil.getTraceId();
RpcContext.getContext().setAttachment("traceId", traceId);
}
Result result = invoker.invoke(invocation);
return result;
}
}
到这里,消费端的工作就做完了,如果你和我一样是jfinal的架构,还需要做一件事,自定义jfinal的请求日志打印器,如下:
import java.io.IOException;
import java.io.Writer;
import org.slf4j.MDC;
import com.alibaba.dubbo.rpc.RpcContext;
/**
* jfinal请求日志打印工具,用来获得jfinal action report的日志内容,进行自定义后输出
* @author Administrator
*
*/
public class JFinalActionReportWriter extends Writer {
private static final String LOG_PREFIX = "[START]";
public void write(String str) throws IOException {
String traceId = MDC.get("traceId");
System.out.print(LOG_PREFIX + traceId + str);
}
public void write(char[] cbuf, int off, int len) throws IOException {}
public void flush() throws IOException {}
public void close() throws IOException {}
}
GlobalConfig中增加如下配置
public void configConstant(Constants me)
{
ActionReporter.setWriter(new JFinalActionReportWriter());
}
就可以自定义jfinal请求输出日志内容,如下,这部分日志是不受log4j配置影响的,所以我们需要做这一步。
[START]VISITOR-dafe84f6ee2f4d2b907a4c7ef8f8d20c
JFinal action report -------- 2019-07-31 17:37:14 ------------------------------
Url : GET /app/subject/searchSubjectV3_4
Controller : com.ttxn.api.controller.app.SubjectRest.(SubjectRest.java:1)
Method : searchSubjectV3_4
Interceptor : com.ttxn.api.interceptor.APIExceptionInterceptor.(APIExceptionInterceptor.java:1)
com.ttxn.frameworks.plugins.spring.IocInterceptor.(IocInterceptor.java:1)
com.ttxn.api.decorator.CoverImgInterceptor.(CoverImgInterceptor.java:1)
Parameter : query=
生产端需要做什么:
1.和消费端一样需要自定义dubbo拦截器,新增配置文件src\main\resources\META-INF\dubbo\com.alibaba.dubbo.rpc.Filter,文件内容:
dubboContextFilter=com.ttxn.webservice.interceptor.dubbo.ContextFilter
修改provider.xml,对生产者应用此拦截器:
<!-- 提供方调用过程缺省拦截器,将拦截所有service -->
<dubbo:provider filter="dubboContextFilter"/>
在自定义拦截器中获取traceId,放入MDC:
package com.ttxn.webservice.interceptor.dubbo;
import java.lang.annotation.Annotation;
import java.sql.SQLException;
import org.apache.log4j.MDC;
import com.alibaba.dubbo.rpc.Filter;
import com.alibaba.dubbo.rpc.Invocation;
import com.alibaba.dubbo.rpc.Invoker;
import com.alibaba.dubbo.rpc.Result;
import com.alibaba.dubbo.rpc.RpcContext;
import com.alibaba.dubbo.rpc.RpcException;
import com.jfinal.kit.StrKit;
import com.jfinal.plugin.activerecord.Db;
import com.jfinal.plugin.activerecord.IAtom;
import com.ttxn.frameworks.annotation.transaction.Transaction;
import com.ttxn.frameworks.utils.trace.TraceIdUtil;
public class ContextFilter implements Filter
{
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation)
throws RpcException
{
String traceId = RpcContext.getContext().getAttachment("traceId");
if (StrKit.notBlank(traceId)) {
MDC.put("traceId", traceId);
}
Result result = invoker.invoke(invocation);
return result;
}
}
修改log4j文件,输出格式中加上traceId变量(%X{traceId}):
# log4j.rootLogger=WARN, stdout, file
log4j.rootLogger=INFO, stdout , file
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%X{traceId}%n%-d{yyyy-MM-dd HH:mm:ss}%n[%p]-[Thread: %t]-[%C.%M()]: %m%n
# Output to the File
log4j.appender.file=org.apache.log4j.DailyRollingFileAppender
log4j.appender.file.DatePattern='_'yyyy-MM-dd'.log'
log4j.appender.file.File=${catalina.base}/logs/ttxn_log.log
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%X{traceId}%n%-d{yyyy-MM-dd HH:mm:ss}%n[%p]-[Thread: %t]-[%C.%M()]: %m%n
到此就实现了traceId从消费端到生产端的透传,生产端log4j日志如下:
Para: [快报]
VISITOR-1e2f6d11ca594ea7af3118567d00f004
2019-07-31 17:37:23
[INFO]-[Thread: DubboServerHandler-192.168.5.15:20884-thread-15]-[com.ttxn.frameworks.annotation.redis.RedisCacheInterceptor.intercept()]: cache not hit key=DATA_CACHE_base.newsList ,filed=15
我们就可以从kibana中按照traceId来过滤单次请求的所有日志,达到方便排查bug的效果。
dubbo traceId透传实现日志链路追踪(基于Filter和RpcContext实现)的更多相关文章
- SpringBoot之微服务日志链路追踪
SpringBoot之微服务日志链路追踪 简介 在微服务里,业务出现问题或者程序出的任何问题,都少不了查看日志,一般我们使用 ELK 相关的日志收集工具,服务多的情况下,业务问题也是有些难以排查,只能 ...
- 原理分析dubbo分布式应用中使用zipkin做链路追踪
zipkin是什么 Zipkin是一款开源的分布式实时数据追踪系统(Distributed Tracking System),基于 Google Dapper的论文设计而来,由 Twitter 公司开 ...
- 原理分析dubbo分布式应用中使用zipkin做链路追踪(转)
作者:@nele本文为作者原创,转载请注明出处:https://www.cnblogs.com/nele/p/10171794.html 目录 zipkin是什么为什么使用Zipkinzipkin架构 ...
- 应用SpringAOP及Tlog工具完成日志链路追踪、收集、持久化
一.痛点 目前我司各系统的日志管理比较原始,使用logback打日志到log文件,虽然有服务管理平台,但记录的日志也仅仅是前置机调用后台系统的出入参,当遇到问题时查日志较为麻烦. 登录VPN-打开服务 ...
- Springboot+Dubbo使用Zipkin进行接口调用链路追踪
Zipkin介绍: Zipkin是一个分布式链路跟踪系统,可以采集时序数据来协助定位延迟等相关问题.数据可以存储在cassandra,MySQL,ES,mem中.分布式链路跟踪是个老话题,国内也有类似 ...
- 基于SLF4J的MDC机制和Dubbo的Filter机制,实现分布式系统的日志全链路追踪
原文链接:基于SLF4J的MDC机制和Dubbo的Filter机制,实现分布式系统的日志全链路追踪 一.日志系统 1.日志框架 在每个系统应用中,我们都会使用日志系统,主要是为了记录必要的信息和方便排 ...
- Dubbo透传traceId/logid的一种思路
前言: 随着dubbo的开源, 以及成为apache顶级项目. dubbo越来越受到国内java developer欢迎, 甚至成为服务化自治的首选方案. 随着微服务的流行, 如何跟踪整个调用链, 成 ...
- .NET Core 中的日志与分布式链路追踪
目录 .NET Core 中的日志与分布式链路追踪 .NET Core 中的日志 控制台输出 非侵入式日志 Microsoft.Extensions.Logging ILoggerFactory IL ...
- SOFA 源码分析 — 链路数据透传
前言 SOFA-RPC 支持数据链路透传功能,官方解释: 链路数据透传功能支持应用向调用上下文中存放数据,达到整个链路上的应用都可以操作该数据. 使用方式如下,可分别向链路的 request 和 re ...
随机推荐
- deno+mongo实战踩坑记
自从 deno 1.0 发布以来,有关 deno 的文章很多,大多数都是在讨论怎么安装 deno .deno 有哪些特点 .deno 和 node 有哪些异同.deno是不是 node 的替代品等.咱 ...
- 疯狂VirtualBox实战讲学录 以及 virtualbox完全学习手册 之我见
都是是很专业,并钻石得很深的内容,但,有些事情是交替进行着的深入,太专注于VIRTUAL BOX就必要要牺牲其它的东西.
- 网页导出成word文档的默认视图方式问题
网页导出成word文档的默认视图方式问题 一般保存后的word文档默认是“Web版式视图”打开,这样会给客户的感觉不是真正的word文档,必须实现打开就是“页面视图” 1. 修改<html> ...
- 安装sql_server_2008问题
http://support.microsoft.com/kb/968382 --安装sql_server_2008_R2提示重启计算机规则失败 http://blog.sina.com.cn/s/b ...
- 性能测试之服务器监控和Prometheus推荐
服务器的监控,也是采用Prometheus和Grafana.可以监控服务器系统负载.CPU使用率.网络流量.磁盘使用率.磁盘读写速度.IO耗时.网络信息. 效果图 安装使用 安装启动node_expo ...
- docker file的基本使用-案例创建centos
#基于镜像 FROM centos #作者 MAINTAINER xiaozhang #声明变量 ENV ROOT_PATH /usr/local/ #设置工作目录 用户进入容器之后终端默认路径 WO ...
- layui 数据表格按钮事件绑定和渲染
先看效果图 使用两种渲染方法: 1.toolbar引入模板 顶部的添加和删除按钮,右侧的三个筛选,打印,导出按钮 基础参数属性:toolbar:'#demo2', //创建 删除 添加按钮模板 < ...
- c# 优化代码的一些规则——使用is或as和强制类型转换的区别[三]
前言 使用as和强制类型转换的时候的区别是否仅仅是代码形式上的区别. 答案是肯定不是的. 正文 看两段代码: object o = Factory.GetObject(); Student stude ...
- [PHP自动化-进阶]002.CURL模拟登录带有验证码的网站
引言:继前文<模拟登录并采集数据>,大家似乎看不过瘾,这会再出一发,模拟实现带验证码网站的登录. 这篇文章主要介绍了PHP使用CURL实现对带有验证码的网站进行模拟登录的方法,可以帮助读者 ...
- 基于 abp vNext 和 .NET Core 开发博客项目 - 定时任务最佳实战(三)
上一篇(https://www.cnblogs.com/meowv/p/12974439.html)完成了全网各大平台的热点新闻数据的抓取,本篇继续围绕抓取完成后的操作做一个提醒.当每次抓取完数据后, ...