LogDog



用户友好的日志记录

apple/swift-log API 兼容

用法

LogDog 设计为开箱即用,您可以随时随地使用预配置的 logger

sugar.debug("hi")

sugar.error("somethings went wrong")

或者,制作本地副本并进行一些更改

var logger = suggar
logger["path"] = "/me"

logger.debug("hi")

您只需使用一个标签即可快速创建一个 logger,它将使用预定义的日志处理器

var logger = Logger.sugar("worker:a")
logger.level = .info

logger.info("hi")

SugarLogHandler

使这一切工作的核心组件是 SugarLogHandler

以下代码片段展示了如何创建 SugarLogHandler 并使用它来引导日志系统。

LoggingSystem.bootstrap { label in

    // ! to create your own `SugarLogHandler`, you need a `sink`, an `appender` and an optional `errorHandler`. 
    let sink = LogSinks.Builtin.short
    let appender = TextLogAppender.stdout
    let errorHandler = { error: Error in
        print("LogError: \(error)")
    }

    var handler = SugarLogHandler(label: label, sink: sink, appender: appender, errorHandler: errorHandler)

    // ! use dynamicMetadata to register values that are evaluted on logging.
    handler.dynamicMetadata["currentUserId"] = {
        AuthService.shared.userId
    }   
    
    return handler
}

let logger = Logger(label: "app")
logger.error("Something went wrong")

Sink (接收器)

Sinks (接收器) 处理日志记录。

一个 sink (接收器) 可以是一个格式化器、一个过滤器、一个 hook (钩子) 或其他 sinks (接收器) 的链。

Formatter (格式化器)

您只需使用一个闭包即可创建一个格式化器 sink (接收器)。

let sink = LogSinks.firstly
    .format {
        "\($0.entry.level.uppercased) \($0.entry.message)\n"
    }
    
// Output:
//
//     DEBUG hello

或者直接使用内置的简洁格式化器。

let short = LogSinks.BuiltIn.short

// Output:
//
//     E: bad response
//     C: can not connect to db


let medium = LogSinks.BuiltIn.medium  // default sink for sugar loggers

// Output:
//
//     20:40:56.850 E/App main.swift.39: bad response url=/me, status_code=404
//     20:40:56.850 C/App main.swift.41: can not connect to db


let long = LogSinks.BuiltIn.long

// Output:
//
//     ╔════════════════════════════════════════════════════════════════════════════════
//     ║ 2020-11-15 20:46:31.157  App  ERROR     (main.swift:39  run(_:))
//     ╟────────────────────────────────────────────────────────────────────────────────
//     ║ bad response
//     ╟────────────────────────────────────────────────────────────────────────────────
//     ║ url=/me
//     ║ status_code=404
//     ╚════════════════════════════════════════════════════════════════════════════════

Filter (过滤器)

您只需使用一个闭包即可创建一个过滤器 sink (接收器)。

let sink = LogSinks.firstly
    .filter {
        $0.entry.source != "LogDog"
    }
    
// logs from `LogDog` will not be output.

DSL

或者使用内置的富有表现力的 dsl 来创建一个。

let sink = LogSinks.BuiltIn.short
    .when(.path)
    .contains("private")
    .deny
    
let sink = LogSinks.BuiltIn.short
    .when(.level)
    .greaterThanOrEqualTo(.error)
    .allow

Concat (连接)

Sinks (接收器) 是可链式的,一个 sink (接收器) 可以连接另一个 sink (接收器)。

let sink = sinkA + sinkB + sinkC // + sinkD + ...

// or
let sink = sinkA
    .concat(sinkB)
    .concat(sinkC)
    // .concat(sinkD) ...

LogDog 附带了许多常用的操作符。

Prefix & Suffix (前缀 & 后缀)

let sink = LogSinks.BuiltIn.short
    .prefix("🎈 ")
    
// Output:
//
//     🎈 E: bad response


