【问题标题】:How to monitor method execution at third party library?如何监控第三方库的方法执行?
【发布时间】:2020-06-03 07:52:54
【问题描述】:

我有任务以某种方式监控在 logback 库中调用的方法。我开始用spring aop做它。因此,例如,我应该在 ch.qos.logback.classic.Logger 类中捕获所有方法的执行。这是我的观点:

@Aspect
@Configuration
public class LogbackAspect {

    @Before(value = "execution(* ch.qos.logback.classic.Logger.*(..))")
    public void getInfo(JoinPoint joinPoint) {
         System.out.println("+++++AOP " + joinPoint.getSignature().getName());
}
}

但它现在不起作用,那么,有没有可能用spring aop拦截方法执行?还是有更好的方法?

【问题讨论】:

  • Spring AOP 只能用于拦截发生在 Spring bean 上的方法调用。请仔细阅读文档seciton
  • 稍微跑题:请不要将@Configuration@Aspect一起使用。 Spring 配置属于一个单独的配置类,一个方面不适合它。我在这里一遍又一遍地看到这种反模式。 (可能一个人开始这样做时没有多想,每个人都只是使用复制和粘贴。)您应该将@Component 添加到方面。
  • @SoftwareEngineer 根据该方法未被拦截的问题和我的理解是切入点指向外部库类推断。我从未将其发布为答案,并且该评论暗示了可能出现的问题。而且这里没有证据表明记录器也是一个弹簧豆

标签: java spring aop spring-aop


【解决方案1】:

很遗憾,您只能编写自己的代码。

Brendon 的回答不正确,因为它专注于 Android。在 Windows、MacOS、Linux 等普通 Java 平台上,您可以拦截第三方代码,条件如下:

  • 您使用完整的 AspectJ,而不是 Spring AOP。 AspectJ 可以在没有 Spring 的情况下使用,也可以在 Spring 项目中使用。
  • 您使用 LTW(加载时编织),通常通过 JVM 参数 -javaagent:/path/to/aspectjweaver.jar,并正确配置 aop.xml
  • 作为替代方案,您可以通过命令行直接使用 AspectJ 编译器 ajc 或通过 AspectJ Maven 插件 进行二进制编织,即将方面代码编织到您的第三个派对库,创建新的编织类文件并将它们重新打包到新的 JAR 中。然后,您将使用该 JAR 代替原始 JAR。 (顺便说一句,如果库是您可以随应用交付和安装的东西,而不是某些无法替换或覆盖的系统类/库,这也适用于 Android。)

如果您没有使用 Java 命令行参数的限制,我建议使用 LTW 而不是二进制编织。 Spring 手册中甚至有一章描述了如何在 Spring 中直接使用它。


更新:这是一个MCVE,向您展示了如何在纯 Java + AspectJ LTW 中做到这一点。

让我们在安装了 AJDT(AspectJ 开发工具)的 Eclipse 中创建一个 AspectJ 项目。当然,您也可以使用 AspectJ Maven 插件创建一个 Maven 项目,甚至不使用,因为如果您通过 -javaagent:/path/to/aspectjweaver.jar 使用 LTW,实际上您不需要 AspectJ 编译器。我的示例项目有这样的结构(Eclipse 布局,不是 Maven,我只是快速完成):

文件内容如下。

src/de/scrum_master/app/Application.java

package de.scrum_master.app;

import org.slf4j.LoggerFactory;

import ch.qos.logback.classic.Logger;

public class Application {
  private static final Logger logger = (Logger) LoggerFactory.getLogger(Application.class);

  public static void main(String[] args) {
    logger.info("Example log from {}", Application.class.getSimpleName());
  }
}

src/logback.xml

<configuration>
  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
  </appender>

  <root level="debug">
    <appender-ref ref="STDOUT" />
  </root>
</configuration>

src/de/scrum_master/aspect/LogbackAspect.aj(或简称 LogbackAspect.java)

package de.scrum_master.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class LogbackAspect {
  @Before(value = "execution(* ch.qos.logback.classic.Logger.*(..))")
  public void getInfo(JoinPoint joinPoint) {
    System.out.println(joinPoint);
  }
}

src/META-INF/aop.xml

<?xml version="1.0" encoding="UTF-8"?>
<aspectj>
  <aspects>
    <aspect name="de.scrum_master.aspect.LogbackAspect" />
  </aspects>
  <weaver options="-verbose -showWeaveInfo" />
</aspectj>

现在为 Application 创建一个简单的 Java 运行配置,并将 Java 代理添加到命令行:

现在运行程序,控制台日志会是这样的:

