iOS 线上崩溃监测、日志分析、问题复盘完整体系:从捕获、定位到根治

36 阅读8分钟

一、前言:为什么你的线上问题永远定位不准?

做 iOS 开发久了,几乎所有人都遇到过这些经典线上问题:

  • 测试环境百分百稳定,上线小规模用户偶现闪退,无法复现
  • Bugly/友盟 只报堆栈,不知道用户操作路径、上下文现场
  • 后台崩溃、OOM 内存溢出、卡死 ANR,无堆栈、无日志
  • 修复完问题过段时间再次复发,没有复盘、没有规范沉淀

很多团队的稳定性体系只停留在「接入 Bugly 看崩溃报表」,这是 被动式维稳。真正企业级大型项目需要一套闭环体系:

崩溃全覆盖捕获 → 精细化日志留存 → 结构化问题分析 → 标准化复盘沉淀 → 前置预防兜底

本文手把手搭建 iOS 完整线上稳定性体系,包含可直接上线的崩溃捕获代码、分级日志框架、日志脱敏加密、真实线上案例分析、标准化复盘模板,全部落地可用。

二、iOS 线上崩溃的完整分类(90% 崩溃都逃不出这 5 类)

想要做好监测,首先要知道「我们要捕获什么」,iOS 线上崩溃分为五大类,覆盖所有闪退场景:

1. OC 异常崩溃(NSException)

常见场景:数组越界、字典插空、参数非法、UI 主线程违规、KVO 重复移除。

特点:有完整堆栈,最容易定位。

2. Signal 信号崩溃(Mach-O 底层崩溃)

常见场景:野指针、内存踩踏、重复 free、多线程读写冲突。

常见信号:SIGSEGV、SIGABRT、SIGBUS、SIGILL

特点:第三方平台经常丢失堆栈,必须自研捕获兜底。

3. Swift 运行时崩溃

常见场景:隐式解包 nil、数组越界、类型强制转换失败、try 未捕获错误。

4. 卡死 / ANR WatchDog 崩溃

主线程卡顿超时被系统杀死,无崩溃堆栈、第三方平台经常不报,是线上隐性闪退重灾区。

5. 内存类崩溃(OOM / Jetsam)

内存峰值过高、后台占用内存超标被系统 Jetsam 机制杀进程,无任何崩溃日志,只能靠性能日志分析。

核心结论:只依赖 Bugly 只能捕获 60% 常规崩溃,信号崩溃、卡死、OOM 大量丢失,必须「第三方监控 + 自研底层捕获 + 本地日志兜底」三位一体。

三、搭建全覆盖崩溃捕获体系(可直接上线代码)

我们搭建一套自研崩溃捕获框架,兜底所有类型崩溃,弥补第三方平台缺失。

1. 捕获 OC 异常崩溃(NSException 全局捕获)


#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

@interface CrashMonitor : NSObject
+ (void)startMonitor;
@end

