全部產品
Search
文件中心

ApsaraVideo VOD:日誌系統

更新時間:Sep 10, 2026

本文介紹AliPlayerKit日誌系統LogHub的核心概念、功能特性及使用方法。

概念介紹

什麼是 LogHub?

LogHub 是 PlayerKit 架構中所有日誌輸出的統一入口,是一個靜態工具類,提供統一的日誌輸出功能。

它封裝了 Android 原生 Log API,在保留原有功能的基礎上,增加了以下核心能力:

能力

說明

日誌開關

控制日誌輸出是否啟用。

控制台開關

控制日誌是否輸出到 Logcat。

層級過濾

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

監聽器機制

支援外部監聽日誌輸出,實現自訂處理(如寫入檔案)。

什麼是日誌監聽器?

日誌監聽器 (LogHubListener) 是用於接收 LogHub 輸出日誌的回調介面。通過實現該介面,可以自訂日誌的處理方式,例如:

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

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

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

功能特性

解決問題

  • 日誌分散,難以統一管理和過濾。

  • 發布版本需要關閉調試日誌,缺乏統一開關。

  • 問題排查時缺乏記錄檔支援。

  • 播放器 SDK 日誌與業務日誌分離,難以關聯分析。

核心價值

使用方式

說明

優勢

基礎使用

直接使用 LogHub 輸出日誌。

無需配置,開箱即用。

層級控制

設定記錄層級過濾。

開發調試詳細,生產環境精簡。

監聽器擴充

註冊監聽器處理日誌。

日誌持久化、遠程上報等。

架構優勢:

  • 統一入口:所有日誌通過 LogHub 統一輸出,便於管理和控制。

  • 靈活控制:支援全域開關、控制台開關、層級過濾三重控制。

  • 可擴充:通過監聽器機制支援自訂Tlog,如檔案寫入、遠程上報。

  • 安全執行緒:監聽器列表使用 CopyOnWriteArrayList,支援多安全執行緒訪問。

核心能力

能力

說明

多層級日誌

支援 VERBOSE、DEBUG、INFO、WARN、ERROR 五個層級。

異常日誌

支援攜帶 Throwable 異常資訊輸出。

長日誌分段

自動分段處理超長日誌,避免 Logcat 截斷。

效能日誌

提供效能時間日誌輸出,便於效能分析。

SDK 日誌整合

通過 IPlayerLogger 介面統一管理播放器 SDK 日誌。

內建群組件詳解

記錄層級

層級

值

說明

適用情境

VERBOSE

0

最詳細資訊層級,輸出所有調試資訊。

開發調試階段。

DEBUG

1

調試資訊層級。

調試特定功能。

INFO

2

資訊層級,輸出一般資訊。

預設層級,生產環境推薦。

WARN

3

警告層級,輸出潛在問題提示。

關注潛在風險。

ERROR

4

錯誤層級,輸出錯誤資訊。

關注嚴重問題。

NONE

100

禁用所有日誌。

關閉日誌輸出。

核心類

類

說明

LogHub

日誌中心工具類,統一日誌輸出入口。

LogLevel

記錄層級註解定義。

LogInfo

日誌資訊資料類,封裝單條日誌完整資訊。

LogHubListener

日誌監聽器介面,用於接收日誌回調。

IPlayerLogger

播放器全域日誌介面,封裝 SDK 日誌功能。

DefaultPlayerLogger

預設播放器日誌實現。

LoggerCallback

播放器日誌回調介面。

基礎使用

日誌系統提供三種使用原則,開發人員可根據需求選擇合適的方式。

策略

說明

適用情境

直接輸出日誌

最簡單的使用方式,直接調用 LogHub 方法。

開發調試、臨時日誌。

配置記錄層級

根據環境配置不同記錄層級。

區分開發/生產環境。

註冊監聽器

通過監聽器自訂Tlog。

日誌持久化、遠程上報。

策略一:直接輸出日誌

最簡單的使用方式,直接調用 LogHub 的靜態方法輸出日誌:

// 輸出 INFO 層級日誌
LogHub.i(this, "playVideo", "開始播放視頻");

