【问题标题】:Java: Is it possible to automatically add log statements to methods?Java:是否可以自动将日志语句添加到方法中?
【发布时间】:2017-10-14 19:36:06
【问题描述】:

我的应用程序中的大部分方法都是这样写的:

public void m() {
    long t1 = System.currentTimeMillis();
    log.info("begin - m()");

    /* method body */

    long t2 = System.currentTimeMillis();
    log.info("end - m(), took " + (t2 - t1) + "ms.");
}

我希望我可以简单地注释我的方法并让日志语句自动生成:

@Log("executionTime")
public void m() {
    /* method body */
}

关于如何继续这种方法的任何想法?有什么已知的解决方案吗?

有人为此建议AOP。问题是,对于 AspectJ 或 Spring AOP,我必须描述所有方法,这些方法的代码量与方法本身中的日志调用一样多。

【问题讨论】:

    标签: java logging aop


    【解决方案1】:

    AspectJSpring AOP 支持类似:

    execution(* com.company.project..*.*(..))
    

    这将涵盖project 的所有子包中的所有方法。所以不需要一一定义所有的方法。

    【讨论】:

      【解决方案2】:

      正如已向您建议的那样,AOP 非常适合满足此要求。不确定“必须描述所有方法”是什么意思。据我所知,有一些方法可以使用通配符来指定适用于哪些方面的方法,这可以简化您的“描述”工作……至少在 Spring AOP 的情况下是这样……不确定其他人。

      是的,Maurice 建议的 CGLIB 是您考虑的另一个不错的候选者。不过没用过。

      【讨论】:

        【解决方案3】:

        CGLIB让你在运行时修改方法代码

        【讨论】:

          【解决方案4】:

          AspectJ 有连接点的概念,它就像一个通配符,可以指定任何匹配该通配符的方法(您可以指定一个类中的特定方法或任何匹配该通配符的类) .然后你可以创建一个aspect,其中包含before adviceafter advice,它们是在连接点匹配的方法之前和之后运行的方法.您可以通过这种方式生成日志方法。

          【讨论】:

            【解决方案5】:

            Perf4j 支持使用注解获取方法的时间信息。请参阅他们的开发者指南中的 here

            【讨论】:

              【解决方案6】:

              虽然这还不是对您的问题的实际实际答案(一些很好的答案是关于 AOP),但我相信 Java 7 中的 ARM 概念应该是在小规模。

              您可以定义一个实用程序日志记录类和一个生成该类的工厂,如下所示:

              public class TimerFactory
              {
                  private static final Logger log = ...; // Obtain however
              
                  static class Timer implements Disposable<RuntimeException>
                  {
                      private final long startTime;
                      private final String name;
              
                      private Timer(String name)
                      {
                         this.name = name;
                         startTime= System.currentTimeMillis();
                         log.info("begin - " + name);
                      }
              
                      public void close()
                      {
                         final long t2 = System.currentTimeMillis();
                         log.info("end - " + name + ", took " + (t2 - t1) + "ms.");
                      }
                  }
              
                  public static Timer getTimer(String name)
                  {
                     return new Timer(name);
                  }
              }
              

              现在有了那个样板文件(本质上是你的日志记录行为的封装),它可以被如下调用:

              public void m() {
                 try (TimerFactory.getTimer("m()")) {
              
                    /* method body */
              
                 }
              }
              

              第一个 log 方法会在 try 块的入口处被调用,并记录开始时间。当 try 块退出时,资源(在本例中为 Timer)将自动“关闭”,这将导致计算和记录最终时间。还要注意,因为这是一个try 块,所以无论是否抛出异常,都会发生结束日志记录。您的原始代码可能应该使用 try-finally 块来确保实际完成日志记录。

              显然,这仍然需要您在每个站点放置一些日志代码,因此即使在 Java 7 发布后,也不能真正替代聪明的切入点和 AOP。但是,如果您发现自己时不时地将日志记录放到少数几个方法中,那么这种模式是一种很好的方式来抽象出日志记录问题并允许您以最少的样板代码重用它。

              【讨论】:

              • 一次性使用非常好!很棒的一个。 try(...>>) 中的小语法错误
              • 感谢您的拼写错误提示(现已修复)-您可以看出我还不习惯编写 ARM 块!
              【解决方案7】:

              您应该使用这个要求的一个方面。这个要求是一个横切关注点(一个在许多类之间“切分”的关注点)。

              要捕获您想要匹配的方法,您应该创建一个匹配一个或多个连接点的切入点。连接点是可以在您的代码上执行的东西(例如方法)。

              查看this simple examples 了解跟踪和日志记录以及查看this link about wildcards and pointcuts

              【讨论】:

                【解决方案8】:

                尝试 jcabi-aspects 中的 @Loggable 注释(由 AspectJ 提供支持):

                @Loggable(Loggable.DEBUG)
                public String load(URL url) {
                  return url.openConnection().getContent();
                }
                

                它通过SLF4J 进行记录,您可以将其重定向到您自己的记录工具,例如 log4j。

                【讨论】:

                  【解决方案9】:

                  使用唯一的搜索词注释掉日志记录或分析调用:

                  void myfunc() {
                  
                    //log-call-123: try { long log_t1 = System.currentTimeMillis();        
                    //log-call-123: log.info("begin - myfunc()"); {
                  
                    ...
                    normal method code
                    ...
                  
                    //log-call-123: } } finally { long log_t2 = System.currentTimeMillis();         
                    //log-call-123: log.info("end - myfunc(), took " + (log_t2 - log_t1) + "ms."); }
                  
                  }
                  

                  当你搜索和替换时:

                  搜索:“//log-call-123:

                  替换为:“/* log-call-123 */

                  当您想要关闭额外的日志记录或分析调用时,执行反向搜索和替换。

                  【讨论】:

                  • 这不会在提交中产生很多噪音吗...?
                  • 如果你想走这条路,最好有一个全局常量布尔值,作为穷人的 IFDEF 来决定是否将日志调用编译到代码中。但是,这两个选项都没有解决首先识别登录方法的实际问题,而无需手动键入所有日志语句。
                  • 我承认这不是问题的解决方案,它与源代码控制系统的关系很差,但它是值得深思的。也许将项目文件复制到“调试”文件夹中的宏,处理源文件以启用日志记录并将它们设为只读,然后使用调试器启动应用程序会很好。
                  猜你喜欢
                  • 2021-08-21
                  • 2021-11-14
                  • 2022-11-09
                  • 2021-12-06
                  • 2012-08-05
                  • 1970-01-01
                  • 2021-08-21
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多