XLFacility 1.5.18

XLFacility 1.5.18

测试已测试
Lang语言 Obj-CObjective C
许可证 NOASSERTION
发布上次发布2019 年 3 月

Pierre-Olivier Latour 维护。



XLFacility 1.5.18

  • Pierre-Olivier Latour

概览

Build Status Version Platform License

XLFacility(代表“广泛的日志功能”),是针对 OS X 和 iOS 的优雅和强大的日志功能。它是基于以下目标从头开始编写的

  • 替换 NSLog(),包括简单的宏来在各种位置记录信息,不会影响性能
  • 支持广泛的日志位置,即“记录器”
  • 可定制日志格式
  • 采用最新 Obj-C 运行时和 Grand Central Dispatch 的现代、干净和紧凑的代码库
  • 易于理解的架构,只需几行代码就能编写自定义记录器
  • 不依赖于第三方源代码
  • 在友好的 新 BSD 许可证 下提供

内置记录器

  • 标准输出和标准错误
  • Apple System Logger
  • 本地文件
  • 本地 SQLite 数据库
  • telnet 服务器,可以访问另一台电脑上的终端来监控到达的日志信息
  • HTTP 服务器,可以访问另一台电脑上的 Web 浏览器来浏览过去的日志信息并查看实时更新
  • 原始 TCP 连接,可以将日志信息实时发送到远程服务器
  • 用于 OS X 和 iOS 应用程序的用户界面日志窗口覆盖层

要求

  • OS X 10.8 或更高版本(x86_64)
  • iOS 8.0 或更高版本(armv7, armv7s 或 arm64)
  • 仅支持 ARC 内存管理

入门

下载或检出 XLFacility 的最新版本,然后将“XLFacility”、“GCDTelnetServer/GCDTelnetServer”和“GCDTelnetServer/GCDNetworking/GCDNetworking”子文件夹添加到您的 Xcode 项目中。

或者,您可以使用 CocoaPods(通过在 Xcode 项目的 Podfile 中添加以下行来安装 XLFacility)

pod "XLFacility", "~> 1.0"

这提供了所有记录器,包括服务器中的记录器,但如果您只想用 XLFacility 的“核心”和基本记录器,请使用以下命令

pod "XLFacility/Core", "~> 1.0"

一次性 NSLog 替换

在您 Xcode 项目的预编译头文件中,插入以下内容

#ifdef __OBJC__
#import "XLFacilityMacros.h"
#define NSLog(...) XLOG_INFO(__VA_ARGS__)
#endif

从这一点开始,您应用程序源代码中任何调用 NSLog() 来记录信息的请求将被替换为向 XLFacility 的请求。请注意,这不会影响您应用程序中由 Apple 框架或第三方库完成的 NSLog() 调用(有关潜在解决方案,请参阅本文件中的“捕获 Stderr 和 Stdout”)。

测试您的(修改后的)应用程序

到目前为止,表面上的变化并不大,只是在从 Xcode 运行应用程序时,使用 NSLog() 记录的信息现在显示在控制台上,如下所示

00:00:00.248 [INFO     ]> Hello World!

而之前它们看起来是这样的

2014-10-12 02:41:29.842 TestApp[37006:2455985] Hello World!

这是 XLFacility 和 NSLog() 之间的第一个主要区别:您可以根据自己的喜好自定义输出。尝试将 #import "XLStandardLogger.h" 添加到应用程序的 main.m 文件的顶部,然后插入以下行,在调用 UIApplicationNSApplication 之前,在 main()

[[XLStandardLogger sharedErrorLogger] setFormat:XLLoggerFormatString_NSLog];

再次运行您的应用程序,并注意控制台中的信息现在看起来与使用 NSLog() 时完全一样。

让我们使用一个自定义紧凑格式

[[XLStandardLogger sharedErrorLogger] setFormat:@"[%l | %q] %m"];

再次运行您的应用程序,现在 Xcode 控制台中的消息应该看起来是这样的

[INFO | com.apple.main-thread] Hello World!

有关支持的所有格式说明符的完整列表,请参阅 XLLogger.h

使用 XLFacility 记录消息

与大多数记录系统一样,XLFacility 定义了各种日志级别,按重要性顺序排列:DEBUGVERBOSEINFOWARNINGERROREXCEPTIONABORT。其基本思想是,在记录消息时,您还提供相应的级别:例如,使用 VERBOSE 来跟踪和帮助调试代码中的行为,而使用 WARNING及以上来报告实际问题。记录系统可以配置为“丢弃”低于一定级别的消息,使用户能够控制“信号与噪声”比。

默认情况下,当以“发布”配置构建应用程序时,XLFacility 忽略 DEBUGVERBOSE 级别的消息。当以“调试”配置构建(需要将 DEBUG 预处理器常量评估为非零)时,它保留所有内容。

重要信息:到目前为止,你已经看到了如何通过“覆盖”你的源代码中的NSLog()调用并将消息重定向到XLFacility的INFO级别,但这并不是最佳做法。相反,根本不要使用NSLog(),而是直接调用XLFacility函数来记录消息。

