Skip to content

日志记录

内置现代化、高性能的日志记录框架,提供了统一的日志记录接口,支持多种输出目标、灵活的过滤机制和丰富的日志级别控制。

快速启动

只需要以下 3 个步骤即可开始使用日志系统:

cangjie
import spire_extensions_logging.*

main() {
    // 1. 创建并配置日志系统
    let logging = LoggingBuilder()
        .addConsole()                    // 添加控制台输出
        .setMinimumLevel(LogLevel.Info)  // 设置最低日志级别
        .build()

    // 2. 创建日志器
    let logger = logging.createLogger("spire.hosting.lifetime")

    // 3. 记录日志
    logger.debug("This is a debug.") // 不打印,因为被过滤了
    logger.info("This is a info.")
    logger.warn("This is a warning.")
    logger.error("This is an Error!")
}

日志级别

日志级别(LogLevel)定义了日志信息的重要性和详细程度:

cangjie
public enum LogLevel <: Comparable<LogLevel> & ToString {
    Trace | Debug | Info | Warn | Error | Fatal | Off
}
级别优先级使用场景典型内容
Trace0 (最低)非常详细的调试方法进入/退出、变量值
Debug1开发调试业务逻辑流程、中间结果
Info2一般信息业务操作完成、状态变更
Warn3警告信息潜在问题、性能问题
Error4错误信息处理失败、异常情况
Fatal5致命错误系统崩溃、无法恢复
Off6 (最高)关闭日志不输出任何日志

日志过滤

日志过滤允许精确控制哪些日志应该被记录:

设置最低级别

cangjie
let logging = LoggingBuilder()
    .addConsole()
    .setMinimumLevel(LogLevel.Warn)  // 全局最低级别为警告
    .build()
let logger = logging.createLogger("spire.hosting.lifetime")  
logger.info("This is a info.") // 不打印
logger.warn("This is a warning.") // 打印
logger.error("This is an Error!") // 打印

使用简单过滤器

cangjie
let logging = LoggingBuilder()
    // 该日志提供程序名为:console
    .addConsole() 
    // 设置日志提供程序名为console的,日志名称spire.hosting.*开头的日志最低级别为:Warn
    .addFilter("console", "spire.hosting.*", LogLevel.Warn).build()
let logger1 = logging.createLogger("spire.hosting.lifetime")
logger1.info("This is a warning.") // 不打印
logger1.error("This is an Error!") // 打印 
println('=================================')
let logger2 = logging.createLogger("spire.web.lifetime")
logger2.info("This is a warning.") // 打印,因为日志名称不匹配
logger2.error("This is an Error!") // 打印

filter的优先级要高于setMinimumLevel

使用lambda过滤器

cangjie
let logging = LoggingBuilder()
    .addConsole() 
    .addFilter{_, categoryName, logLevel =>
        // 名称为"spire.hosting."并且日志级别大于Warn,才打印
        categoryName.startsWith("spire.hosting.") && logLevel > LogLevel.Warn
    }.build()
let logger1 = logging.createLogger("spire.hosting.lifetime")
logger1.info("This is a warning.") // 不打印
logger1.error("This is an Error!") // 打印 
println('=================================')
let logger2 = logging.createLogger("spire.web.lifetime")
logger2.info("This is a warning.") // 不打印
logger2.error("This is an Error!") // 不打印

使用配置文件配置

我们支持通过 JSON 配置文件来配置日志过滤器,并与 extensions_configuration 模块进行了集成。

配置规则如下:

bash
{providerName}:{categoryName}:{logLevel}

参数说明:

  1. providerName(日志提供程序名称)

    • 如果是根节点下的 logLevel,表示不区分日志提供程序。
  2. categoryName(日志名称)

    • 如果是 default,表示不区分日志名称。
  3. logLevel(日志级别)

    • 支持前置匹配和后置 * 通配符。

最后,系统会使用解析后的参数调用前面提到的 addFilter(providerName, categoryName, logLevel) 函数。

cangjie
import spire_extensions_logging.*
import spire_extensions_configuration.*
import spire_extensions_logging_configuration.*

main() {
    let configuration = ConfigurationManager()
        .addJsonString(#"
            {
                "logging": {
                    "logLevel": {
                        "default": "Info",
                        "spire.hosting.*": "Warn"
                    },
                    "file": {
                        "logLevel": {
                            "default": "Error"
                        }
                    }
                }
            }
        "#)
        .build()
    let section = configuration.getSection("logging")
    
    let logging = LoggingBuilder()
        .addConsole() 
        .addConfiguration(section)
        .build()

    let logger1 = logging.createLogger("spire.hosting.lifetime")
    logger1.info("This is an Info!")
    logger1.error("This is an Error!")

    let logger2 = logging.createLogger("spire.web.lifetime")
    logger2.info("This is an Info!")
    logger2.error("This is an Error!")
}

自定义提供程序

定义文件日志

cangjie
// 定义文件日志器
class FileLogger <: ILogger {
    FileLogger(let categoryName: String) {

    }
    public func log(logLevel: LogLevel, message: String, exception: ?Exception): Unit {
        let path = getFileName()
        try (sw = StringWriter(getFileStream(path))){
            sw.write("${DateTime.now()}|${logLevel}|${categoryName}: ")
            if (let Some(exception) <- exception) {
                sw.writeln(exception.toString())
            } else {
                sw.writeln(message)
            }
        }
    }

    private func getFileName() {
        let name = DateTime.now().format("yyyyMMdd")
        if (!exists("logs")) {
            Directory.create("logs", recursive: true)
        }
        return Path("./logs/${name}.log")
    }

    private func getFileStream(path: Path) {
        if (!exists(path)) {
            return File(path, OpenMode.Write)
        }
        return File(path, OpenMode.Append)
    }
}

// 定义文件日志提供程序
class FileLoggerProvider <: ILoggerProvider {
    public prop name: String {
        get() {
            "file"
        }
    }

    public func createLogger(categoryName: String): ILogger {
        return FileLogger(categoryName)
    }
}

// 扩展到LoggingBuilder
extend LoggingBuilder{
    public func addFile() {
        addProvider(FileLoggerProvider())
        return this
    }
}

测试打印效果

cangjie
let logging = LoggingBuilder()
    .addConsole() 
    .addFile()
    .build()
let logger = logging.createLogger("spire.hosting.lifetime")
logger.error("This is an Error!")

此时我们可以看到控制台打印了日志,同时在当前项目路径下创建了一个logs文件夹,并且里面有一个文件,也记录了这个日志。