Spring如何實(shí)現(xiàn)輸出帶動(dòng)態(tài)標(biāo)簽的日志
版權(quán)說(shuō)明: 本文由CSDN博主keep丶原創(chuàng),轉(zhuǎn)載請(qǐng)保留此塊內(nèi)容在文首。
原文地址: https://blog.csdn.net/qq_38688267/article/details/145022997
背景
部分業(yè)務(wù)代碼會(huì)被多個(gè)模塊調(diào)用,此時(shí)該部分代碼輸出的日志無(wú)法直觀看出是從哪個(gè)模塊調(diào)用的,因此提出動(dòng)態(tài)標(biāo)簽日志需求,效果如下:

底層原理
業(yè)務(wù)代碼起始時(shí)通過(guò)ThreadLocal存儲(chǔ)當(dāng)前業(yè)務(wù)標(biāo)簽值,后續(xù)日志輸出時(shí),插入緩存的業(yè)務(wù)標(biāo)簽到輸出的日志中。即可實(shí)現(xiàn)該需求。
實(shí)現(xiàn)方案
Tag緩存實(shí)現(xiàn)
private static final ThreadLocal<String> logTagCache = new ThreadLocal<>();
/**
* 獲取緩存的標(biāo)簽值
* <b style="color:red">只有添加了@BizLog注解的方法內(nèi)才可用</b>
*/
static String getCacheTag() {
String temp = logTagCache.get();
if (temp == null) {
log.warn("[LogHelper] 緩存標(biāo)簽為空, 請(qǐng)及時(shí)配置@BizLog注解或手動(dòng)緩存標(biāo)簽.");
return DEFAULT_TAG;
}
return temp;
}
static void cacheTag(String logTag) {
logTagCache.set(logTag);
}
/**
* 清空當(dāng)前線程緩存
* <br/>
* <b>使用set()或init()之后,請(qǐng)?jiān)诤线m的地方調(diào)用clean(),一般用try-finally語(yǔ)法在finally塊中調(diào)用</b>
*/
static void cleanCache() {
logTagCache.remove();
}
封裝注解通過(guò)AOP實(shí)現(xiàn)日志緩存
注解定義
/**
* 啟動(dòng)業(yè)務(wù)日志注解
*
* @author zeng.zf
*/
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface BizLog {
/**
* 日志標(biāo)簽值
* <p>
* 如果不給值則默認(rèn)輸出類型方法名,給值用給的值<br/>
* <b>會(huì)緩存標(biāo)簽值,可使用{@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} 切面,記錄業(yè)務(wù)鏈路
*
* @author zzf
*/
@Aspect
@Order// 日志的AOP邏輯放最后執(zhí)行
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();
}
}
封裝行為參數(shù)通用方法實(shí)現(xiàn)
/**
* 緩存給定tag后執(zhí)行給定方法<br/>
* 使用:{@code LogHelper.beginTrace("AddUser", () -> userService.addUser(user))}
*
* @param tag 日志標(biāo)簽
* @param runnable 執(zhí)行內(nèi)容
*/
static void beginTrace(String tag, Runnable runnable) {
try {
cacheTag(tag);
runnable.run();
} finally {
cleanCache();
}
}
/**
* 緩存給定tag后執(zhí)行給定方法<br/>
* 使用:{@code return LogHelper.beginTrace("AddUser", () -> userService.addUser(user))}
*
* @param tag 日志標(biāo)簽
* @param supplier 有返回值的執(zhí)行內(nèi)容
*/
static <T> T beginTrace(String tag, Supplier<T> supplier) {
try {
cacheTag(tag);
return supplier.get();
} finally {
cleanCache();
}
}手動(dòng)緩存Tag值
- 非Bean方法可通過(guò)手動(dòng)調(diào)用LogHelper.UTIL.beginTrace()方法實(shí)現(xiàn)@BizLog相同功能。
- 也可以參考方法手寫cacheTag()和cleanCache()實(shí)現(xiàn)該功能。
- 一般不建議這么做,使用工具類方法最好。
- 如果runnable參數(shù)會(huì)拋異常的情況下就不適合用工具方法,此時(shí)可以手寫。
- 手寫時(shí)必須使用try-finally塊,并在finally塊中調(diào)用cleanCache()。

