Spring如何实现输出带动态标签的日志
作者:keep丶
文章介绍了如何通过动态标签日志实现,解决了部分业务代码在多个模块中调用时日志无法直观看出来源的问题,主要通过ThreadLocal存储业务标签,并在日志输出时插入该标签,实现日志的动态标签功能,感兴趣的朋友一起看看吧
版权说明: 本文由CSDN博主keep丶原创,转载请保留此块内容在文首。
原文地址: https://blog.csdn.net/qq_38688267/article/details/145022997
背景
部分业务代码会被多个模块调用,此时该部分代码输出的日志无法直观看出是从哪个模块调用的,因此提出动态标签日志需求,效果如下:
底层原理
业务代码起始时通过ThreadLocal存储当前业务标签值,后续日志输出时,插入缓存的业务标签到输出的日志中。即可实现该需求。
实现方案
Tag缓存实现
private static final ThreadLocal<String> logTagCache = new ThreadLocal<>(); /** * 获取缓存的标签值 * <b style="color:red">只有添加了@BizLog注解的方法内才可用</b> */ static String getCacheTag() { String temp = logTagCache.get(); if (temp == null) { log.warn("[LogHelper] 缓存标签为空, 请及时配置@BizLog注解或手动缓存标签."); return DEFAULT_TAG; } return temp; } static void cacheTag(String logTag) { logTagCache.set(logTag); } /** * 清空当前线程缓存 * <br/> * <b>使用set()或init()之后,请在合适的地方调用clean(),一般用try-finally语法在finally块中调用</b> */ static void cleanCache() { logTagCache.remove(); }
封装注解通过AOP实现日志缓存
注解定义
/** * 启动业务日志注解 * * @author zeng.zf */ @Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) @Inherited public @interface BizLog { /** * 日志标签值 * <p> * 如果不给值则默认输出类型方法名,给值用给的值<br/> * <b>会缓存标签值,可使用{@code LogHelper.xxxWCT()}方法</b> * @see cn.xxx.log.util.LogHelper.WCT#catchLog(Logger, Runnable) * @see cn.xxx.log.util.LogHelper.WCT#log(String, Object...) */ String value(); }
AOP切面配置
/** * {@link BizLog} 切面,记录业务链路 * * @author zzf */ @Aspect @Order// 日志的AOP逻辑放最后执行 public class BizLogAspect { @Around("@annotation(bizLog)") public Object around(ProceedingJoinPoint joinPoint, BizLog bizLog) throws Throwable { try { LogHelper.UTIL.cacheTag(bizLog.value()); return joinPoint.proceed(); } finally { LogHelper.UTIL.cleanCache(); } }
封装行为参数通用方法实现
/** * 缓存给定tag后执行给定方法<br/> * 使用:{@code LogHelper.beginTrace("AddUser", () -> userService.addUser(user))} * * @param tag 日志标签 * @param runnable 执行内容 */ static void beginTrace(String tag, Runnable runnable) { try { cacheTag(tag); runnable.run(); } finally { cleanCache(); } } /** * 缓存给定tag后执行给定方法<br/> * 使用:{@code return LogHelper.beginTrace("AddUser", () -> userService.addUser(user))} * * @param tag 日志标签 * @param supplier 有返回值的执行内容 */ static <T> T beginTrace(String tag, Supplier<T> supplier) { try { cacheTag(tag); return supplier.get(); } finally { cleanCache(); } }
手动缓存Tag值
- 非Bean方法可通过手动调用LogHelper.UTIL.beginTrace()方法实现@BizLog相同功能。
- 也可以参考方法手写cacheTag()和cleanCache()实现该功能。
- 一般不建议这么做,使用工具类方法最好。
- 如果runnable参数会抛异常的情况下就不适合用工具方法,此时可以手写。
- 手写时必须使用try-finally块,并在finally块中调用cleanCache()。
整理代码,封装通用LogHelper类
/** * 日志输出辅助类 * <br/> * 注意:所有格式化参数在格式化时都是调用其toString()方法<br/> * 因此对象需要重写toString()方法或者使用{@code JSONUtil.toJsonStr()}转成JSON字符串。<br/> * <br/> * <b>如果自行输出日志,请按照该格式: {@code "[TAG][SUB_TAG] CONTENT"}</b> * <p>如: 1. {@code "[AddUser] add success"}</p> * <p>  2. {@code "[AddUser][GenRole] add success"}</p> * <p>  2. {@code "[AddUser][BizException] 用户名重复"}</p> * <p>更多请参考源文件中的LogHelperTest测试类</p> */ @Slf4j public class LogHelper { /** * 缓存{@link cn.xxx.log.core.aop.log.BizLog} 注解的value值 */ private static final ThreadLocal<String> logTagCache = new ThreadLocal<>(); private static final String DEFAULT_TAG = "TAG_NOT_CONFIG"; /*===========以下为工具方法,提供Tag缓存相关方法============*/ public interface UTIL { /** * 缓存给定tag后执行给定方法<br/> * 使用:{@code LogHelper.beginTrace("AddUser", () -> userService.addUser(user))} * * @param tag 日志标签 * @param runnable 执行内容 */ static void beginTrace(String tag, Runnable runnable) { try { cacheTag(tag); runnable.run(); } finally { cleanCache(); } } /** * 缓存给定tag后执行给定方法<br/> * 使用:{@code return LogHelper.beginTrace("AddUser", () -> userService.addUser(user))} * * @param tag 日志标签 * @param supplier 有返回值的执行内容 */ static <T> T beginTrace(String tag, Supplier<T> supplier) { try { cacheTag(tag); return supplier.get(); } finally { cleanCache(); } } /** * 缓存给定tag后执行给定方法,提供默认异常处理<br/> * 使用:{@code LogHelper.catchBeginTrace(log, "AddUser", () -> userService.addUser(user))} * * @param tag 日志标签 * @param runnable 执行内容 */ static void catchBeginTrace(Logger logger, String tag, Runnable runnable) { try { cacheTag(tag); WCT.catchLog(logger, runnable); } finally { cleanCache(); } } /** * 缓存给定tag后执行给定方法,提供默认异常处理<br/> * 使用:{@code return LogHelper.catchBeginTrace(log, "AddUser", () -> userService.addUser(user))} * * @param tag 日志标签 * @param supplier 有返回值的执行内容 */ static <T> @Nullable T catchBeginTrace(Logger logger, String tag, Supplier<T> supplier) { try { cacheTag(tag); return WCT.catchLog(logger, supplier); } finally { cleanCache(); } } /** * 获取缓存的标签值 * <b style="color:red">只有添加了@BizLog注解的方法内才可用</b> */ static String getCacheTag() { String temp = logTagCache.get(); if (temp == null) { log.warn("[LogHelper] 缓存标签为空, 请及时配置@BizLog注解或手动缓存标签."); return DEFAULT_TAG; } return temp; } static void cacheTag(String logTag) { logTagCache.set(logTag); } /** * 清空当前线程缓存 * <br/> * <b>使用set()或init()之后,请在合适的地方调用clean(),一般用try-finally语法在finally块中调用</b> */ static void cleanCache() { logTagCache.remove(); } } /*=========================以下为基础方法,提供基础日志输出方法=======================*/ public interface BASIC { /** * 标签日志<br/> * 例: * {@code LogHelper.tag("AddUser", "GenRole", "add success, user id = {}, name = {}", 1L, "zs")}<br/> * 返回 {@code "[AddUser][GenRole] add success, user id = 1, name = zs"} * * @param tag 标签 * @param content 需要格式化内容 * @param arg 格式化参数 * @return 最终日志 */ static String tag(String tag, String content, Object... arg) { return StrUtil.format("[{}] {}", tag, StrUtil.format(content, arg)); } /** * 两级标签日志<br/> * 例: * {@code LogHelper.tag("AddUser", "GenRole", "add success")}<br/> * 返回 {@code "[AddUser][GenRole] add success"} * * @param tag 标签 * @param subTag 子标签 * @param content 内容 * @return 最终日志 */ static String doubleTag(String tag, String subTag, String content, Object... args) { return StrUtil.format("[{}][{}] {}", tag, subTag, StrUtil.format(content, args)); } /** * 业务异常tag日志内容生成 */ static String bizExTag(String tag, BizExceptionMark bizException) { return StrUtil.format("[{}][{}] code={},msg={}", tag, bizException.getClass().getSimpleName(), bizException.getCode(), bizException.getMsg()); } /** * 业务异常tag日志内容生成 */ static String bizExTag(String tag, BizExceptionMark bizException, String extraInfo, Object... args) { return StrUtil.format("[{}][{}] code={},msg={}, extraInfo={{}}", tag, bizException.getClass().getSimpleName(), bizException.getCode(), bizException.getMsg(), StrUtil.format(extraInfo, args)); } /** * 业务异常tag日志内容生成 */ static String bizEx(BizExceptionMark bizException) { return StrUtil.format("[{}] code={},msg={}", bizException.getClass().getSimpleName(), bizException.getCode(), bizException.getMsg()); } /** * 运行时异常tag日志内容生成 */ static String otherExTag(String tag, Exception e) { return StrUtil.format("[{}][{}] msg={}, stackTrace={}", tag, e.getClass().getSimpleName(), e.getMessage(), TraceUtils.getStackTraceStr(e.getStackTrace())); } /** * 运行时异常tag日志内容生成 */ static String otherExTag(String tag, Exception e, String extraInfo, Object... args) { return StrUtil.format("[{}][{}] msg={}, extraInfo={{}}, stackTrace={}", tag, e.getClass().getSimpleName(), e.getMessage(), StrUtil.format(extraInfo, args), TraceUtils.getStackTraceStr(e.getStackTrace())); } /** * 其他异常tag日志内容生成 */ static String otherEx(Exception e) { return StrUtil.format("[{}] msg={}, stackTrace={}", e.getClass().getSimpleName(), e.getMessage(), TraceUtils.getStackTraceStr(e.getStackTrace())); } /** * 通用标签日志包装<br/> * <p>使用案例:</p> * 1. {@code tagLogWrap(() -> bizMethod(param1, param2))}<br/> * 2. * <pre><code> * \@Slf4j * public class UserServiceImpl{ * * public void addUsers(List<User> users) { * for(user in users) { * LogHelper.tagLogWrap(log, "AddUsers", () -> addUser(user)); * } * } * public void addUser(User user) { * xxx * xxx * ... * } * } * </code></pre> * * @param tag 日志标签 * @param runnable 你要执行的无返回值方法 */ static void catchLog(Logger logger, String tag, Runnable runnable) { try { runnable.run(); } catch (Exception e) { if (e instanceof BizExceptionMark) { logger.warn(bizExTag(tag, (BizExceptionMark) e)); } else { logger.error(otherExTag(tag, e)); } } } /** * 通用标签日志包装<br/> * <p>使用案例:</p> * 1. {@code tagLogWrap(() -> bizMethod(param1, param2))}<br/> * 2. * <pre><code> * \@Slf4j * public class UserServiceImpl{ * * public void addUsers(List<User> users) { * for(user in users) { * LogHelper.tagLogWrap( * log, * "AddUsers", * () -> addUser(user), * "id = {}, name={}", * user.getId(), * user.getName() * ); * } * } * public void addUser(User user) { * xxx * xxx * ... * } * } * </code></pre> * * @param tag 日志标签 * @param runnable 你要执行的无返回值方法 */ static void catchLog(Logger logger, String tag, Runnable runnable, String extraInfo, Object... args) { try { runnable.run(); } catch (Exception e) { if (e instanceof BizExceptionMark) { logger.warn(bizExTag(tag, (BizExceptionMark) e, extraInfo, args)); } else { logger.error(otherExTag(tag, e, extraInfo, args)); } } } /** * 通用标签日志包装<br/> * <p>使用案例:</p> * 1. {@code return tagLogWrap(() -> bizMethod(param1, param2))}<br/> * 2. * <pre><code> * \@Slf4j * public class UserServiceImpl{ * * public List<User> getUserByIds(List<Long> ids) { * return ids.map(id -> * LogHelper.tagLogWrap(log, "getUserByIds", () -> getUserById(id)) * ).collect(Collectors.toList()); * } * public User getUserById(Long userId) { * xxx * xxx * ... * return user; * } * } * </code></pre> * * @param tag 日志标签 * @param supplier 你要执行的有返回值方法 * @return 方法执行结果(可能为null) */ static <R> @Nullable R catchLog(Logger logger, String tag, Supplier<R> supplier) { try { return supplier.get(); } catch (Exception e) { if (e instanceof BizExceptionMark) { logger.warn(bizExTag(tag, (BizExceptionMark) e)); } else { logger.error(otherExTag(tag, e)); } } return null; } /** * 通用标签日志包装<br/> * <p>使用案例:</p> * 1. {@code return tagLogWrap(() -> bizMethod(param1, param2))}<br/> * 2. * <pre><code> * \@Slf4j * public class UserServiceImpl{ * * public List<User> getUserByIds(List<Long> ids) { * return ids.map(id -> * LogHelper.tagLogWrap( * log, * "getUserByIds", * () -> getUserById(id), * "id={}", * id * ) * ).collect(Collectors.toList()); * } * public User getUserById(Long userId) { * xxx * xxx * ... * return user; * } * } * </code></pre> * * @param tag 日志标签 * @param supplier 你要执行的有返回值方法 * @return 方法执行结果(可能为null) */ static <R> @Nullable R catchLog(Logger logger, String tag, Supplier<R> supplier, String extraInfo, Object... args) { try { return supplier.get(); } catch (Exception e) { if (e instanceof BizExceptionMark) { logger.warn(bizExTag(tag, (BizExceptionMark) e, extraInfo, args)); } else { logger.error(otherExTag(tag, e, extraInfo, args)); } } return null; } } /*===================以下为基于缓存标签的方法,理论上上方基础方法都要在下面有对应的方法==================*/ /* WCT = with cached tag */ public interface WCT { /** * <b style="color:red">只有添加了@BizLog注解的方法内才可用</b> */ static String log(String content, Object... args) { return BASIC.tag(getCacheTag(), content, args); } /** * <b style="color:red">只有添加了@BizLog注解的方法内才可用</b> */ static String sub(String subTag, String content, Object... args) { return BASIC.doubleTag(getCacheTag(), subTag, content, args); } /** * 业务异常tag日志内容生成 * <b style="color:red">只有添加了@BizLog注解的方法内才可用</b> */ static String bizEx(BizExceptionMark bizException) { return BASIC.bizExTag(getCacheTag(), bizException); } /** * 业务异常tag日志内容生成 * <b style="color:red">只有添加了@BizLog注解的方法内才可用</b> */ static String bizEx(BizExceptionMark bizException, String extraInfo, Object... args) { return BASIC.bizExTag(getCacheTag(), bizException, extraInfo, args); } /** * 运行时异常tag日志内容生成 * <b style="color:red">只有添加了@BizLog注解的方法内才可用</b> */ static String otherEx(Exception e) { return BASIC.otherExTag(getCacheTag(), e); } /** * 运行时异常tag日志内容生成 * <b style="color:red">只有添加了@BizLog注解的方法内才可用</b> */ static String otherEx(String tag, Exception e, String extraInfo, Object... args) { return BASIC.otherExTag(tag, e, extraInfo, args); } /** * 通用标签日志包装<br/> * <b style="color:red">只有添加了@BizLog注解的方法内才可用</b> * * @param runnable 你要执行的无返回值方法 */ static void catchLog(Logger logger, Runnable runnable) { BASIC.catchLog(logger, getCacheTag(), runnable); } /** * 通用标签日志包装<br/> * <b style="color:red">只有添加了@BizLog注解的方法内才可用</b> * * @param runnable 你要执行的无返回值方法 */ static void catchLog(Logger logger, Runnable runnable, String extraInfo, Object... args) { BASIC.catchLog(logger, getCacheTag(), runnable, extraInfo, args); } /** * 通用标签日志包装<br/> * <b style="color:red">只有添加了@BizLog注解的方法内才可用</b> * * @param supplier 你要执行的有返回值方法 * @return 方法执行结果(可能为null) */ static <R> @Nullable R catchLog(Logger logger, Supplier<R> supplier) { return BASIC.catchLog(logger, getCacheTag(), supplier); } /** * 通用标签日志包装<br/> * <b style="color:red">只有添加了@BizLog注解的方法内才可用</b> * * @param supplier 你要执行的有返回值方法 * @return 方法执行结果(可能为null) */ static <R> @Nullable R catchLog(Logger logger, Supplier<R> supplier, String extraInfo, Object... args) { return BASIC.catchLog(logger, getCacheTag(), supplier, extraInfo, args); } } }
相关资料
到此这篇关于Spring实现输出带动态标签的日志的文章就介绍到这了,更多相关Spring动态标签的日志内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!