[AppClassLoader@18b4aac2] info AspectJ Weaver Version 1.9.5 built on Thursday Nov 28, 2019 at 11:28:53 PST
[AppClassLoader@18b4aac2] info register classloader sun.misc.Launcher$AppClassLoader@18b4aac2
[AppClassLoader@18b4aac2] info using configuration /C:/Users/alexa/Documents/java-src/SO_AJ_LTW_Logback_60295366/bin/META-INF/aop.xml
[AppClassLoader@18b4aac2] info register aspect de.scrum_master.aspect.LogbackAspect
[AppClassLoader@18b4aac2] weaveinfo Join point 'method-execution(ch.qos.logback.classic.Level ch.qos.logback.classic.Logger.getEffectiveLevel())' in Type 'ch.qos.logback.classic.Logger' (Logger.java:109) advised by before advice from 'de.scrum_master.aspect.LogbackAspect' (LogbackAspect.aj)
[AppClassLoader@18b4aac2] weaveinfo Join point 'method-execution(int ch.qos.logback.classic.Logger.getEffectiveLevelInt())' in Type 'ch.qos.logback.classic.Logger' (Logger.java:113) advised by before advice from 'de.scrum_master.aspect.LogbackAspect' (LogbackAspect.aj)
[AppClassLoader@18b4aac2] weaveinfo Join point 'method-execution(ch.qos.logback.classic.Level ch.qos.logback.classic.Logger.getLevel())' in Type 'ch.qos.logback.classic.Logger' (Logger.java:117) advised by before advice from 'de.scrum_master.aspect.LogbackAspect' (LogbackAspect.aj)
(...)
[AppClassLoader@18b4aac2] weaveinfo Join point 'method-execution(java.lang.Object ch.qos.logback.classic.Logger.readResolve())' in Type 'ch.qos.logback.classic.Logger' (Logger.java:787) advised by before advice from 'de.scrum_master.aspect.LogbackAspect' (LogbackAspect.aj)
[AppClassLoader@18b4aac2] info processing reweavable type de.scrum_master.aspect.LogbackAspect: de\scrum_master\aspect\LogbackAspect.aj
[AppClassLoader@18b4aac2] info successfully verified type de.scrum_master.aspect.LogbackAspect exists.  Originates from de\scrum_master\aspect\LogbackAspect.aj
execution(void ch.qos.logback.classic.Logger.setLevel(Level))
execution(void ch.qos.logback.classic.Logger.setLevel(Level))
execution(String ch.qos.logback.classic.Logger.toString())
execution(void ch.qos.logback.classic.Logger.addAppender(Appender))
execution(Logger ch.qos.logback.classic.Logger.getChildByName(String))
execution(Logger ch.qos.logback.classic.Logger.createChildByName(String))
execution(Logger ch.qos.logback.classic.Logger.getChildByName(String))
execution(Logger ch.qos.logback.classic.Logger.createChildByName(String))
execution(Logger ch.qos.logback.classic.Logger.getChildByName(String))
execution(Logger ch.qos.logback.classic.Logger.createChildByName(String))
execution(Logger ch.qos.logback.classic.Logger.getChildByName(String))
execution(Logger ch.qos.logback.classic.Logger.createChildByName(String))
execution(void ch.qos.logback.classic.Logger.info(String, Object))
execution(void ch.qos.logback.classic.Logger.filterAndLog_1(String, Marker, Level, String, Object, Throwable))
execution(void ch.qos.logback.classic.Logger.buildLoggingEventAndAppend(String, Marker, Level, String, Object[], Throwable))
execution(String ch.qos.logback.classic.Logger.getName())
execution(LoggerContext ch.qos.logback.classic.Logger.getLoggerContext())
execution(void ch.qos.logback.classic.Logger.callAppenders(ILoggingEvent))
execution(int ch.qos.logback.classic.Logger.appendLoopOnAppenders(ILoggingEvent))
execution(int ch.qos.logback.classic.Logger.appendLoopOnAppenders(ILoggingEvent))
execution(int ch.qos.logback.classic.Logger.appendLoopOnAppenders(ILoggingEvent))
execution(int ch.qos.logback.classic.Logger.appendLoopOnAppenders(ILoggingEvent))
execution(int ch.qos.logback.classic.Logger.appendLoopOnAppenders(ILoggingEvent))
16:04:52.791 [main] INFO  de.scrum_master.app.Application - Example log from Application

我认为这正是您想要的。请参考Spring manual,了解如何在那里配置 AspectJ LTW。

【讨论】:

  • 这是真的吗?我认为我们可以使用 spring 方面切入 any spring-bean。
  • OP 想要拦截 Logback 内部的执行,而不是 Spring bean。这就是为什么问题标题和我的答案都提到了第三方代码,后者超出了 Spring AOP 的能力。
  • 请注意我的大规模更新以及完整的 MCVE + Eclipse 屏幕截图。
【解决方案2】:

查看其他答案Intercepting third party functions in android using AspectJ

很遗憾,您只能编写自己的代码。

但是,根据您实际需要对 Logback 执行的操作,您可以创建一个自定义 Appender 并在其中执行您需要执行的操作?

更新

看到你想记录这些异常的某种形式的指标,你可以根据 logstash-logback 文档配置一个 StatusListener

https://github.com/logstash/logstash-logback-encoder#status-listeners

有了这个,您可以接收事件,并记录您需要的任何指标

【讨论】:

  • 感谢您的回答。我有一个带有 ringBuffer 的 LogstashTcpSocketAppender。在某些方面,我们的 logstash 关闭了,所以 ringBuffer 有时会溢出。我浏览了 logback 代码,找到了一种方法,其中 logback 检查环形缓冲区的大小并启动​​丢弃事件。我想以某种方式捕捉那个时刻,当 ringBuffer 溢出并制作一些指标时,有多少和女巫事件下降了。
猜你喜欢
  • 1970-01-01
  • 2013-04-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-03-23
  • 1970-01-01
  • 2018-01-12
  • 2012-08-20
相关资源
最近更新 更多