当前位置: 首页 > article >正文

Spring实现输出带动态标签的日志

版权说明: 本文由博主keep丶原创,转载请保留此块内容在文首。
原文地址: https://blog.csdn.net/qq_38688267/article/details/144851857

文章目录

    • 背景
    • 底层原理
    • 实现方案
      • Tag缓存实现
      • 封装注解通过AOP实现日志缓存
      • 封装行为参数通用方法实现
      • 手动缓存Tag值
    • 整理代码,封装通用LogHelper类
    • 相关博客

背景

  部分业务代码会被多个模块调用,此时该部分代码输出的日志无法直观看出是从哪个模块调用的,因此提出动态标签日志需求,效果如下:
在这里插入图片描述

底层原理

  业务代码起始时通过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>&emsp;&emsp;2. {@code "[AddUser][GenRole] add success"}</p>
 * <p>&emsp;&emsp;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实现Logback日志模板设置动态参数

http://www.kler.cn/a/463114.html

相关文章:

  • 云从科技Java面试题及参考答案
  • 除了淘宝、天猫和京东,其他电商平台的按图搜索商品API返回值结构是怎样的?
  • Vue3 内置组件之component
  • 基于51单片机和16X16LED点阵屏(74HC138和74HC595驱动)的小游戏《贪吃蛇》
  • df.groupby()方法使用表达式分组
  • 【文献精读笔记】Explainability for Large Language Models: A Survey (大语言模型的可解释性综述)(三)
  • 【非关系型数据库Redis 】 入门
  • 32单片机从入门到精通之开发环境——库文件(六)
  • 三层交换机的原理详解
  • Keil中的gcc
  • 用PicGo向Github图床上传图片,然后通过markdown语言显示图片
  • Qt天气预报系统设计界面布局第四部分左边
  • 基于单片机中药存放环境监测系统的实现
  • 第三讲 比特币的早期发展
  • overscroll-behavior-解决H5在ios上过度滚动的默认行为
  • 茶叶连锁店管理系统(源码+文档+部署+讲解)
  • MySQL三大日志(binlog、redo log和undo log)详解
  • 职场常用Excel基础01-数据验证
  • LVGL部件篇: 开关部件(lv_switch)
  • 电商数据API接口的稳定性保障与性能优化策略
  • AI as a Platform (AIaaP) for Enterprises — 发展与转型
  • Go信号处理:如何优雅地关闭你的应用
  • 论文略读: Scaling laws with vocabulary: larger model deserve larger vocabularies
  • 科伦川宁生物:绿色科技引领,双翼齐飞筑梦未来
  • 深入聊聊typescript、ES6和JavaScript的关系与前瞻技术发展
  • 数据仓库中的指标体系模型介绍