JustLog
JustLog将iOS上的日志记录提升至新的水平。它无需额外努力即可通过TCP套接字支持控制台、文件和远程Logstash日志记录。支持logz.io。
概述
在Just Eat,日志记录和监控是我们工程师工作的基本部分。无论是后端工程师还是前端工程师,你都可能发现自己处在理解软件在生产环境中行为的重要性场合,如果不是关键场合。近几年来,用于实时日志记录的ELK堆栈获得了广泛的采用,主要是在多个微服务经常相互作用的后端世界。
在移动世界,调查问题的常见方法是从设备收集日志或通过遵循报告的操作序列来尝试重新复制问题。移动开发者大多熟悉像Google Analytics或Fabric.io这样的工具,但它们是跟踪系统,而不是完整的日志记录解决方案。
我们认为跟踪的本质与日志记录不同,并且移动应用程序应该利用ELK来将监控和分析提升到另一个水平。远程记录正确的一组信息可以提供难以收集的其他有价值的情報,揭示意外的行为和错误,即使在数据被适当匿名化之后,也可以识别单个用户的操作序列。
JustLog 将iOS的日志记录提升到新的高度。它支持通过TCP套接字直接进行控制台、文件和远程Logstash日志记录。您还可以轻松设置JustLog以使用logz.io。JustLog依赖于SwiftyBeaver,提供了一个简单的Swifty API,同时也可以很好地与Objective-C配合使用。
JustLog专注于远程日志记录,但同时也完全满足本地控制台和文件日志的基本需求。
安装
CocoaPods
将以下内容添加到您的Podfile中
pod "JustLog"
您可以在cocoapods.org上找到最新版本
Swift包管理器
复制该存储库的URL,并在您的项目设置中添加该包。
使用方法
像这样将日志系统导入到文件
// swift
import JustLog
// Objective-C
@import JustLog;
这个日志系统强烈依赖于SwiftyBeaver。我们决定采用SwiftyBeaver,原因如下
- 优秀且可扩展的设计
- 能够上传日志到云端
- macOS应用程序用于分析日志
日志可以分为5种不同类型,具体使用根据特定需求。在移动设备上采用的一种合理惯例如下
📣 verbose:用于跟踪代码,试图找到函数的一部分,类似于具有大量信息的调试。📝 debug:有助于开发人员诊断问题的信息。ℹ️ info:通常是有用的日志信息(服务启动/停止、配置假设等)。始终可用的信息,但在通常情况下并不关心。默认配置级别。⚠️ warning:任何可能导致应用异常但可以自动恢复的事情(例如重试操作、缺失数据等)☠️ 错误:任何对操作致命但不是服务或应用(无法打开所需文件、缺少数据等)的错误。这些错误将强制用户干预。通常这些错误用于失败的API调用、缺失的服务等。
在使用JustLog时,唯一需要与之交互的对象是Logger
类的共享实例,它支持3个目的地
以下是一个配置和使用Logger的代码示例。应在AppDelegate中的applicationDidFinishLaunchingWithOptions
方法在应用程序启动时执行。
let logger = Logger.shared
// file destination
logger.logFilename = "justeat-demo.log"
// logstash destination
logger.logstashHost = "my.logstash.endpoint.com"
logger.logstashPort = 3515
logger.logstashTimeout = 5
logger.logLogstashSocketActivity = true
// default info
logger.defaultUserInfo = ["app": "my iOS App",
"environment": "production",
"tenant": "UK",
"sessionID": someSessionID]
logger.setup()
《defaultUserInfo
》字典包含一组添加到每个日志的基本信息。
Logger类公开5个函数,用于不同类型的日志。唯一必需的参数是消息,可选的错误和userInfo可以提供。以下是向JustLog发送日志的一些示例
Logger.shared.verbose("not so important")
Logger.shared.debug("something to debug")
Logger.shared.info("a nice information", userInfo: ["some key": "some extra info"])
Logger.shared.warning("oh no, that won’t be good", userInfo: ["some key": "some extra info"])
Logger.shared.error("ouch, an error did occur!", error: someError, userInfo: ["some key": "some extra info"])
它也可以与Objective-C很好地协作
[Logger.shared debug_objc:@"some message"];
[Logger.shared info_objc:@"some message" userInfo:someUserInfo];
[Logger.shared error_objc:@"some message" error:someError];
[Logger.shared error_objc:@"some message" error:someError userInfo:someUserInfo];
请注意,在Objective-C中,如文件名和行号等元数据是不可用的。
对于每种日志类型,消息是唯一必需的参数,而userInfo和error是可选的。Logger将来自message
、error
、error.userInfo
、userInfo
、defaultUserInfo
和调用点信息/元数据的Information统一到一个类型为[String : Any]的字典中,采用以下模式(我们称之为“聚合形式”)。例如,在JSON表示形式中
{
"message": "the log message",
"user_info": {
"app": "my iOS App",
"environment": "production",
"custom_key": "some custom value",
...
},
"errors": [
{
"error_domain" : "com.domain",
"error_code" : "1234",
"NSLocalizedDescription": ...,
"NSLocalizedFailureReasonError": ...,
...
},
{
"errorDomain" : "com.domain.inner",
"errorCode" : "5678",
"NSLocalizedDescription": ...,
"NSLocalizedFailureReasonError": ...,
...
}],
"metadata": {
"file": ...,
"function": ...,
"line": ...,
...
}
}
默认情况下,所有目的地(控制台、文件、logstash)都启用,但在配置时可以禁用,如下所示
logger.enableConsoleLogging = false
logger.enableFileLogging = false
logger.enableLogstashLogging = false
上述5种日志在每个目的地上的处理和显示方式各不相同
控制台
控制台仅打印消息。
文件
在文件上,我们将所有日志信息存储在“聚合形式”中。
2016-12-24 12:31:02.734 📣 VERBOSE: {"metadata":{"file":"ViewController.swift","app_version":"1.0 (1)","version":"10.1","function":"verbose()","device":"x86_64","line":"15"},"user_info":{"environment":"production","app":"my iOS App","log_type":"verbose","tenant":"UK"},"message":"not so important"}
2016-12-24 12:31:36.777 📝 DEBUG: {"metadata":{"file":"ViewController.swift","app_version":"1.0 (1)","version":"10.1","function":"debug()","device":"x86_64","line":"19"},"user_info":{"environment":"production","app":"my iOS App","log_type":"debug","tenant":"UK"},"message":"something to debug"}
2016-12-24 12:31:37.368 ℹ️ INFO: {"metadata":{"file":"ViewController.swift","app_version":"1.0 (1)","version":"10.1","function":"info()","device":"x86_64","line":"23"},"user_info":{"environment":"production","app":"my iOS App","log_type":"info","tenant":"UK","some key":"some extra info"},"message":"a nice information"}
2016-12-24 12:31:37.884 ⚠️ WARNING: {"metadata":{"file":"ViewController.swift","app_version":"1.0 (1)","version":"10.1","function":"warning()","device":"x86_64","line":"27"},"user_info":{"environment":"production","app":"my iOS App","log_type":"warning","tenant":"UK","some key":"some extra info"},"message":"oh no, that won’t be good"}
2016-12-24 12:31:38.475 ☠️ ERROR: {"metadata":{"file":"ViewController.swift","app_version":"1.0 (1)","version":"10.1","function":"error()","device":"x86_64","line":"47"},"user_info":{"environment":"production","log_type":"error","some key":"some extra info","app":"my iOS App","tenant":"UK","NSLocalizedFailureReason":"error value"},"errors":[{"error_code":1234,"error_domain":"com.just-eat.test","NSLocalizedDescription":"description","NSLocalizedRecoverySuggestion":"recovery suggestion"}],"message":"ouch, an error did occur!"}
Logstash
在将日志发送到Logstash之前,“聚合形式”被展平为一个更简单的 `[String : Any]` 字典,易于Logstash理解并在Kibana上显示。例如,在JSON表示形式中
{
"message": "ouch, an error did occur!",
"environment": "production",
"log_type": "error",
"version": "10.1",
"app": "iOS UK app",
"tenant": "UK",
"app_version": "1.0 (1)",
"device": "x86_64",
"file": "ViewController.swift",
"function": "error()",
"line": "47",
"errors": [{
"error_domain": "com.just-eat.test",
"error_code": "1234",
"NSLocalizedDescription": "description",
"NSLocalizedFailureReason": "error value"
}]
}
在Kibana中如下显示
关于Logstash目标的说明
Logstash目标通过Logger公开的属性进行配置。例如:
let logger = Logger.shared
logger.logstashHost = "my.logstash.endpoint.com"
logger.logstashPort = 3515
logger.logstashTimeout = 5
logger.logLogstashSocketActivity = true
当将logLogstashSocketActivity
设置为true时,套接字活动将打印到控制台
这是JustLog附带的所有异步目标中的唯一目标。这意味着日志将分批发送,在未来某个时刻(当计时器触发时)发送。可以设置logstashTimeout
属性为分发所需秒数。在某些情况下,可能需要在事件发生后立即发送日志,如下所示:
Logger.shared.forceSend()
或者更一般地说,在AppDelegate中的applicationDidEnterBackground
和applicationWillTerminate
方法中,如下所示:
func applicationDidEnterBackground(_ application: UIApplication) {
forceSendLogs(application)
}
func applicationWillTerminate(_ application: UIApplication) {
forceSendLogs(application)
}
private func forceSendLogs(_ application: UIApplication) {
var identifier = UIBackgroundTaskIdentifier(rawValue: 0)
identifier = application.beginBackgroundTask(expirationHandler: {
application.endBackgroundTask(identifier)
identifier = UIBackgroundTaskIdentifier.invalid
})
Logger.shared.forceSend { completionHandler in
application.endBackgroundTask(identifier)
identifier = UIBackgroundTaskIdentifier.invalid
}
}
向logz.io发送日志
JustLog支持将日志发送到logz.io。
截至写作时,logz.io使用以下主机和端口(请参阅官方文档)
logger.logstashHost = "listener.logz.io"
logger.logstashPort = 5052
在配置Logger(在调用setup()
之前)时,只需按如下设置令牌
logger.logzioToken = <logzio_token>
自定义
从3.2.0开始,JustLog支持你可以自定义的目标。要实现自定义日志记录器,你需要提供一个符合CustomDestinationSender
协议的类型给新的重载方法setupWithCustomLogSender()
。
调用setupWithCustomLogSender
之前,不要忘了将enableCustomLogging
属性设置为true
。
class MyCustomDestinationSender: CustomDestinationSender {
func log(_ string: String) {
// send the log somewhere
}
}
let customSender = MyCustomDestinationSender()
let logger = Logger.shared
logger.enableCustomLogging = true
logger.setupWithCustomLogSender(customSender)
日志消毒
JustLog支持在客户端设置消毒方法。此方法接受两个占位符变量
- 消息:该变量关注你想消毒的日志消息。
- 日志类型:该变量关注应用于给定日志消息的日志级别。
public var sanitize: (_ message: String, _ minimumLogType: LogType) -> String = { message, minimumLogType in
return message
}
此关闭方法在 Logger.Swift 内设置和调用。如果客户端没有扩展此方法,它将简单地返回原始消息,符合预期。我们如何采用此清理方法的示例可以在 AppDelegate.swift 中找到,其中我们根据输入列表删除某些值。在示例应用中点击“清理日志消息”将提供清理器方法在实际操作中的示例。
结论
JustLog 致力于成为一款易于使用的工作解决方案,配置简便。它通过 SwiftBeaver 提供的坚实基础覆盖了大多数基本的日志记录需求(控制台和文件记录),同时还提供了适用于 Logstash 的高级远程日志记录解决方案(通常与 Elasticsearch 和 Kibana 结合在一个ELK 架构中)。JustLog 集成于 logz.io,这是最广泛使用的 ELK SaaS 之一,使其成为市场上唯一能够利用此类堆栈在 iOS 上工作的解决方案(截至编写时)。
我们希望这个库能够简化您团队进行日志记录设置的过程,并帮助您解决那些您之前不知道的问题。
- Just Eat iOS 团队