全部產品
Search
文件中心

ApsaraVideo VOD:日誌系統

更新時間:Sep 10, 2026

日誌系統 (Log System) 是 AliPlayerKit 的基礎支撐模組。它通過統一的日誌中心 LogHub,提供日誌輸出、層級過濾、觀察者機制等核心能力,實現播放器運行時資訊的可視化與可追溯。

概念介紹

什麼是 LogHub?

LogHub 是 AliPlayerKit 中所有日誌輸出的統一入口,採用單例模式設計。它封裝了 iOS 原生日誌 API(NSLog),在保留原有功能的基礎上,增加了以下核心能力:

能力

說明

日誌開關

控制日誌輸出是否啟用(總開關)

控制台開關

控制日誌是否輸出到 Xcode Console

層級過濾

只輸出指定層級及以上的日誌

觀察者機制

支援外部觀察者接收日誌輸出,實現自訂處理(如寫入檔案)

什麼是日誌觀察者?

日誌觀察者 (LogObserver) 是用於接收 LogHub 輸出日誌的回調協議。通過實現該協議,可以自訂日誌的處理方式,例如:

  • 將日誌寫入檔案,便於問題排查時提供給支援人員團隊

  • 將日誌上傳到遠程伺服器,實現遠程日誌收集

  • 在 UI 上展示日誌,用於調試面板

混合執行緒模式

LogHub 採用混合執行緒模式:

操作

線程

線程名和時間戳記捕獲

調用線程同步執行

控制台輸出(NSLog)

調用線程同步執行

觀察者通知

後台串列隊列非同步指派

說明

LogObserver.onLog: 回調在後台串列隊列執行,如需更新 UI 請切換到主線程。

功能特性

核心能力

能力

說明

多層級日誌

支援 Verbose、Debug、Info、Warn、Error 五個層級

三重控制

全域開關 + 層級過濾 + 控制台開關,精確控制輸出範圍

觀察者機制

支援註冊自訂觀察者,實現日誌持久化、遠程上報等

便捷宏

提供 SLOGV/D/I/W/E 宏,自動捕獲源碼位置

日誌面板

通過 LogPanelSlot 在播放器視圖中即時展示日誌(調試專用)

全域快捷 API

通過 AliPlayerKit 類方法快速配置記錄層級和控制台輸出

核心組件

組件

類型

說明

LogHub

單例

日誌中心,統一日誌輸出入口

LogLevel

枚舉

記錄層級定義

LogInfo

資料類

日誌資訊模型,封裝單條日誌的完整資訊

LogObserver

協議

日誌觀察者介面

LogPanelSlot

插槽

播放器內嵌日誌面板,即時展示關鍵日誌

使用方式

基本用法 — 全域快捷配置

通過 AliPlayerKit 類方法快速配置日誌系統(推薦方式):

// 在 AppDelegate 中,setup 之後配置
[AliPlayerKit setup];

// 設定記錄層級
[AliPlayerKit setLogLevel:LogLevelVerbose];

// 啟用/禁用控制台日誌
[AliPlayerKit enableConsoleLog:YES];

AliPlayerKit 方法

說明

setLogLevel:

設定記錄層級,低於該層級的日誌被忽略(預設 LogLevelInfo)

logLevel

擷取當前記錄層級

enableConsoleLog:

啟用/禁用控制台輸出(預設啟用)

isConsoleLogEnabled

查詢控制台日誌是否啟用

setDebugModeEnabled:

開啟/關閉偵錯模式

isDebugModeEnabled

查詢偵錯模式是否開啟

setLogPanelEnabled:

啟用/禁用日誌面板插槽(預設禁用)

isLogPanelEnabled

查詢日誌面板是否啟用

基本用法 — 輸出日誌

使用 SLOG 系列便捷宏輸出各層級日誌,宏會自動捕獲源檔案名稱和行號:

#import <AliPlayerKit/LogMacros.h>

// 輸出各層級日誌(自動捕獲 __FILE_NAME__ 和 __LINE__)
SLOGV(@"viewDidLoad called");
SLOGD(@"配置播放器: videoId=%@", videoId);
SLOGI(@"開始播放視頻: %@", videoTitle);
SLOGW(@"緩衝中,當前進度: %.1f%%", progress * 100);
SLOGE(@"播放失敗,錯誤碼: %ld", (long)errorCode);

便捷宏列表:

宏

層級

說明

SLOGV(fmt, ...)

Verbose

最詳細資訊層級日誌

SLOGD(fmt, ...)

Debug

調試資訊

SLOGI(fmt, ...)

Info

一般資訊

