资讯专栏INFORMATION COLUMN

log4j MDC实现日志追踪

aristark / 3181人阅读

摘要:介绍中包含的可以被同一线程中执行的代码所访问内容。当前线程的子线程会继承其父线程中的的内容。记录日志时,只需要从中获取所需的信息即可。作用使用来记录日志,可以规范多开发下日志格式。

介绍:
MDC 中包含的可以被同一线程中执行的代码所访问内容。当前线程的子线程会继承其父线程中的 MDC 的内容。记录日志时,只需要从 MDC 中获取所需的信息即可。
作用:
使用MDC来记录日志,可以规范多开发下日志格式。

一:新建线程处理类 ThreadContext

</>复制代码

  1. import java.io.Serializable;
  2. import java.util.HashMap;
  3. import java.util.Map;
  4. import java.util.Optional;
  5. /**
  6. * 线程上下文
  7. *
  8. * @date 201731
  9. * @since 1.0.0
  10. */
  11. public class ThreadContext {
  12. /**
  13. * 线程上下文变量的持有者
  14. */
  15. private final static ThreadLocal> CTX_HOLDER = new ThreadLocal>();
  16. static {
  17. CTX_HOLDER.set(new HashMap());
  18. }
  19. /**
  20. * traceID
  21. */
  22. private final static String TRACE_ID_KEY = "traceId";
  23. /**
  24. * 会话ID
  25. */
  26. private final static String SESSION_KEY = "token";
  27. /**
  28. * 操作用户ID
  29. */
  30. private final static String VISITOR_ID_KEY = "userId";
  31. /**
  32. * 操作用户名
  33. */
  34. private final static String VISITOR_NAME_KEY = "userName";
  35. /**
  36. * 客户端IP
  37. */
  38. private static final String CLIENT_IP_KEY = "clientIp";
  39. /**
  40. * 添加内容到线程上下文中
  41. *
  42. * @param key
  43. * @param value
  44. */
  45. public final static void putContext(String key, Object value) {
  46. Map ctx = CTX_HOLDER.get();
  47. if (ctx == null) {
  48. return;
  49. }
  50. ctx.put(key, value);
  51. }
  52. /**
  53. * 从线程上下文中获取内容
  54. *
  55. * @param key
  56. */
  57. @SuppressWarnings("unchecked")
  58. public final static T getContext(String key) {
  59. Map ctx = CTX_HOLDER.get();
  60. if (ctx == null) {
  61. return null;
  62. }
  63. return (T) ctx.get(key);
  64. }
  65. /**
  66. * 获取线程上下文
  67. */
  68. public final static Map getContext() {
  69. Map ctx = CTX_HOLDER.get();
  70. if (ctx == null) {
  71. return null;
  72. }
  73. return ctx;
  74. }
  75. /**
  76. * 删除上下文中的key
  77. *
  78. * @param key
  79. */
  80. public final static void remove(String key) {
  81. Map ctx = CTX_HOLDER.get();
  82. if (ctx != null) {
  83. ctx.remove(key);
  84. }
  85. }
  86. /**
  87. * 上下文中是否包含此key
  88. *
  89. * @param key
  90. * @return
  91. */
  92. public final static boolean contains(String key) {
  93. Map ctx = CTX_HOLDER.get();
  94. if (ctx != null) {
  95. return ctx.containsKey(key);
  96. }
  97. return false;
  98. }
  99. /**
  100. * 清空线程上下文
  101. */
  102. public final static void clean() {
  103. CTX_HOLDER.remove();
  104. }
  105. /**
  106. * 初始化线程上下文
  107. */
  108. public final static void init() {
  109. CTX_HOLDER.set(new HashMap());
  110. }
  111. /**
  112. * 设置traceID数据
  113. */
  114. public final static void putTraceId(String traceId) {
  115. putContext(TRACE_ID_KEY, traceId);
  116. }
  117. /**
  118. * 获取traceID数据
  119. */
  120. public final static String getTraceId() {
  121. return getContext(TRACE_ID_KEY);
  122. }
  123. /**
  124. * 设置会话的用户ID
  125. */
  126. public final static void putUserId(Integer userId) {
  127. putContext(VISITOR_ID_KEY, userId);
  128. }
  129. /**
  130. * 设置会话的用户ID
  131. */
  132. public final static int getUserId() {
  133. Integer val = getContext(VISITOR_ID_KEY);
  134. return val == null ? 0 : val;
  135. }
  136. /**
  137. * 设置会话的用户名
  138. */
  139. public final static void putUserName(String userName) {
  140. putContext(VISITOR_NAME_KEY, userName);
  141. }
  142. /**
  143. * 获取会话的用户名称
  144. */
  145. public final static String getUserName() {
  146. return Optional.ofNullable(getContext(VISITOR_NAME_KEY))
  147. .map(name -> String.valueOf(name))
  148. .orElse("");
  149. }
  150. /**
  151. * 取出IP
  152. *
  153. * @return
  154. */
  155. public static final String getClientIp() {
  156. return getContext(CLIENT_IP_KEY);
  157. }
  158. /**
  159. * 设置IP
  160. *
  161. * @param ip
  162. */
  163. public static final void putClientIp(String ip) {
  164. putContext(CLIENT_IP_KEY, ip);
  165. }
  166. /**
  167. * 设置会话ID
  168. *
  169. * @param token
  170. */
  171. public static void putSessionId(String token) {
  172. putContext(SESSION_KEY, token);
  173. }
  174. /**
  175. * 获取会话ID
  176. *
  177. * @param token
  178. */
  179. public static String getSessionId(String token) {
  180. return getContext(SESSION_KEY);
  181. }
  182. /**
  183. * 清空会话数据
  184. */
  185. public final static void removeSessionId() {
  186. remove(SESSION_KEY);
  187. }
  188. }