您可以通过调用共享的XLFacility实例上的记录方法或使用XLFacilityMacros.h中的宏在XLFacility中记录消息。后者强烈推荐,因为宏可以产生完全相同的记录结果,但看起来更简洁,打字更快,最重要的是,它们只在必要时才评估它们的参数。

以下是一些可用的宏,可以用来在各个级别记录消息

  • XLOG_DEBUG(...):如果构建“发布”(即,如果DEBUG预处理器常量计算为零),则成为空操作
  • XLOG_VERBOSE(...)
  • XLOG_INFO(...)
  • XLOG_WARNING(...)
  • XLOG_ERROR(...)
  • XLOG_EXCEPTION(__EXCEPTION__):接收一个NSException而不是一个格式字符串(消息将自动从异常生成)
  • XLOG_ABORT(...):在记录消息后立即调用abort()来终止应用程序

当调用宏时,除了XLOG_EXCEPTION()之外,请像在NSLog()+[NSString stringWithFormat:...]等中一样使用Obj-C的标准格式说明符。例如

XLOG_WARNING(@"Unable to load URL \"%@\": %@", myURL, myError);

其他有用的宏,可用于您的源代码

  • XLOG_CHECK(__CONDITION__):检查一个条件,如果为假则调用包含自动生成消息的XLOG_ABORT()
  • XLOG_UNREACHABLE():如果应用程序达到这一点,则调用包含自动生成消息的XLOG_ABORT()
  • XLOG_DEBUG_CHECK(__CONDITION__):与XLOG_CHECK()相同,但在“发布”配置下成为空操作(即,如果DEBUG预处理器常量计算为零)
  • XLOG_DEBUG_UNREACHABLE():与XLOG_UNREACHABLE()相同,但在“发布”配置下成为空操作(即,如果DEBUG预处理器常量计算为零)

以下是一些示例用法

- (void)processString:(NSString*)string {
  XLOG_CHECK(string);  // Passing a nil string is a serious programming error and we can't continue
  // Do something
}

- (void)checkString:(NSString*)string {
  if ([string hasPrefix:@"foo"]) {
    // Do something
  } else if ([string hasPrefix:@"bar"]) {
    // Do something
  } else {
    XLOG_DEBUG_UNREACHABLE();  // This should never happen
  }
}

与XLFacility记录的消息可以关联一个可选的标签,这是一个任意字符串。这是一个强大的功能,可以让你例如捕获源文件名和行号作为日志消息的一部分。有关更多信息,请参阅XLFacilityMacros.h

与远程记录一起使用

回到您的应用程序的main.m文件,在顶部添加#import "XLTelnetServerLogger.h",并在调用UIApplicationNSApplication之前插入此行

[XLSharedFacility addLogger:[[XLTelnetServerLogger alloc] init]];

我们在做的是向XLFacility添加一个辅助的“记录器”,以便将日志消息同时发送到两个目的地。

在您的计算机上本地运行您的应用程序(对于iOS应用程序请使用iOS模拟器),然后在终端应用程序中输入此命令

telnet localhost 2323

您应该在屏幕上看到以下输出

$ telnet localhost 2323
Trying ::1...
telnet: connect to address ::1: Connection refused
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
You are connected to TestApp[37006] (in color!)

现在,您应用程序源代码中对NSLog()的任何调用都将实时发送到您的终端窗口。并且当您连接到应用程序时,为了方便起见,确保您没有错过任何内容,XLTelnetServerLogger将立即回放自应用程序启动以来记录的所有消息(此行为可以更改)。

真正有趣且有用的是在您的应用在另一台Mac或真实的iPhone / iPad上运行时连接到您的应用。只要您的家庭/办公室/WiFi网络不阻止端口2323XLTelnetServerLogger的默认端口)上的通信,您就可以通过在您的电脑的终端中简单输入telnet YOUR_DEVICE_IP_ADDRESS 2323来远程连接。

当然,与您上面用XLStandardLogger操作的方式一样,您可以自定义XLTelnetServerLogger使用的格式,例如如下所示

XLLogger* logger = [[XLTelnetServerLogger alloc] init];
logger.format = @"[%l | %q] %m";
[XLSharedFacility addLogger:logger];

您甚至可以将多个XLTelnetServerLogger实例添加到XLFacility,每个实例监听不同的端口并配置不同。

重要:不推荐在App Store中默认启用XLTelnetServerLogger来发布您的应用,因为这可能会对您的用户造成安全和/或隐私问题。由于您可以在应用生命周期的任何阶段添加和删除记录器,您可以通过为用户提供用户界面设置来动态向XLFacility添加或删除XLTelnetServerLogger

从您的网页浏览器进行日志监控

像您上面为添加对XLTelnetServerLogger的支持所做的那样进行修改,但使用XLHTTPServerLogger代替。当您的应用运行时,在您的网页浏览器中转到http://127.0.0.1:8080/http://YOUR_DEVICE_IP_ADDRESS:8080/。您应该能看到自从应用启动以来,XLFacility的所有日志消息。当有新的日志消息可用时,网页将自动刷新。

重要:XLTelnetServerLogger的原因相同,也不推荐在App Store中将带有默认启用的XLHTTPServerLogger的应用发布。