SLOGW(fmt, ...)

Warn

潛在問題

SLOGE(fmt, ...)

Error

錯誤資訊

說明

SLOG 宏內部調用 LogHub 的 logLevel:file:line:message:error: 方法,自動通過 __FILE_NAME__ 和 __LINE__ 捕獲源碼位置,無需手動傳入 tag。

也可以直接調用 LogHub 執行個體方法,手動指定 tag:

// 使用 tag 標識日誌來源
[[LogHub sharedHub] i:@"PlayerVC" msg:@"開始播放"];
[[LogHub sharedHub] e:@"MediaPlayer" msg:@"播放失敗" error:error];

基本用法 — 配置記錄層級

通過 LogHub 直接配置日誌屬性:

LogHub *logHub = [LogHub sharedHub];

#ifdef DEBUG
    // 開發環境:詳細日誌 + 控制台輸出
    logHub.logEnabled = YES;
    logHub.consoleLogEnabled = YES;
    logHub.logLevel = LogLevelVerbose;
#else
    // 生產環境:精簡日誌 + 關閉控制台
    logHub.logEnabled = YES;
    logHub.consoleLogEnabled = NO;
    logHub.logLevel = LogLevelInfo;
#endif

三重控制機制:

控制層

屬性

說明

總開關

logEnabled

關閉後所有日誌靜默(包括觀察者通知)

層級過濾

logLevel

低於該層級的日誌被忽略(預設 LogLevelInfo)

控制台開關

consoleLogEnabled

控制是否通過 NSLog 輸出到 Xcode Console

說明

關閉 consoleLogEnabled 後,日誌仍會通過觀察者通知分發,僅抑制控制台輸出。關閉 logEnabled 後,所有日誌請求被完全忽略,觀察者也不會收到通知。

進階用法 — 註冊日誌觀察者

通過實現 LogObserver 協議自訂Tlog:

// 建立並註冊檔案日誌觀察者
FileLogObserver *fileObserver = [[FileLogObserver alloc] init];
[[LogHub sharedHub] addObserver:fileObserver];

// 移除觀察者(不再需要時)
[[LogHub sharedHub] removeObserver:fileObserver];
說明

LogHub 以強引用方式持有觀察者,直到通過 removeObserver: 顯式移除。因此必須在適當時機(如 dealloc 或 viewWillDisappear:)調用 removeObserver: 以避免循環參考。

進階用法 — 日誌面板

LogPanelSlot 是一個內建的調試插槽,可在播放器視圖中即時展示關鍵日誌資訊:

// 啟用日誌面板(建議僅在調試情境中啟用)
[AliPlayerKit setLogPanelEnabled:YES];

互動

說明

點擊標題列

展開/摺疊日誌內容區

點擊"清除"按鈕

清空已展示的日誌

長按日誌地區

將日誌內容拷貝到剪貼簿

說明

日誌面板僅顯示 Debug 及以上層級的日誌。需要在 rebuildSlots 之前設定 setLogPanelEnabled:。在 Minimal 情境中不可見。

自訂開發

日誌觀察者協議 (LogObserver)

@protocol LogObserver <NSObject>

@required

/// 日誌輸出回調
/// @param logInfo 日誌資訊對象,包含層級、標籤、訊息和時間戳記
/// @warning 回調在後台串列隊列執行,如需更新 UI 請 dispatch 到主線程
- (void)onLog:(LogInfo *)logInfo;

@end

LogInfo資料模型

屬性

類型

說明

level

LogLevel

記錄層級(readonly)

tag

NSString *

日誌標籤(readonly)

message

NSString *

日誌訊息內容(readonly)

timestamp

NSDate *

日誌產生時間戳記(readonly)

threadName

NSString *

組建記錄檔的線程名稱(readonly)

fileName

NSString *

源檔案名稱,如 @"MyClass.m"(readonly)

line

NSInteger

原始碼行號(readonly)

error

NSError *

關聯的錯誤對象,可為 nil(readonly)

方法

說明

formattedString

擷取格式化的日誌字串

initWithLevel:fileName:line:message:error:threadName:timestamp:

指定初始化方法

initWithLevel:tag:message:

便捷初始化方法(自動捕獲時間戳記和線程名)

格式化輸出格式:

2024-04-22 10:30:00.123 I/AliPlayerKit [version] [FileName:line] [threadName] : message

如果存在錯誤,將附加額外一行:

Error: domain=xxx code=xxx userInfo=xxx

自訂檔案日誌觀察者樣本

以下是一個檔案日誌觀察者的完整實現:

@interface MyFileLogObserver : NSObject <LogObserver>
- (instancetype)initWithLogDirectory:(NSString *)directory;
@end

@implementation MyFileLogObserver {
    NSString *_logFilePath;
    dispatch_queue_t _writeQueue;
    NSFileHandle *_fileHandle;
}

- (instancetype)initWithLogDirectory:(NSString *)directory {
    self = [super init];
    if (self) {
        _writeQueue = dispatch_queue_create("com.example.filelog",
                                            DISPATCH_QUEUE_SERIAL);
        [self setupLogFileAtDirectory:directory];
    }
    return self;
}

- (void)setupLogFileAtDirectory:(NSString *)directory {
    NSFileManager *fm = [NSFileManager defaultManager];
    if (![fm fileExistsAtPath:directory]) {
        [fm createDirectoryAtPath:directory
      withIntermediateDirectories:YES
                       attributes:nil
                            error:nil];
    }

    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
    formatter.dateFormat = @"yyyy-MM-dd";
    NSString *fileName = [NSString stringWithFormat:@"player_%@.log",
                          [formatter stringFromDate:[NSDate date]]];
    _logFilePath = [directory stringByAppendingPathComponent:fileName];

    if (![fm fileExistsAtPath:_logFilePath]) {
        [fm createFileAtPath:_logFilePath contents:nil attributes:nil];
    }
    _fileHandle = [NSFileHandle fileHandleForWritingAtPath:_logFilePath];
    [_fileHandle seekToEndOfFile];
}

#pragma mark - LogObserver

- (void)onLog:(LogInfo *)logInfo {
    NSString *formatted = [logInfo formattedString];

    dispatch_async(_writeQueue, ^{
        NSString *line = [formatted stringByAppendingString:@"\n"];
        NSData *data = [line dataUsingEncoding:NSUTF8StringEncoding];
        @try {
            [self->_fileHandle writeData:data];
        } @catch (NSException *exception) {
            // 忽略寫入異常
        }
    });
}

- (void)dealloc {
    [_fileHandle closeFile];
}

@end
說明

onLog: 回調已在後台串列隊列執行,但檔案 I/O 仍建議使用獨立的寫入隊列,避免阻塞日誌分發隊列影響其他觀察者。

完整整合樣本

// AppDelegate.m
#import <AliPlayerKit/AliPlayerKit.h>

@interface AppDelegate ()
@property (nonatomic, strong) MyFileLogObserver *fileLogObserver;
@end

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    // 1. 初始化 AliPlayerKit
    [AliPlayerKit setup];

    // 2. 配置日誌系統
#ifdef DEBUG
    [AliPlayerKit setLogLevel:LogLevelVerbose];
    [AliPlayerKit enableConsoleLog:YES];
    // 可選:啟用日誌面板
    [AliPlayerKit setLogPanelEnabled:YES];
#else
    [AliPlayerKit setLogLevel:LogLevelInfo];
    [AliPlayerKit enableConsoleLog:NO];

    // 3. 註冊檔案日誌觀察者(僅生產環境)
    NSString *logDir = [NSSearchPathForDirectoriesInDomains(
        NSDocumentDirectory, NSUserDomainMask, YES).firstObject
        stringByAppendingPathComponent:@"PlayerLogs"];
    self.fileLogObserver = [[MyFileLogObserver alloc] initWithLogDirectory:logDir];
    [[LogHub sharedHub] addObserver:self.fileLogObserver];
#endif

    return YES;
}

@end
說明

PlayerKitUsages/UsageLogSystem 模組提供了完整的日誌系統使用樣本,包含 FileLogObserver 檔案日誌觀察者和 UI 日誌展示。

最佳實務

環境配置建議

環境

總開關

控制台

層級

觀察者

日誌面板

開發調試

YES

YES

Verbose

可選(UI 面板)

可選

自我裝載

YES

YES

Debug

檔案日誌

NO

生產環境

YES

NO

Info

檔案日誌 + 遠程上報

NO

敏感情境

NO

—

—

—

NO

常見錯誤

錯誤 1:觀察者回調中直接更新 UI

// ✗ 錯誤:onLog: 在後台串列隊列執行,直接更新 UI 會崩潰
- (void)onLog:(LogInfo *)logInfo {
    self.logLabel.text = [logInfo formattedString];  // 崩潰!
}

// ✓ 正確:切換到主線程更新 UI
- (void)onLog:(LogInfo *)logInfo {
    NSString *formatted = [logInfo formattedString];
    dispatch_async(dispatch_get_main_queue(), ^{
        self.logLabel.text = formatted;
    });
}

錯誤 2:觀察者中同步阻塞操作