二:添加工具类TraceUtil

</>复制代码

  1. import java.util.UUID;
  2. import org.slf4j.MDC;
  3. import ThreadContext;
  4. /**
  5. * trace工具
  6. *
  7. * @date 2017310
  8. * @since 1.0.0
  9. */
  10. public class TraceUtil {
  11. public static void traceStart() {
  12. ThreadContext.init();
  13. String traceId = generateTraceId();
  14. MDC.put("traceId", traceId);
  15. ThreadContext.putTraceId(traceId);
  16. }
  17. public static void traceEnd() {
  18. MDC.clear();
  19. ThreadContext.clean();
  20. }
  21. /**
  22. * 生成跟踪ID
  23. *
  24. * @return
  25. */
  26. private static String generateTraceId() {
  27. return UUID.randomUUID().toString();
  28. }
  29. }

三:添加ContextFilter,对于每个请求随机生成RequestID并放入MDC

</>复制代码

  1. import java.io.IOException;
  2. import javax.servlet.FilterChain;
  3. import javax.servlet.ServletException;
  4. import javax.servlet.http.HttpServletRequest;
  5. import javax.servlet.http.HttpServletResponse;
  6. import org.springframework.core.Ordered;
  7. import org.springframework.core.annotation.Order;
  8. import org.springframework.web.filter.OncePerRequestFilter;
  9. import com.pingan.manpan.common.util.TraceUtil;
  10. import com.pingan.manpan.user.dto.ThreadContext;
  11. import com.pingan.manpan.web.common.surpport.IpUtils;
  12. /**
  13. * 上下文Filter
  14. *
  15. * @date 2017/3/10
  16. * @since 1.0.0
  17. */
  18. //@Order 标记组件的加载顺序
  19. @Order(Ordered.HIGHEST_PRECEDENCE)
  20. public class ContextFilter extends OncePerRequestFilter {
  21. @Override
  22. protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
  23. FilterChain filterChain) throws ServletException, IOException {
  24. try {
  25. ThreadContext.putClientIp(IpUtils.getClientIp(request));
  26. TraceUtil.traceStart();
  27. filterChain.doFilter(request, response);
  28. } finally {
  29. TraceUtil.traceEnd();
  30. }
  31. }
  32. }