let sink = LogSinks.BuiltIn.short
    .suffix(" 🎈")
    
// Output:
//
//     E: bad response 🎈

Encode (编码)

let sink = LogSinks.firstly
    .encode(JSONEncoder())

Crypto (加密)

let sink = LogSinks.firstly
    .encode(JSONEncoder())
    .encrypt(using: key, cipher: .ChaChaPoly)

Compress (压缩)

let sink = LogSinks.firstly
    .encode(JSONEncoder())
    .compress(.COMPRESSION_LZFSE)

Schedule (调度)

Sinks (接收器) 的处理可能很耗时,如果您不想让它减慢您的工作速度,您可以使用 Scheduler 使日志记录异步。

let sink = LogSinks.firstly
    .sink(on: dispatchQueue) // or an operationQueue, or some other custom schedulers, for example, an eventLoop.
    .encode(JSONEncoder()) // time-consuming processing begins.
    .encrypt(using: key, cipher: .ChaChaPoly)
    .compress(.COMPRESSION_LZFSE)

Hook (钩子)

由于 Scheduler,日志记录可能是异步的。

这意味着 sinking (接收) 可能在不同的上下文中,

您可以使用带有 entry.parametershook (钩子) 来捕获和传递上下文。

private struct CustomSinkContext: LogParameterKey {
    typealias Value = CustomSinkContext

    let date = Date()
    let thread = LogHelper.thread
}

let customSink: AnyLogSink<Void, String> = AnyLogSink {

    // ! beforeSink: in the same context as the log generation.
    
    $0.parameters[CustomSinkContext.self] = .init()
} sink: { record, next in

    // ! sink: may not be in the same context as the log generation.

    record.sink(next: next) { (record) -> String? in
        guard let context = record.entry.parameters[CustomSinkContext.self] else {
            return nil
        }

        let time = LogHelper.format(context.date, using: "HH:mm:ss.SSS")
        let thread = context.thread
        
        // ...
    }
}

请注意,当使用 encode (编码) 时,以字符串为键的参数也将被编码。

LogSinks.firstly
  .hook { 
     $0.parameters["currentUserId"] = AuthService.shared.userId
  }
  .hook(.appName, .appVersion, .date) /* , ..., a lot of built-in hooks */
  .encode(JSONEncoder())
  
/*
{
  "label": "app",
  "level": "debug",
  "metadata": {
    // ...
  }
  // ...
  "currentUserId": "1",
  "appName": "LogDog",
  "appVersion": "0.0.1",
  "date": "2020-11-19T01:12:37.001Z"
}
 */

Appender (追加器)

Appenders (追加器) 是日志记录的目标地。

内置 Appenders (追加器)

OSLogAppender

将字符串追加到底层的 OSLog

let appender = OSLogAppender(osLog)

TextLogAppender

将字符串追加到底层的 TextOutputStream

let appender = TextLogAppender(stream)

let stdout = TextLogAppender.stdout
let stderr = TextLogAppender.stderr

MultiplexLogAppender

将输出追加到底层的 appenders (追加器)。

// when `concurrent` is `true`, a dispatch group is used to make the appending of all appenders parallel. 
let appender = MultiplexLogAppender(concurrent: true, a, b, c, d/*, ...*/)

FileLogAppender

WIP (正在进行中)

Community (社区)

除了上面提到的 sinks (接收器)/appenders (追加器) 之外,您还可以在 LogDogCommunity 找到更多集成!

LogDogChalk

为 LogDog 的输出着色。

LogDogCryptoSwift

加密 LogDog 的输出。

LogDogCocoaLumberjack

将 LogDog 的输出追加到 CocoaLumberjack。

没有您想要的 sink (接收器)/appender (追加器)?欢迎贡献!

Installation (安装)

Swift Package Manager

.package(url: "https://github.com/luoxiu/LogDog.git", from: "0.2.0"),

CocoaPods

pod 'LogDog', '~> 0.2.0'