我想将字符串添加到我的所有日志中以实现此目的,我计划使用 aop,但我无法为我的所有记录器对象声明切点。我正在使用 slf4j 记录器,这里是类中的示例日志
Logger logger = LoggerFactory.getLogger(InterviewService.class);
logger.error(ex.getMessage(),ex);
我正在尝试拦截 ss 中的记录器,因此在打印该日志之前我可以更改消息中的参数并添加我的文本 我目前稍微更改了代码,我可以捕获抛出的异常,但仍然无法拦截方面方法中的 logger.error("some error log") 消息。
@Pointcut("within(org.apache.logging.log4j.*)")
public void logPointcut() {}
@Pointcut("within(*..Log4jLogger.*)")
public void logPointcuts() {}
@Pointcut("execution(* *.error(*))")
public void logPointcutsMethod() {}
@Pointcut("within(*.error)")
public void logPointcutsMethodw() {}
@Around("logPointcutsMethod() || logPointcuts() || logPointcut() || logPointcutsMethodw()")
public Object logError(ProceedingJoinPoint joinPoint) throws Throwable {
Object result = joinPoint.proceed();
String user = "";
if (state)
user = getUser();
logger.error(
"Exception in {} User :{}.{}() with cause = {}",
joinPoint.getSignature().getDeclaringTypeName(),
user,
joinPoint.getSignature().getName()
);
return result;
}
总而言之,我想为每个 logger.error 调用创建切入点,以便我可以将字符串添加到每条消息中
这里有完整的 MCVE,介绍如何在本机 AspectJ 中解决此问题,可以使用加载时编织,也可以使用 AspectJ 编译器的 inpath 上的 Slf4J 和 Log4J 库进行编译时编织。因为 Spring AOP 只能拦截 Spring beans/组件内的方法,但 Slf4j 和 Log4j 都不是 Spring 组件,所以您需要一个成人 AOP 框架,而不是像 Spring AOP 这样的 AOP lite 解决方案。幸运的是,Spring 有一个非常好的 AspectJ 集成,并且还在其手册中进行了解释。
package de.scrum_master.app;
import org.apache.log4j.BasicConfigurator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Application {
private static final Logger logger = LoggerFactory.getLogger(Application.class);
public static void main(String[] args) {
BasicConfigurator.configure();
logger.info("Hello Slf4j");
logger.error("uh-oh", new Exception("oops!"));
logger.error("WTF dude?!?");
}
}
package de.scrum_master.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
@Aspect
public class LogEnhancerAspect {
@Around("execution(void org.slf4j.Logger.error(String, ..)) && args(msg, ..)")
public void formatMessageLog(String msg, ProceedingJoinPoint joinPoint) throws Throwable {
Object[] args = joinPoint.getArgs();
args[0] = "[MY PREFIX] " + msg;
joinPoint.proceed(args);
}
}
控制台日志:
0 [main] INFO de.scrum_master.app.Application - Hello Slf4j
7 [main] ERROR de.scrum_master.app.Application - [MY PREFIX] uh-oh
java.lang.Exception: oops!
at de.scrum_master.app.Application.main(Application.java:13)
8 [main] ERROR de.scrum_master.app.Application - [MY PREFIX] WTF dude?!?
这是最简单的解决方案。如果您在日志输出中需要更多信息,例如调用者或异常中的特殊信息,则调整切入点非常容易。此外,
Logger.error(..)
有很多重载。我的示例切入点仅捕获第一个参数为 String
的切入点,例如 error(String)
或 error(String, Throwable)
。如果需要其他,则需要调整方面。但这基本上就是它的工作原理。
另请注意,这将拦截所有错误日志,甚至是从应用程序外部调用的错误日志。如果您想将其限制在应用程序的基础包中,则必须再次调整切入点并使用
call()
而不是 execution()
,例如 within(my.base.pkg..*) && call(void org.slf4j.Logger.error(String, ..)) && args(msg, ..)
。
调整这个解决方案的方法有很多,列出示例需要几个小时。
我找到了另一种方法来做到这一点,但上述解决方案是更正确和更好的方法
我在 Spring Aop 中使用 ThreadContext。下面的代码部分属于我的 Spring Aop 类,因此我为每个端点调用创建一个切入点。当有一个 api 调用时,它会进入 logAround 方法,我在其中声明 ThreadContext
@Pointcut("within(com.saas.contoller.*)")
public void applicationPackagePointcut() {
// Method is empty as this is just a Pointcut, the implementations are in the advices.
}
/**
* Pointcut that matches all Spring beans in the application's main packages.
*/
@Pointcut("within(com.saas.service.*)")
public void servicePackagePointcut() {
// Method is empty as this is just a Pointcut, the implementations are in the advices.
}
@Around("applicationPackagePointcut() && springBeanPointcut()")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
String user = "";
if (state) {
user = getUser();
}
ThreadContext.put("user",user);
Object result = joinPoint.proceed();
ThreadContext.remove("user");
并且还将用户前缀添加到 log4j 模式,如下所示
<Property name="LOG_PATTERN_CONSOLE" >
%d{yyyy-MM-dd HH:mm:ss.SSS} - user:%X{user} %highlight{${LOG_LEVEL_PATTERN:-%5p}}{FATAL=red, ERROR=red, WARN=yellow, INFO=green, DEBUG=blue, TRACE=cyan} %style{[%15.15t]}{magenta} : %style{%-40.40c{1.}}{cyan} : %m%n%ex
</Property>
通过这种方式,在每次 api 调用之前,我将用户名添加到线程上下文,然后 log4j 为每个 logger.error 使用此用户参数(这取决于您如何安排 log4j.xml 文件。从这一点上,您可以添加到跟踪或信息或你的自定义日志级别)并且在api调用之后我必须从线程上下文中删除这个用户名,因为如果你不从线程上下文中删除这个值,这个线程将进入线程池并被另一个进程调用,它也将携带你的值在我的例子中添加它是用户名。因此,如果我不删除我的值,我可以看到不应该看到用户名的用户名