四:在webConfiguriation注册filter

</>复制代码

  1. /**
  2. * 请求上下文,应该在最外层
  3. *
  4. * @return
  5. */
  6. @Bean
  7. public FilterRegistrationBean requestContextRepositoryFilterRegistrationBean() {
  8. FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
  9. filterRegistrationBean.setFilter(new ContextFilter());
  10. filterRegistrationBean.addUrlPatterns("/*");
  11. return filterRegistrationBean;
  12. }

五:修改log4j日志配置文件,设置日志traceId

</>复制代码

  1. ${FILE_LOG_PATTERN}
  2. ${LOG_FILE}${LOG_FILE_SUFFIX}
  3. ${LOG_FILE}.%d{yyyy-MM-dd}${LOG_FILE_SUFFIX}
  4. 127.0.0.1
  5. local6
  6. 514
  7. ${FILE_LOG_PATTERN}

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/68984.html

相关文章

  • 日志排查问题困难?分布式日志链路跟踪来帮你

    摘要:当前线程的子线程会继承其父线程中的的内容。若希望在线程池与主线程间传递,需配合和使用。 一、背景 开发排查系统问题用得最多的手段就是查看系统日志,在分布式环境中一般使用ELK来统一收集日志,但是在并发大时使用日志定位问题还是比较麻烦,由于大量的其他用户/其他线程的日志也一起输出穿行其中导致很难筛选出指定请求的全部相关日志,以及下游线程/服务对应的日志。   二、解决思路 每个请求都使...

    EasonTyler 评论0 收藏0
  • 分布式调用跟踪实战

    摘要:为了追踪一个请求完整的流转过程,我可以给请求分配一个唯一的,当请求调用其他服务时,我们传递这个。这是一个简单的实现分布式调用追踪的实践,以上。 背景 分布式环境下,跨服务之间的调用错综复杂,如果突然爆出一个错误,虽然有日志记录,但到底是哪个服务出了问题呢?是移动端传的参数有错误,还是系统X或者系统Y提供的接口导致?在这种情况下,错误排查起来就非常费劲。 为了追踪一个请求完整的流转过程,...

    jlanglang 评论0 收藏0
  • Spring Boot 参考指南(日志记录)

    摘要:默认情况下,如果使用,则使用进行日志记录,还包括适当的路由,以确保使用或的依赖库都能正确工作。分隔符,用于区分实际日志消息的开始。 26. 日志记录 Spring Boot为所有内部日志记录使用Commons Logging,但开放底层日志实现,提供了Java Util Logging、Log4J2和Logback的默认配置,在每种情况下,日志记录器都被预先配置为使用控制台输出,可选的...

    mengera88 评论0 收藏0
  • Spring Cloud 参考文档(Spring Cloud Sleuth特性)

    摘要:介绍从版本开始,使用作为追踪库,为方便起见,在此处嵌入了的部分文档。具有一个上下文,其中包含标识符,该标识符将放置在表示分布式操作的树中的正确位置。追踪通常由拦截器自动完成,在幕后,他们添加与他们在操作中的角色相关的标签和事件。 Spring Cloud Sleuth特性 将trace和span ID添加到Slf4J MDC,因此你可以在日志聚合器中从给定的trace或span提取...

    chinafgj 评论0 收藏0
  • 【Java笔记】ThreadLocal的学习和理解

    摘要:底层是一个的散列表可扩容的数组,并采用开放地址法来解决冲突。稍后讨论方法每个对象都有一个值,每初始化一个对象,值就增加一个固定的大小。因此在使用的时候要手动调用方法,防止内存泄漏。 ThreadLocal定义 先看JDK关于ThreadLocal的类注释: This class provides thread-local variables. These variables diffe...

    red_bricks 评论0 收藏0

发表评论

0条评论

aristark

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<