// 輸出 WARN 層級日誌
LogHub.w(this, "onBuffering", "緩衝中,當前進度: ", progress);

// 輸出 ERROR 層級日誌
LogHub.e(this, "onError", "播放失敗,錯誤碼: ", errorCode);

// 輸出帶異常的 ERROR 日誌
LogHub.e(this, "onException", throwable, "發生異常");

// 輸出效能時間日誌
LogHub.t("loadVideoSource", costTime);

日誌格式說明:

[類名] 方法名: 訊息內容

Logcat 過濾建議:

package:mine tag:AliPlayerKit

策略二:配置記錄層級

根據運行環境配置不同的記錄層級:

// 開發環境:輸出所有日誌
if (BuildConfig.DEBUG) {
    LogHub.setEnableLog(true);
    LogHub.setEnableConsoleLog(true);
    LogHub.setLogLevel(LogLevel.VERBOSE);
}

// 生產環境:只輸出 INFO 及以上層級
else {
    LogHub.setEnableLog(true);
    LogHub.setEnableConsoleLog(false);  // 關閉控制台輸出
    LogHub.setLogLevel(LogLevel.INFO);
}

// 完全關閉日誌(敏感情境)
LogHub.setEnableLog(false);

策略三:註冊監聽器

通過監聽器實現日誌自訂處理:

// 建立監聽器
LogHubListener listener = logInfo -> {
    // 處理日誌資訊
    String formattedLog = logInfo.getFormattedMessage();
    // 寫入檔案、上傳伺服器等
    saveToFile(formattedLog);
};

// 註冊監聽器
LogHub.addListener(listener);

// 移除監聽器(不再需要時)
LogHub.removeListener(listener);   

進階使用

如何?日誌持久化?

通過 LogHubListener 介面實現記錄檔寫入,便於問題排查。

  1. 建立日誌監聽器。

    public class FileLogListener implements LogHubListener {
    
        private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
        private final File mLogFile;
    
        public FileLogListener(Context context) {
            // 記錄檔路徑
            File logDir = new File(context.getExternalFilesDir(null), "logs");
            if (!logDir.exists()) {
                logDir.mkdirs();
            }
            mLogFile = new File(logDir, "player_" + getDateStr() + ".log");
        }
    
        @Overridepublic void onLog(@NonNull LogInfo logInfo) {
            // 非同步寫入檔案,避免阻塞調用線程
            mExecutor.execute(() -> {
                try {
                    String log = logInfo.getFormattedMessage() + "\n";
                    FileOutputStream fos = new FileOutputStream(mLogFile, true);
                    fos.write(log.getBytes(StandardCharsets.UTF_8));
                    fos.close();
                } catch (IOException e) {
                    // 忽略寫入異常
                }
            });
        }
    }
  2. 在Application中註冊。

    public class MyApplication extends Application {
    
        private FileLogListener mFileLogListener;
    
        @Overridepublic void onCreate() {
            super.onCreate();
    
            // 建立並註冊檔案日誌監聽器
            mFileLogListener = new FileLogListener(this);
            LogHub.addListener(mFileLogListener);
        }
    }
  3. 擷取記錄檔。

    // 記錄檔路徑
    File logDir = new File(getExternalFilesDir(null), "logs");
    File[] logFiles = logDir.listFiles();
    
    // 提供給支援人員團隊進行分析

樣本參考:playerkit-examples/example-log-system/FileLogListener.java。

如何整合播放器 SDK 日誌?

通過IPlayerLogger介面統一管理播放器 SDK 的日誌輸出。

  1. 建立播放器日誌執行個體。

    // 在 Application 中初始化
    IPlayerLogger playerLogger = new DefaultPlayerLogger(context);
    
    // 啟用 SDK 控制台日誌(調試時開啟)
    playerLogger.enableConsoleLog(BuildConfig.DEBUG);
    
    // 設定 SDK 記錄層級
    playerLogger.setLogLevel(LogLevel.INFO);
  2. 設定 SDK 日誌回調。

    playerLogger.setLogCallback((level, message) -> {
        // 處理 SDK 日誌,如寫入檔案、上報等
        LogHub.log(level, "PlayerSDK", message);
    });