static void globalUncaughtExceptionHandler(NSException *exception) {
    // 1. 获取崩溃核心信息
    NSString *name = exception.name;
    NSString *reason = exception.reason;
    NSArray *stackSymbols = exception.callStackSymbols;
    
    // 2. 组装崩溃现场:设备、版本、操作栈、时间
    NSMutableDictionary *crashInfo = [NSMutableDictionary dictionary];
    crashInfo[@"crash_name"] = name;
    crashInfo[@"crash_reason"] = reason;
    crashInfo[@"crash_stack"] = [stackSymbols componentsJoinedByString:@"\n"];
    crashInfo[@"version"] = [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"];
    crashInfo[@"device"] = [UIDevice currentDevice].model;
    crashInfo[@"system"] = [UIDevice currentDevice].systemVersion;
    
    // 3. 本地加密写入日志 + 延迟上报
    [LogManager saveCrashLog:crashInfo];
    
    // 4. 留给第三方SDK继续上报
}

@implementation CrashMonitor
+ (void)startMonitor {
    // 注册全局异常捕获
    NSSetUncaughtExceptionHandler(&globalUncaughtExceptionHandler);
}
@end

2. 捕获 Signal 信号崩溃(野指针、内存踩踏兜底)

这是第三方平台最容易丢失的崩溃类型,也是大型项目疑难闪退的核心来源。


#include <signal.h>

// 监听常见崩溃信号
static int crashSignals[] = {
    SIGSEGV,
    SIGABRT,
    SIGBUS,
    SIGILL,
    SIGFPE
};

static void signalCrashHandler(int sig) {
    // 获取线程堆栈、信号类型
    NSString *signalMsg = [NSString stringWithFormat:@"Signal Crash: %d", sig];
    NSArray *stack = [NSThread callStackSymbols];
    
    NSMutableDictionary *crashInfo = [NSMutableDictionary dictionary];
    crashInfo[@"crash_type"] = @"Signal";
    crashInfo[@"crash_reason"] = signalMsg;
    crashInfo[@"crash_stack"] = [stack componentsJoinedByString:@"\n"];
    
    [LogManager saveCrashLog:crashInfo];
    
    // 退出进程,避免继续异常
    exit(EXIT_FAILURE);
}

// 注册信号监听
- (void)registerSignalMonitor {
    int count = sizeof(crashSignals) / sizeof(int);
    for (int i = 0; i < count; i++) {
        signal(crashSignals[i], signalCrashHandler);
    }
}

3. 主线程卡死 ANR 监测(解决无堆栈闪退)

通过主线程 RunLoop 心跳检测,识别卡死、ANR 问题,弥补系统 WatchDog 无日志缺陷。

// 主线程卡顿监测 Swift 版
class ANRMonitor {
    static let shared = ANRMonitor()
    private var isRunning = false
    private let checkInterval: TimeInterval = 0.8
    
    func start() {
        guard !isRunning else { return }
        isRunning = true
        
        DispatchQueue.global().async { [weak self] in
            while self?.isRunning ?? false {
                let semaphore = DispatchSemaphore(value: 0)
                DispatchQueue.main.async {
                    semaphore.signal()
                }
                // 超时说明主线程卡死
                let result = semaphore.wait(timeout: .now() + self.checkInterval)
                if result == .timedOut {
                    // 记录卡顿堆栈
                    let stack = Thread.callStackSymbols
                    LogManager.saveANRLog(stack: stack)
                }
            }
        }
    }
}

4. 监测体系组合方案(企业级标准)

Bugly/Firebase + 自研底层捕获 + ANR 卡顿监测

  • 第三方:负责大数据量统计、崩溃聚合、版本环比、告警推送
  • 自研捕获:兜底信号崩溃、卡死崩溃、丢失堆栈问题
  • 本地日志:留存崩溃前后上下文,解决「有堆栈无现场」问题

四、企业级分级日志体系(可落地、脱敏、加密、可上报)

崩溃只是结果,日志才是根因。没有上下文日志,90% 偶现崩溃无法定位。

1. 日志分级规范(统一团队标准)

  • DEBUG:开发调试日志,打包自动关闭
  • INFO:正常业务流程(页面打开、接口请求、缓存读写)
  • WARN:非致命异常(缓存失效、参数缺失、重试触发)
  • ERROR:业务错误(接口失败、数据解析失败、文件读写失败)
  • FATAL:崩溃、严重异常(配合崩溃捕获)

2. 日志分类维度(排查问题极速定位)

  • 业务日志:页面生命周期、用户点击、业务流程
  • 网络日志:请求地址、参数、响应码、耗时
  • 存储日志:沙盒读写、FMDB/Realm 操作、加密解密结果
  • 性能日志:帧率、内存、CPU、卡顿点
  • 崩溃日志:异常信息、信号堆栈、卡死记录

3. 核心能力:日志脱敏 + 加密存储(合规必备)

绝对禁止明文日志记录:手机号、Token、身份证、地址,会引发隐私合规风险。


// 日志脱敏工具
class LogSensitiveFilter {
    static func filterSensitive(_ content: String) -> String {
        var result = content
        // 手机号脱敏 138****1234
        result = result.replacingOccurrences(of: "(1[0-9]{2})[0-9]{4}([0-9]{4})", with: "$1****$2", options: .regularExpression)
        // Token 整体脱敏
        result = result.replacingOccurrences(of: "token=\w+", with: "token=***", options: .regularExpression)
        // 身份证脱敏
        result = result.replacingOccurrences(of: "([0-9]{6})[0-9]{8}([0-9]{4})", with: "$1****$2", options: .regularExpression)
        return result
    }
}

4. 日志沙盒加密存储 + 自动清理

日志存放路径规范:Library/Application Support/Log/ (不备份、持久化、不被系统清理)

能力:按天分割、7天自动过期、AES加密、崩溃前完整留存

五、线上问题分析实战(3个真实线上案例)

案例1:FMDB 多队列导致 SQLITE_BUSY 偶现崩溃

现象:用户频繁进出聊天页,偶现闪退,Bugly 堆栈模糊,无法稳定复现。

日志现场

  • 子线程 A 开启批量写入事务未关闭
  • 主线程 B 立即执行查询操作
  • 数据库文件锁冲突 SQLITE_BUSY,超时崩溃

根因:项目多处创建 FMDatabaseQueue,多队列竞争同一数据库文件锁。

修复方案:全局单例队列统一调度,所有读写收敛至全局 Queue。

案例2:沙盒 tmp 目录存储关键数据导致随机丢失、闪退

现象:部分用户离线数据偶现清空,偶发文件读取空指针崩溃。

根因:开发将离线解密文件放在 tmp 目录,系统后台回收清理文件。

修复:核心数据迁移至 Application Support,临时文件仅用作中转。

案例3:主线程大量文件解密导致 ANR 卡死、系统闪退

现象:页面滑动卡顿,部分用户直接 WatchDog 杀进程,无崩溃堆栈。

日志定位:主线程执行 AES 大文件解密,阻塞 RunLoop 超过 1.2s。

修复:所有文件加解密、IO 操作全部迁移至子线程。

六、标准化问题复盘体系(团队可直接套用)

没有复盘的 Bug,一定会重蹈覆辙。统一 四段式复盘模板

1. 复盘标准四要素

  • 现象:崩溃率、影响用户、触发场景、版本范围
  • 根因:代码层级根本原因(禁止归因「系统问题、偶现问题」)
  • 修复:临时修复方案、版本生效时间
  • 规避:长期规范、代码检查、监控兜底、新增单测

2. 复盘沉淀落地机制

  • 高频崩溃录入团队问题库
  • 对应代码增加注释与告警日志
  • CI 流水线增加高危代码扫描
  • 新增专项测试用例,防止回归

七、线上稳定性兜底预防机制

1. 代码层自愈兜底

  • 数组、字典、参数强制判空兜底
  • 数据库事务失败自动回滚
  • 文件读写异常捕获、损坏文件自动重建
  • 网络失败重试、弱网/断网降级

2. 监控告警机制

  • 版本崩溃率 > 0.5% 触发告警
  • 新增崩溃类型立即推送
  • 卡顿率、OOM 率、页面加载耗时实时监控

3. 灰度与回滚机制

小流量灰度发布,监控稳定性指标,异常立即热修复或版本回滚,杜绝大规模线上事故。

八、全文总结

  1. 单纯依赖第三方崩溃平台无法覆盖全部线上问题,必须自研底层捕获兜底信号崩溃、卡死、OOM 场景。

  2. 日志是定位疑难崩溃的核心,必须做到分级、分类、脱敏、加密、留存、可上报

  3. 所有线上问题必须走标准化复盘闭环,从「修 Bug」升级为「根治问题、完善规范」。

  4. 真正的线上稳定性 = 全量捕获 + 精细化日志 + 精准分析 + 复盘沉淀 + 前置预防