混入日志

作者: 刘光聪 | 来源:发表于2016-08-05 07:21 被阅读281次

    本文通过应用中天天都见得到的日志打印谈起,聊聊封装隐藏,性能优化,惰性求值,消除重复的技术实践。

    延迟评估

    Eliminate Effects Between Unrelated Things.

    远古时代

    这是早期日志打印的方式。

    if (logger.isLoggable(Level.INFO)) {
      logger.info("problem:" + getDiagnostic());
    }
    

    这个实现存在如下一些问题:

    • 重复的「样板代码」,并且散乱到程序的各个角落;
    • logger.debug之前,首先要logger.isLoggableLogger暴露了太多的状态逻辑,违反了LoD(Law of Demeter)

    应用LoD

    logger.info("unexpect problem: {}", getDiagnostic());
    

    这样的设计虽然将状态的查询进行了封装,但依然存在一个严重的性能问题。即使日志开关关闭,getDiagnostic都将被调用;如果它是一个耗时、昂贵的操作,将严重地消耗系统性能。

    使用Java8

    灵活地应用Lambda惰性求值的特性,可以很漂亮地解决这个问题。

    public void log(Level level, Supplier<String> supplier) {
      if (isLoggable(level)) {
        log(supplier.get());
      }
    }
    
    public void debug(Supplier<String> supplier) {
      log(Level.DEBUG, supplier);
    }
    
    public void info(Supplier<String> supplier) {
      log(Level.INFO, supplier);
    }
    
    ...
    

    用户的代码也更加简洁,省略了那些重复的样板代码。

    logger.info(() -> "problem:" + getDiagnostic());
    

    使用Scala: call-by-name

    使用Java8 Lambda时,() ->的语法显得有点怪异;如果使用Scala,可以使用by-name机制进一步提高表达力。

    def log(level: Level, msg: => String) {
      if (isLoggable(level)) {
        log(msg)
      }
    }
    
    def debug(msg: => String) {
      log(DEBUG, msg)
    }
    
    def info(msg: => String) {
      log(INFO, msg)
    }
    
    logger.info(s"problem: ${getDiagnostic()}");
    

    s"problem: ${getDiagnostic()}"语句并非在logger.info展开计算,它被延迟直至被apply的时候才真正地被评估和计算。

    复用代码

    DRY: Don't Repeat Youself

    用户空间

    再将目光投放到Java应用,当每次要使用Logger时,都需要搬迁import语句,并定义logger的静态字段,这样的重复结构很容易通过Copy-Paste产生。

    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    public class Application {
      private static Logger logger = LoggerFactory.getLogger(Application.class);
      
      public void run() {
        logger.trace("start run");
      }
    }
    

    消除重复

    使用Scala可以定义Logging的特质,以便消除重复。

    import org.slf4j.{Logger, LoggerFactory}
    
    trait Logging {
      val loggerName = this.getClass.getName
      lazy val logger: Logger = LoggerFactory.getLogger(loggerName)
    
      def trace(msg: => String) {
        if (logger.isTraceEnabled())
          logger.trace(msg)
      }
    
      def info(msg: => String) {
        if (logger.isTraceEnabled())
          logger.trace(msg)
      }
      
      ...
    }
    

    混入特质

    应用程序可以通过混入Logging,自动得到日志打印的各个接口。

    class Main extends App with Logging {
      info("starting...")
    }
    

    相关文章

      网友评论

      • 尉刚强:混入机制还是很好用的呀
      • 8ccb5d4c97a5:很多语言都支持mixin的机制,java只能通过集成做这样的事,但java又是单继承,很多时候可能就只能写重复的代码,现在有一些类似aspectj这样的编译工具,可以通过添加标记的方式来做一些样板式的代码,但这个也有缺陷,就是代码可读性会降低,而且需要额外的插件支持,总的来说比起Scala,确实要笨拙许多
        刘光聪:@取得个什么名字啊 是呀,没有mixin,根本发挥不出OO巨大的威力;Java8的default method也是一个半残品。
      • 乐逍遥5830:用Java是不是实现不了这种呢
        刘光聪:@乐逍遥5830 Java中规中矩,是做不到Scala那么简洁 :smile:

      本文标题:混入日志

      本文链接:https://www.haomeiwen.com/subject/vmdhsttx.html