如何使用Objective-C的os_log API获取iOS设备日志
Objective-C 基于OSLog API实现iOS日志采集方案
iOS 10开始苹果废弃了原有的ASL日志接口,改用OSLog作为系统日志框架,以下是使用OSLog采集日志用于远程上报的实现方案:
权限与版本说明
- 仅iOS 10及以上系统支持OSLog相关能力
- 普通沙箱应用默认仅可采集当前应用自身输出的OSLog日志,若需采集全系统日志,需要特殊系统授权(仅支持MDM管控应用、企业内部定制系统应用,普通App Store上架应用无法获取该权限)
- iOS 15及以上系统提供了公开的日志读取接口,iOS 14及以下版本需使用私有API实现日志读取,私有API存在App Store审核被拒风险,仅适合企业内部分发场景使用
iOS 15+ 公开API实现方案
苹果在iOS 15推出了OSLogStore公开类用于读取系统存储的OSLog日志,Objective-C调用示例如下:
#import <Foundation/Foundation.h> #import <OSLog/OSLogStore.h> #import <OSLog/OSLogEntry.h> /// 采集最近1小时内的OSLog日志 /// - Parameter completion: 采集完成回调,返回格式化后的日志数组或错误信息 - (void)collectRecentOSLogsWithCompletion:(void(^)(NSArray<NSString *> * _Nullable logs, NSError * _Nullable error))completion { NSError *initError = nil; // 初始化日志存储实例,OSLogStoreScopeSystem表示读取系统维度日志,普通应用默认仅能拿到自身进程日志 OSLogStore *logStore = [[OSLogStore alloc] initWithScope:OSLogStoreScopeSystem error:&initError]; if (!logStore) { completion(nil, initError); return; } // 配置查询条件:仅查询最近1小时的日志 NSTimeInterval timeRange = 60 * 60; NSDate *startTime = [NSDate dateWithTimeIntervalSinceNow:-timeRange]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"date >= %@", startTime]; // 若仅需采集当前应用日志,可新增条件:@"process == %@", [[NSProcessInfo processInfo] processName] NSError *queryError = nil; NSMutableArray<NSString *> *logResult = [NSMutableArray array]; // 遍历匹配的日志条目 [logStore enumerateEntriesWithPredicate:predicate options:0 error:&queryError usingBlock:^(OSLogEntry * _Nonnull entry, BOOL * _Nonnull stop) { // 按需拼接日志字段,可扩展进程ID、子系统、类别、日志级别等信息 NSString *formattedLog = [NSString stringWithFormat:@"[%@] [%@:%d] %@", entry.date, entry.process, entry.processIdentifier, entry.composedMessage]; [logResult addObject:formattedLog]; }]; if (queryError) { completion(nil, queryError); return; } completion(logResult.copy, nil); }
iOS 10~14 兼容实现方案
iOS 14及以下没有公开的OSLog读取接口,需要通过动态加载私有API实现日志采集,实现示例如下:
#import <Foundation/Foundation.h> #include <dlfcn.h> // 私有接口枚举定义 typedef NS_ENUM(uint32_t, OSLogCopyType) { OSLogCopyTypeInfo = 1 << 4, OSLogCopyTypeError = 1 << 5, OSLogCopyTypeFault = 1 << 6 }; typedef void (^OSLogEnumerateBlock)(NSDictionary *entry, BOOL *stop); // 私有方法指针声明 static NSArray* (*os_log_copy_entries)(NSDate *start, NSDate *end, OSLogCopyType type, NSError **error) = NULL; static void (*os_log_enumerate_entries)(NSArray *entries, OSLogEnumerateBlock block) = NULL; /// 动态加载OSLog私有API + (void)loadPrivateAPI { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ void *libHandle = dlopen("/usr/lib/system/libsystem_trace.dylib", RTLD_LAZY); if (libHandle) { os_log_copy_entries = dlsym(libHandle, "os_log_copy_entries"); os_log_enumerate_entries = dlsym(libHandle, "os_log_enumerate_entries"); } }); } /// 采集iOS 14及以下系统的OSLog日志 - (NSArray<NSString *> *)collectLegacyOSLogsWithError:(NSError **)error { [Self loadPrivateAPI]; if (!os_log_copy_entries || !os_log_enumerate_entries) { if (error) { *error = [NSError errorWithDomain:@"OSLogCollect" code:-1 userInfo:@{NSLocalizedDescriptionKey:@"私有API加载失败"}]; } return nil; } // 查询最近1小时日志 NSDate *startTime = [NSDate dateWithTimeIntervalSinceNow:-3600]; NSArray *entries = os_log_copy_entries(startTime, nil, OSLogCopyTypeInfo | OSLogCopyTypeError, error); if (!entries) { return nil; } NSMutableArray *logs = [NSMutableArray array]; os_log_enumerate_entries(entries, ^(NSDictionary *entry, BOOL *stop) { NSString *logMsg = entry[@"composedMessage"]; NSString *process = entry[@"process"]; NSDate *logDate = entry[@"date"]; NSString *formattedLog = [NSString stringWithFormat:@"[%@] [%@] %@", logDate, process, logMsg]; [logs addObject:formattedLog]; }); return logs.copy; }
上报优化建议
- 日志采集逻辑放在子线程执行,避免阻塞主线程UI交互
- 采集到的日志先做本地缓存、压缩,再批量上报,降低流量损耗和上报请求次数
- 敏感信息采集前需做脱敏处理,符合隐私合规要求
内容的提问来源于stack exchange,提问作者John_S
相关产品推荐
相关产品推荐