如何在 UI 上展示日誌?

通過監聽器將日誌即時展示在介面上。

public class LogPanelActivity extends AppCompatActivity {

    private TextView mTvLogOutput;
    private LogHubListener mLogListener;

    @Overrideprotected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_log_panel);

        mTvLogOutput = findViewById(R.id.tv_log_output);

        // 建立 UI 日誌監聽器
        mLogListener = logInfo -> runOnUiThread(() -> {
            mTvLogOutput.append(logInfo.getFormattedMessage() + "\n");
        });

        // 註冊監聽器
        LogHub.addListener(mLogListener);
    }

    @Overrideprotected void onDestroy() {
        super.onDestroy();
        // 移除監聽器,避免記憶體流失
        LogHub.removeListener(mLogListener);
    }
}

最佳實務

記錄層級選擇

情境

推薦層級

說明

詳細調試資訊

VERBOSE

開發階段追蹤流程。

調試關鍵節點

DEBUG

記錄關鍵狀態變化。

重要商務資訊

INFO

生產環境保留的重要訊息。

潛在問題警告

WARN

可恢複的異常、效能警告。

嚴重錯誤

ERROR

導致功能失敗的錯誤。

生產環境配置

public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();

        if (BuildConfig.DEBUG) {
            // 開發環境:詳細日誌 + 控制台輸出
            LogHub.setEnableLog(true);
            LogHub.setEnableConsoleLog(true);
            LogHub.setLogLevel(LogLevel.VERBOSE);
        } else {
            // 生產環境:精簡日誌 + 關閉控制台 + 檔案持久化
            LogHub.setEnableLog(true);
            LogHub.setEnableConsoleLog(false);  // 關閉 Logcat 輸出
            LogHub.setLogLevel(LogLevel.INFO);   // 只保留 INFO 及以上
            LogHub.addListener(new FileLogListener(this));
        }
    }
}  

注意事項

事項

說明

避免敏感資訊

日誌中不要輸出使用者隱私、密鑰等敏感資訊。

非同步寫入檔案

監聽器中寫檔案應使用非同步方式,避免阻塞。

監聽器異常處理

監聽器異常不應影響正常日誌輸出。

及時移除監聽器

頁面銷毀時移除監聽器,避免記憶體流失。

全域監聽器位置

檔案日誌監聽器建議放在 Application 中,確保全生命週期收集。

樣本參考

專案提供了完整的樣本,位於 playerkit-examples/example-log-system。

樣本功能

功能

說明

日誌輸出示範

示範各層級日誌輸出。

UI 日誌展示

在介面上即時展示日誌。

檔案日誌監聽器

示範如何?日誌持久化。

運行樣本

在 Demo App 中選擇 Log System 樣本查看效果。

API 參考

核心介面

介面/類

說明

LogHub

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

LogHubListener

日誌監聽器介面,接收日誌回調。

LogInfo

日誌資訊資料類。

IPlayerLogger

播放器全域日誌介面。

LogHub 主要方法

方法

說明

setEnableLog(boolean)

開啟/關閉日誌輸出(總開關)。

setEnableConsoleLog(boolean)

開啟/關閉控制台日誌輸出。

setLogLevel(int)

設定記錄層級過濾。

addListener(LogHubListener)

添加日誌監聽器。

removeListener(LogHubListener)

移除日誌監聽器。

v/d/i/w/e(Object, String, Object...)

輸出各層級日誌。

t(String, long)

輸出效能時間日誌。

LogInfo 主要方法

方法

說明

getLevel()

擷取記錄層級。

getTag()

擷取日誌標籤。

getMessage()

擷取日誌訊息。

getThrowable()

擷取異常資訊。

getThreadName()

擷取線程名稱。

getTimestamp()

擷取時間戳記(毫秒)。

getFormattedMessage()

擷取格式化的日誌字串。

技術原理

長日誌分段機制

Logcat 單條日誌有長度限制(約 4000 字元),LogHub 通過分段機制處理超長日誌:

private static final int BUFFER_SIZE = 3000;