// ✗ 錯誤:在回調中同步寫檔案,阻塞日誌分發隊列
- (void)onLog:(LogInfo *)logInfo {
    NSString *log = [logInfo formattedString];
    [log writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:nil];
}

// ✓ 正確:非同步寫入檔案
- (void)onLog:(LogInfo *)logInfo {
    NSString *log = [logInfo formattedString];
    dispatch_async(_writeQueue, ^{
        // 非同步寫入...
    });
}

錯誤 3:忘記移除觀察者導致循環參考

// LogHub 弱引用觀察者,不移除不會造成循環參考,但可能在頁面消失後仍收到日誌回調
- (void)viewDidLoad {
    [[LogHub sharedHub] addObserver:self];
}

// ✓ 推薦:在生命週期結束時主動移除
- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
    [[LogHub sharedHub] removeObserver:self];
}
說明

與通知系統的 Dispatcher 類似,LogHub 以弱引用方式持有觀察者,因此不會造成記憶體流失;調用 removeObserver: 主要是為了避免在觀察者生命週期結束後繼續收到無意義的日誌回調。

API 速查

記錄層級 (LogLevel)

層級

枚舉值

數值

說明

Verbose

LogLevelVerbose

0

最詳細,開發調試

Debug

LogLevelDebug

1

調試資訊

Info

LogLevelInfo

2

預設層級,生產推薦

Warn

LogLevelWarn

3

潛在問題

Error

LogLevelError

4

錯誤資訊

None

LogLevelNone

100

禁用所有日誌

說明

LogLevel.h 同時提供了 LogLevelToString() 內嵌函式,將層級轉為縮寫字串(V/D/I/W/E/N)。

LogHub 核心 API

屬性 / 方法

類型

說明

+sharedHub

類方法

擷取單例

logEnabled

BOOL

日誌總開關(預設 YES)

logLevel

LogLevel

記錄層級(預設 LogLevelInfo)

consoleLogEnabled

BOOL

控制台開關(預設 YES)

v:msg:

執行個體方法

輸出 Verbose 日誌

d:msg:

執行個體方法

輸出 Debug 日誌

i:msg:

執行個體方法

輸出 Info 日誌

w:msg:

執行個體方法

輸出 Warn 日誌

e:msg:

執行個體方法

輸出 Error 日誌

e:msg:error:

執行個體方法

輸出 Error 日誌,附帶 NSError

log:tag:msg:

執行個體方法

按指定層級輸出

logLevel:file:line:message:error:

執行個體方法

核心日誌方法(供 SLOG 宏使用)

addObserver:

執行個體方法

添加觀察者(弱引用,重複添加無效果)

removeObserver:

執行個體方法

移除觀察者

+levelToString:

類方法

層級轉字串(V/D/I/W/E/N)

+stringToLevel:

類方法

字串轉層級(不區分大小寫,無法識別時返回 LogLevelInfo)

AliplayerKit日誌快捷API

方法

說明

+setLogLevel:

設定記錄層級(需在 setup 之後調用)

+logLevel

擷取當前記錄層級

+enableConsoleLog:

啟用/禁用控制台日誌輸出

+isConsoleLogEnabled

查詢控制台日誌是否啟用

+setDebugModeEnabled:

開啟/關閉偵錯模式

+isDebugModeEnabled

查詢偵錯模式是否開啟

+setLogPanelEnabled:

啟用/禁用日誌面板插槽

+isLogPanelEnabled

查詢日誌面板是否啟用

SLOG便捷宏

宏

層級

展開形式

SLOGV(fmt, ...)

Verbose

[[LogHub sharedHub] logLevel:LogLevelVerbose file:@(__FILE_NAME__) line:__LINE__ message:... error:nil]

SLOGD(fmt, ...)

Debug

同上,層級為 Debug

SLOGI(fmt, ...)

Info

同上,層級為 Info

SLOGW(fmt, ...)

Warn

同上,層級為 Warn

SLOGE(fmt, ...)

Error

同上,層級為 Error

LogInfo屬性

屬性

類型

說明

level

LogLevel

記錄層級

tag

NSString *

日誌標籤

message

NSString *

日誌訊息內容

timestamp

NSDate *

日誌產生時間戳記

threadName

NSString *

線程名稱

fileName

NSString *

源檔案名稱

line

NSInteger

原始碼行號

error

NSError *

關聯的錯誤對象(nullable)

formattedString

方法

擷取格式化的日誌字串

LogObserver 協議

方法

必選

說明

onLog:

是(@required)

日誌輸出回調,在後台串列隊列執行