Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

日志记录

日志记录 捕获正在运行的程序的信息。

例如,一个安装程序可能会记录:

  • 安装过程中采取的步骤。
  • 用于文件存储的目录。
  • 程序的启动值。

一个 Web 服务器可能会记录每个请求的来源地址和状态。

在调试过程中,日志记录也非常有用。如果没有日志记录,您可能会使用 println() 语句来解析程序的行为。这在没有调试器(比如内置在 IntelliJ IDEA 中的调试器)的情况下可能很有帮助。然而,一旦您确定程序正常工作,您可能会将 println() 语句移除。稍后,如果遇到更多的错误,您可能会再次添加它们。相比之下,日志记录可以在需要时动态启用,否则可以关闭。

对于一些错误,您只能报告问题。某些类型的错误程序可以从中恢复(如 异常处理 中所示),可以记录有关这些错误的详细信息以供后续分析。例如,在 Web 应用程序中,如果出现问题,您不会终止程序。日志记录会捕获这些事件,为程序员和管理员提供一种发现问题的方法。与此同时,应用程序会继续运行。

我们使用一款专为 Kotlin 设计的开源日志记录库称为 Kotlin-logging,它具有 Kotlin 的感觉和简单性。请注意,还有其他可供选择的日志记录库。

您必须在使用日志记录之前创建一个日志记录器。您几乎总是希望在文件范围内创建它,以便在该文件中的所有组件中都可用:

// Logging/BasicLogging.kt
package logging
import mu.KLogging

private val log = KLogging().logger

fun main() {
  val msg = "Hello, Kotlin Logging!"
  log.trace(msg)
  log.debug(msg)
  log.info(msg)
  log.warn(msg)
  log.error(msg)
}

main() 显示了不同的 日志级别trace()debug()info() 捕获行为信息,而 warn()error() 表示问题。

启动配置确定实际上报告的日志级别。这可以在执行过程中进行修改。长时间运行的应用程序的操作者可以在不重新启动程序的情况下更改日志级别(通常是不可接受的)。

日志记录库有着相当奇怪的历史。人们不满意 Java 分发的原始日志记录库,因此他们创建了其他库。为了统一日志记录,设计人员开始开发共同的日志记录接口。鉴于组织可能已经投资于现有的日志记录库,这些接口被创建为多个不同日志记录库的 外观。后来,其他程序员创建了(可能是改进的)覆盖 这些 外观的外观。使用日志记录系统通常意味着选择一个外观,然后选择一个或多个基础实现。

Kotlin-logging 库是 Simple Logging Facade for Java (SLF4J) 的外观,SLF4J 是多个日志框架的抽象。您可以选择满足您需求的框架,尽管更有可能是公司的运营团队会做出这个决策,因为他们通常管理日志记录并分析生成的日志文件。

对于这个示例,我们使用 slf4j-simple 作为我们的实现。这是 SLF4J 的一部分,因此我们不需要安装或配置额外的库,有些库有让人讨厌的设置复杂性。slf4j-simple 将其输出发送到控制台错误流。当您运行程序时,您会看到:

[main] INFO mu.KLogging - Hello, Kotlin Logging!
[main] WARN mu.KLogging - Hello, Kotlin Logging!
[main] ERROR mu.KLogging - Hello, Kotlin Logging!

trace()debug() 不产生输出,因为默认配置不报告这些级别。要获取不同的报告级别,请更改日志记录配置。日志记录配置因您使用的日志记录库而异,因此我们不在这里讨论它。

将日志记录到文件的实现通常通过在文件变得太大时自动丢弃最旧部分来管理这些日志文件。还有其他用于读取和分析日志文件的工具。日志记录的实践可能需要相当深入的研究。

对于基本问题,安装、配置和使用日志记录系统的工作可能会诱使您回到 println() 语句。幸运的是,有更简单的策略。

一个快速而肮脏的方法是定义一个全局函数。在您不需要它时,这很容易禁用:

// Logging/SimpleLoggingStrategy.kt
package logging
import checkinstructions.DataFile

val logFile = // 重置以确保文件为空:
  DataFile("simpleLogFile.txt").reset()

fun debug(msg: String) =
  System.err.println("Debug: $msg")
// 要禁用:
// fun debug(msg: String) = Unit

fun trace(msg: String) =
  logFile.appendText("Trace: $msg\n")

fun main() {
  debug("Simple Logging Strategy")
  trace("Line 1")
  trace("Line 2")
  println(logFile.readText())
}
/* 输出结果:
Debug: Simple Logging Strategy
Trace: Line 1
Trace: Line 2
*/

debug() 将其输出发送到控制台错误流。trace() 将其输出发送到日志文件。

您还可以创建自己的简单日志类:



// Logging/AtomicLog.kt
package atomiclog
import checkinstructions.DataFile

class Logger(fileName: String) {
  val logFile = DataFile(fileName).reset()
  private fun log(type: String, msg: String) =
    logFile.appendText("$type: $msg\n")
  fun trace(msg: String) = log("Trace", msg)
  fun debug(msg: String) = log("Debug", msg)
  fun info(msg: String) = log("Info", msg)
  fun warn(msg: String) = log("Warn", msg)
  fun error(msg: String) = log("Error", msg)
  // 用于基本测试:
  fun report(msg: String) {
    trace(msg)
    debug(msg)
    info(msg)
    warn(msg)
    error(msg)
  }
}

您可以添加对其他功能的支持,比如日志级别和时间戳。

使用这个库很简单:

// Logging/UseAtomicLog.kt
package useatomiclog
import atomiclog.Logger
import atomictest.eq

private val logger = Logger("AtomicLog.txt")

fun main() {
  logger.report("Hello, Atomic Log!")
  logger.logFile.readText() eq """
  Trace: Hello, Atomic Log!
  Debug: Hello, Atomic Log!
  Info: Hello, Atomic Log!
  Warn: Hello, Atomic Log!
  Error: Hello, Atomic Log!
  """
}

创建另一个日志记录库可能并不是一个好的时间利用方式。

  • -

日志记录并不像调用库函数那么简单 - 它有一个显著的运行时组件。通常情况下,日志记录通常包括在可交付的产品中,运维人员必须能够打开和关闭日志记录,动态地调整日志记录级别,并控制日志文件。对于长时间运行的程序(例如服务器),最后一个问题特别重要,因为它包括了防止日志文件填满的策略。

Exercises and solutions can be found at www.AtomicKotlin.com.