屏幕日志覆盖

在OS X和iOS应用中,您可以轻松地配置一个在向XLFacility发送日志消息时出现的日志覆盖窗口。只需像以下这样利用XLUIKitOverlayLoggerXLAppKitOverlayLogger

iOS版本

#import "XLUIKitOverlayLogger.h"

@implementation MyAppDelegate

- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
  [XLSharedFacility addLogger:[XLUIKitOverlayLogger sharedLogger]];
  
  // Rest of your app initialization code goes here
}

@end

OS X版本

#import "XLAppKitOverlayLogger.h"

@implementation MyAppDelegate

- (void)applicationDidFinishLaunching:(NSNotification*)notification {
  [XLSharedFacility addLogger:[XLAppKitOverlayLogger sharedLogger]];
  
  // Rest of your app initialization code goes here
}

@end

归档日志消息

有一些方法可以保存XLFacility发送的日志消息以供应用重启后持久保存

最简单的解决方案是使用XLFileLogger将日志消息保存到纯文本文件,例如

XLFileLogger* fileLogger = [[XLFileLogger alloc] initWithFilePath:@"my-file.log" append:YES];
fileLogger.minLogLevel = kXLLogLevel_Error;
fileLogger.format = @"%d\t%m";
[XLSharedFacility addLogger:fileLogger];

更强大的解决方案是使用使用内部SQLite数据库的XLDatabaseLogger

XLDatabaseLogger* databaseLogger = [[XLDatabaseLogger alloc] initWithDatabasePath:@"my-database.db" appVersion:0];
[XLSharedFacility addLogger:databaseLogger];

请注意,XLDatabaseLogger 将日志消息原样序列化到数据库中,不会对其进行格式化,即其 format 属性不起作用。

您可以轻松“回放”稍后保存的日志消息,例如在您的应用程序界面的日志窗口中显示它们或将它们发送到服务器。

[databaseLogger enumerateRecordsAfterAbsoluteTime:0.0
                                         backward:NO
                                       maxRecords:0
                                       usingBlock:^(int appVersion, XLLogRecord* record, BOOL* stop) {
  // Do something with each log record
  printf("%s\n", [record.message UTF8String]);
}];

过滤XLFacility日志消息

您可以使用 XLFacility 共享实例上的 minLogLevel 属性来使 XLFacility 忽略低于一定级别的所有日志消息。

您还可以使用每个日志记录器的 minLogLevelmaxLogLevel 属性来控制它们的最小和最大日志级别。您甚至可以为日志记录器设置完全自定义的日志记录记录过滤器,如下所示

myLogger.logRecordFilter = ^BOOL(XLLogger* logger, XLLogRecord* record) {
  return [record.tag hasPrefix:@"com.my-app."];
};

记录异常

尽早(通常在调用 UIApplicationNSApplication 之前)在您的应用程序中调用 [XLSharedFacility setLogsUncaughtExceptions:YES],以使 XLFacility 安装未捕获异常处理器并自动在应用程序终止之前调用 XLOG_EXCEPTION()并在传递异常。

如果您想要记录所有异常,包括它们是创建还是捕获,请使用 [XLSharedFacility setLogsInitializedExceptions:YES] 代替。请注意,这将记录未抛出的异常。

在这两种情况下,XLFacility 都会将当前的调用堆栈作为日志消息的一部分捕获。

捕获Stderr和Stdout

如果您在应用程序中完全使用 XLFacility 函数来记录消息,那么从源代码记录的所有内容都将进入 XLFacility。如果您使用第三方源代码,您可能能够修补或覆盖其 NSLog()printf() 或等效调用的调用,如本文档开头所示。但是,这不会对 Apple 或第三方库或框架起作用。

XLFacility 具有一个强大的功能,可以捕获应用程序的标准输出和标准错误。只需调用 [XLSharedFacility setCapturesStandardError:YES](分别 [XLSharedFacility setCapturesStandardOutput:YES]),并且从这一刻起,写入标准输出(分别标准错误)的所有内容将在新行边界处分割,并且在 XLFacility 中自动成为单独的日志消息,其级别为 INFO(分别 ERROR)。

编写自定义记录器

您可以使用几行代码通过使用XLCallbackLogger来编写自定义记录器,例如:

[XLSharedFacility addLogger:[XLCallbackLogger loggerWithCallback:^(XLCallbackLogger* logger, XLLogRecord* record) {
  // Do something with the log record
  printf("%s\n", [record.message UTF8String]);
}]];

要实现更复杂的记录器,您需要继承XLLogger并实现至少-logRecord:方法。

@interface MyLogger : XLLogger
@end

@implementation MyLogger

- (void)logRecord:(XLLogRecord*)record {
  // Do something with the log record
  NSString* formattedMessage = [self formatRecord:record];
  printf("%s", [formattedMessage UTF8String]);
}

@end

如果您需要在您的记录器实例添加到或从XLFacility中删除时执行特定设置和清理操作,也需要实现-open-close方法。

重要:由于XLFacility的工作方式,记录器实例不需要是可重入的,但它们需要在任意线程上运行。