整理代碼,封裝通用LogHelper類
/**
* 日志輸出輔助類
* <br/>
* 注意:所有格式化參數(shù)在格式化時(shí)都是調(diào)用其toString()方法<br/>
* 因此對(duì)象需要重寫toString()方法或者使用{@code JSONUtil.toJsonStr()}轉(zhuǎn)成JSON字符串。<br/>
* <br/>
* <b>如果自行輸出日志,請(qǐng)按照該格式: {@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] 用戶名重復(fù)"}</p>
* <p>更多請(qǐng)參考源文件中的LogHelperTest測(cè)試類</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緩存相關(guān)方法============*/
public interface UTIL {
/**
* 緩存給定tag后執(zhí)行給定方法<br/>
* 使用:{@code LogHelper.beginTrace("AddUser", () -> userService.addUser(user))}
*
* @param tag 日志標(biāo)簽
* @param runnable 執(zhí)行內(nèi)容
*/
static void beginTrace(String tag, Runnable runnable) {
try {
cacheTag(tag);
runnable.run();
} finally {
cleanCache();
}
}
/**
* 緩存給定tag后執(zhí)行給定方法<br/>
* 使用:{@code return LogHelper.beginTrace("AddUser", () -> userService.addUser(user))}
*
* @param tag 日志標(biāo)簽
* @param supplier 有返回值的執(zhí)行內(nèi)容
*/
static <T> T beginTrace(String tag, Supplier<T> supplier) {
try {
cacheTag(tag);
return supplier.get();
} finally {
cleanCache();
}
}
/**
* 緩存給定tag后執(zhí)行給定方法,提供默認(rèn)異常處理<br/>
* 使用:{@code LogHelper.catchBeginTrace(log, "AddUser", () -> userService.addUser(user))}
*
* @param tag 日志標(biāo)簽
* @param runnable 執(zhí)行內(nèi)容
*/
static void catchBeginTrace(Logger logger, String tag, Runnable runnable) {
try {
cacheTag(tag);
WCT.catchLog(logger, runnable);
} finally {
cleanCache();
}
}
/**
* 緩存給定tag后執(zhí)行給定方法,提供默認(rèn)異常處理<br/>
* 使用:{@code return LogHelper.catchBeginTrace(log, "AddUser", () -> userService.addUser(user))}
*
* @param tag 日志標(biāo)簽
* @param supplier 有返回值的執(zhí)行內(nèi)容
*/
static <T> @Nullable T catchBeginTrace(Logger logger, String tag, Supplier<T> supplier) {
try {
cacheTag(tag);
return WCT.catchLog(logger, supplier);
} finally {
cleanCache();
}
}
/**
* 獲取緩存的標(biāo)簽值
* <b style="color:red">只有添加了@BizLog注解的方法內(nèi)才可用</b>
*/
static String getCacheTag() {
String temp = logTagCache.get();
if (temp == null) {
log.warn("[LogHelper] 緩存標(biāo)簽為空, 請(qǐng)及時(shí)配置@BizLog注解或手動(dòng)緩存標(biāo)簽.");
return DEFAULT_TAG;
}
return temp;
}
static void cacheTag(String logTag) {
logTagCache.set(logTag);
}
/**
* 清空當(dāng)前線程緩存
* <br/>
* <b>使用set()或init()之后,請(qǐng)?jiān)诤线m的地方調(diào)用clean(),一般用try-finally語(yǔ)法在finally塊中調(diào)用</b>
*/
static void cleanCache() {
logTagCache.remove();
}
}
/*=========================以下為基礎(chǔ)方法,提供基礎(chǔ)日志輸出方法=======================*/
public interface BASIC {
/**
* 標(biāo)簽日志<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 標(biāo)簽
* @param content 需要格式化內(nèi)容
* @param arg 格式化參數(shù)
* @return 最終日志
*/
static String tag(String tag, String content, Object... arg) {
return StrUtil.format("[{}] {}", tag, StrUtil.format(content, arg));
}
/**
* 兩級(jí)標(biāo)簽日志<br/>
* 例:
* {@code LogHelper.tag("AddUser", "GenRole", "add success")}<br/>
* 返回 {@code "[AddUser][GenRole] add success"}
*
* @param tag 標(biāo)簽
* @param subTag 子標(biāo)簽
* @param content 內(nèi)容
* @return 最終日志
*/
static String doubleTag(String tag, String subTag, String content, Object... args) {
return StrUtil.format("[{}][{}] {}", tag, subTag, StrUtil.format(content, args));
}
/**
* 業(yè)務(wù)異常tag日志內(nèi)容生成
*/
static String bizExTag(String tag, BizExceptionMark bizException) {
return StrUtil.format("[{}][{}] code={},msg={}", tag, bizException.getClass().getSimpleName(),
bizException.getCode(), bizException.getMsg());
}
/**
* 業(yè)務(wù)異常tag日志內(nèi)容生成
*/
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));
}
/**
* 業(yè)務(wù)異常tag日志內(nèi)容生成
*/
static String bizEx(BizExceptionMark bizException) {
return StrUtil.format("[{}] code={},msg={}", bizException.getClass().getSimpleName(),
bizException.getCode(), bizException.getMsg());
}
/**
* 運(yùn)行時(shí)異常tag日志內(nèi)容生成
*/
static String otherExTag(String tag, Exception e) {
return StrUtil.format("[{}][{}] msg={}, stackTrace={}", tag, e.getClass().getSimpleName(), e.getMessage(),
TraceUtils.getStackTraceStr(e.getStackTrace()));
}
/**
* 運(yùn)行時(shí)異常tag日志內(nèi)容生成
*/
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日志內(nèi)容生成
*/
static String otherEx(Exception e) {
return StrUtil.format("[{}] msg={}, stackTrace={}", e.getClass().getSimpleName(), e.getMessage(),
TraceUtils.getStackTraceStr(e.getStackTrace()));
}
/**
* 通用標(biāo)簽日志包裝<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 日志標(biāo)簽
* @param runnable 你要執(zhí)行的無(wú)返回值方法
*/
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));
}
}
}
/**
* 通用標(biāo)簽日志包裝<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 日志標(biāo)簽
* @param runnable 你要執(zhí)行的無(wú)返回值方法
*/
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));
}
}
}
/**
* 通用標(biāo)簽日志包裝<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 日志標(biāo)簽
* @param supplier 你要執(zhí)行的有返回值方法
* @return 方法執(zhí)行結(jié)果(可能為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;
}
/**
* 通用標(biāo)簽日志包裝<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 日志標(biāo)簽
* @param supplier 你要執(zhí)行的有返回值方法
* @return 方法執(zhí)行結(jié)果(可能為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;
}
}
/*===================以下為基于緩存標(biāo)簽的方法,理論上上方基礎(chǔ)方法都要在下面有對(duì)應(yīng)的方法==================*/
/* WCT = with cached tag */
public interface WCT {
/**
* <b style="color:red">只有添加了@BizLog注解的方法內(nèi)才可用</b>
*/
static String log(String content, Object... args) {
return BASIC.tag(getCacheTag(), content, args);
}
/**
* <b style="color:red">只有添加了@BizLog注解的方法內(nèi)才可用</b>
*/
static String sub(String subTag, String content, Object... args) {
return BASIC.doubleTag(getCacheTag(), subTag, content, args);
}
/**
* 業(yè)務(wù)異常tag日志內(nèi)容生成
* <b style="color:red">只有添加了@BizLog注解的方法內(nèi)才可用</b>
*/
static String bizEx(BizExceptionMark bizException) {
return BASIC.bizExTag(getCacheTag(), bizException);
}
/**
* 業(yè)務(wù)異常tag日志內(nèi)容生成
* <b style="color:red">只有添加了@BizLog注解的方法內(nèi)才可用</b>
*/
static String bizEx(BizExceptionMark bizException, String extraInfo, Object... args) {
return BASIC.bizExTag(getCacheTag(), bizException, extraInfo, args);
}
/**
* 運(yùn)行時(shí)異常tag日志內(nèi)容生成
* <b style="color:red">只有添加了@BizLog注解的方法內(nèi)才可用</b>
*/
static String otherEx(Exception e) {
return BASIC.otherExTag(getCacheTag(), e);
}
/**
* 運(yùn)行時(shí)異常tag日志內(nèi)容生成
* <b style="color:red">只有添加了@BizLog注解的方法內(nèi)才可用</b>
*/
static String otherEx(String tag, Exception e, String extraInfo, Object... args) {
return BASIC.otherExTag(tag, e, extraInfo, args);
}
/**
* 通用標(biāo)簽日志包裝<br/>
* <b style="color:red">只有添加了@BizLog注解的方法內(nèi)才可用</b>
*
* @param runnable 你要執(zhí)行的無(wú)返回值方法
*/
static void catchLog(Logger logger, Runnable runnable) {
BASIC.catchLog(logger, getCacheTag(), runnable);
}
/**
* 通用標(biāo)簽日志包裝<br/>
* <b style="color:red">只有添加了@BizLog注解的方法內(nèi)才可用</b>
*
* @param runnable 你要執(zhí)行的無(wú)返回值方法
*/
static void catchLog(Logger logger, Runnable runnable, String extraInfo, Object... args) {
BASIC.catchLog(logger, getCacheTag(), runnable, extraInfo, args);
}
/**
* 通用標(biāo)簽日志包裝<br/>
* <b style="color:red">只有添加了@BizLog注解的方法內(nèi)才可用</b>
*
* @param supplier 你要執(zhí)行的有返回值方法
* @return 方法執(zhí)行結(jié)果(可能為null)
*/
static <R> @Nullable R catchLog(Logger logger, Supplier<R> supplier) {
return BASIC.catchLog(logger, getCacheTag(), supplier);
}
/**
* 通用標(biāo)簽日志包裝<br/>
* <b style="color:red">只有添加了@BizLog注解的方法內(nèi)才可用</b>
*
* @param supplier 你要執(zhí)行的有返回值方法
* @return 方法執(zhí)行結(jié)果(可能為null)
*/
static <R> @Nullable R catchLog(Logger logger, Supplier<R> supplier, String extraInfo, Object... args) {
return BASIC.catchLog(logger, getCacheTag(), supplier, extraInfo, args);
}
}
}相關(guān)資料
Spring實(shí)現(xiàn)Logback日志模板設(shè)置動(dòng)態(tài)參數(shù)
Spring實(shí)現(xiàn)通過(guò)工具類統(tǒng)一輸出日志(不改變?nèi)罩绢愋畔?
到此這篇關(guān)于Spring實(shí)現(xiàn)輸出帶動(dòng)態(tài)標(biāo)簽的日志的文章就介紹到這了,更多相關(guān)Spring動(dòng)態(tài)標(biāo)簽的日志內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java 重載(overload)與重寫(override)詳解及實(shí)例
這篇文章主要介紹了java 重載(overload)與重寫(override)詳解及實(shí)例的相關(guān)資料,并附實(shí)例代碼,需要的朋友可以參考下2016-10-10
詳解如何在SpringBoot中優(yōu)雅地重試調(diào)用第三方API
在實(shí)際的應(yīng)用中,我們經(jīng)常需要調(diào)用第三方API來(lái)獲取數(shù)據(jù)或執(zhí)行某些操作,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-12-12
Python自定義計(jì)算時(shí)間過(guò)濾器實(shí)現(xiàn)過(guò)程解析
這篇文章主要介紹了Python自定義計(jì)算時(shí)間過(guò)濾器實(shí)現(xiàn)過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-01-01
java Gui實(shí)現(xiàn)肯德基點(diǎn)餐收銀系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了java Gui實(shí)現(xiàn)肯德基點(diǎn)餐收銀系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-01-01
springboot 事件監(jiān)聽(tīng)的實(shí)現(xiàn)方法
這篇文章主要介紹了springboot 事件監(jiān)聽(tīng)的實(shí)現(xiàn)方法,并詳細(xì)的介紹了四種監(jiān)聽(tīng)方式,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-04-04