// 分段輸出到 Logcatwhile (startIndex < length) {
    int endIndex = Math.min(length, startIndex + BUFFER_SIZE);
    String sub = s.substring(startIndex, endIndex);
    // 輸出分段日誌...
    startIndex = endIndex;
}

最佳化點:對於監聽器,發送完整的日誌內容;對於 Logcat,進行分段輸出。

安全執行緒機制

監聽器列表使用 CopyOnWriteArrayList,確保多安全執行緒:

private static final List<LogHubListener> sListeners = new CopyOnWriteArrayList<>();

特點:

  • 讀操作無鎖,效能高。

  • 寫操作複製新數組,不影響進行中的讀操作。

  • 適合讀多寫少的監聽器註冊/移除情境。

常見問題

如何在 Logcat 中過濾 PlayerKit 日誌?

使用以下過濾條件:

package:mine tag:AliPlayerKit

或者按層級過濾:

package:mine tag:AliPlayerKit level:info       

生產環境記錄檔在哪裡?

預設路徑(如果使用 FileLogListener 樣本):

/sdcard/Android/data/[包名]/files/logs/

監聽器回調在哪個線程?

監聽器回調在日誌輸出的調用線程執行。如果需要在 UI 線程處理,請使用 runOnUiThread() 切換線程。

高頻錯誤錯例

以下是客戶回函中最常導致問題的情況,請務必避免:

錯例 1:監聽器中阻塞操作導致卡頓

錯誤碼:

@Overridepublic void onLog(@NonNull LogInfo logInfo) {
    // 在主線程同步寫檔案,會阻塞 UItry {
        FileOutputStream fos = new FileOutputStream(mLogFile, true);
        fos.write(logInfo.getFormattedMessage().getBytes());
        fos.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

正確代碼:

private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();

@Overridepublic void onLog(@NonNull LogInfo logInfo) {
    // 非同步寫入檔案
    mExecutor.execute(() -> {
        try {
            // 檔案寫入邏輯...
        } catch (IOException e) {
            // 忽略
        }
    });
}

問題原因:如果日誌輸出在主線程,同步檔案寫入會阻塞 UI,導致卡頓甚至 ANR。

錯例 2:忘記移除監聽器導致記憶體流失

錯誤碼:

@Overrideprotected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // 註冊了監聽器但從未移除
    LogHub.addListener(logInfo -> {
        mTvLogOutput.append(logInfo.getMessage());
    });
}

正確代碼:

private LogHubListener mLogListener;

@Overrideprotected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    mLogListener = logInfo -> runOnUiThread(() -> {
        mTvLogOutput.append(logInfo.getMessage());
    });
    LogHub.addListener(mLogListener);
}

@Overrideprotected void onDestroy() {
    super.onDestroy();
    // 及時移除監聽器if (mLogListener != null) {
        LogHub.removeListener(mLogListener);
    }
}

問題原因:監聽器持有 Activity 引用,Activity 銷毀後無法釋放,導致記憶體流失。

錯例 3:監聽器異常影響其他監聽器

錯誤碼:

@Overridepublic void onLog(@NonNull LogInfo logInfo) {
    // 未處理異常,如果此處拋出異常會影響其他監聽器
    String data = logInfo.getMessage();
    int result = 1 / 0;  // 拋出異常
}

正確代碼:

@Overridepublic void onLog(@NonNull LogInfo logInfo) {
    try {
        // 在監聽器內部處理異常
        processLog(logInfo);
    } catch (Exception e) {
        // 監聽器自身的異常處理,不影響日誌系統
        android.util.Log.e("FileLogListener", "Error processing log", e);
    }
}

問題原因:雖然 LogHub 內部已捕獲監聽器異常,但監聽器本身應該處理可能的異常,保證自身穩定性。

如何調試?

  1. 檢查日誌開關狀態:

    boolean enabled = LogHub.isLogEnabled();
    boolean consoleEnabled = LogHub.isConsoleLogEnabled();
    int level = LogHub.getLogLevel();
  2. 查看 Logcat 輸出:

    使用 tag:AliPlayerKit 過濾查看日誌輸出。

  3. 檢查監聽器數量:

    日誌系統的監聽器數量可通過反射查看(調試用),確保監聽器正確註冊和移除。