如何为 slf4j 记录器创建切面切点?

问题描述 投票:0回答:2

我想将字符串添加到我的所有日志中以实现此目的,我计划使用 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 调用创建切入点,以便我可以将字符串添加到每条消息中

java log4j aspectj slf4j spring-aop
2个回答
1
投票

这里有完整的 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, ..)

调整这个解决方案的方法有很多,列出示例需要几个小时。


0
投票

我找到了另一种方法来做到这一点,但上述解决方案是更正确和更好的方法

我在 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调用之后我必须从线程上下文中删除这个用户名,因为如果你不从线程上下文中删除这个值,这个线程将进入线程池并被另一个进程调用,它也将携带你的值在我的例子中添加它是用户名。因此,如果我不删除我的值,我可以看到不应该看到用户名的用户名

© www.soinside.com 2019 - 2024. All rights reserved.