このトピックでは、AliPlayerKit のロギングシステムである LogHub の中核となる概念、機能、および使用方法について説明します。
概念
LogHub とは
LogHub は、PlayerKit フレームワークにおけるすべてのログ出力のための統一エントリポイントです。一貫したロギング機能を提供する静的なユーティリティクラスです。
LogHub は Android のネイティブ Log API をラップしています。元の機能をすべて保持しつつ、以下のコア機能を追加しています。
機能 | 説明 |
ログの有効/無効化 | ログを出力するかどうかを制御します。 |
コンソールの有効/無効化 | ログを Logcat に出力するかどうかを制御します。 |
レベルフィルタリング | 指定されたレベル以上のログのみを出力します。 |
リスナーメカニズム | ファイルへの書き込みなど、カスタムのログ処理を行うための外部リスナーをサポートします。 |
ログリスナーとは
LogHubListener は、LogHub からログを受信するコールバックインターフェイスです。このインターフェイスを実装することで、ログの処理方法をカスタマイズできます。例:
トラブルシューティング中にテクニカルサポートと共有するために、ログをファイルに書き込みます。
一元収集のために、ログをリモートサーバーにアップロードします。
デバッグパネル用に、ログを UI に表示します。
機能
解決される課題
ログがコードベース全体に散在しており、管理やフィルタリングが困難です。
リリースビルドではデバッグログを無効にする必要がありますが、統一された切り替え機能が存在しません。
トラブルシューティングのためにログファイルを扱う組み込みサポートがありません。
プレーヤー SDK のログとビジネスログが分離しており、関連付けた分析が困難です。
コアバリュー
使用方法 | 説明 | 利点 |
基本的な使用方法 | LogHub を直接使用してログを出力します。 | 設定は不要です。すぐに使用できます。 |
レベル制御 | ログレベルのフィルタリングを設定します。 | 開発用には詳細なログを、本番用には最小限のログを出力します。 |
リスナー拡張 | リスナーを登録してログを処理します。 | ログの永続化とリモートレポートを可能にします。 |
アーキテクチャの利点:
エントリポイントの統一:すべてのログが LogHub を通過するため、管理と制御が簡素化されます。
柔軟な制御:グローバルな有効/無効化、コンソールの有効/無効化、レベルフィルタリングの3段階の制御をサポートします。
拡張性:リスナーメカニズムにより、ファイルへの書き込みやリモートレポートなどのカスタムログ処理をサポートします。
スレッドセーフ:リスナーリストは CopyOnWriteArrayList を使用しており、安全に同時アクセスできます。
コア機能
機能 | 説明 |
マルチレベルロギング | VERBOSE、DEBUG、INFO、WARN、ERROR の各レベルをサポートします。 |
例外ロギング | Throwable の詳細な出力をサポートします。 |
長文ログの分割 | Logcat での切り捨てを避けるため、長文ログを自動的に分割します。 |
パフォーマンスロギング | 分析用のパフォーマンスタイミングログを提供します。 |
SDK ログの統合 | IPlayerLogger インターフェイスを介して、プレーヤー SDK のログを統一的に管理します。 |
組み込みコンポーネント
ログレベル
レベル | 値 | 説明 | シナリオ |
VERBOSE | 0 | 最も詳細なレベル。すべてのデバッグ情報を出力します。 | 開発とデバッグ |
DEBUG | 1 | デバッグ情報レベル。 | 特定の機能のデバッグ |
INFO | 2 | 情報レベル。一般的なメッセージを出力します。 | デフォルトレベル。本番環境で推奨されます。 |
WARN | 3 | 警告レベル。潜在的な問題を示します。 | 潜在的なリスクの監視 |
ERROR | 4 | エラーレベル。エラーを報告します。 | 重大な問題の監視 |
NONE | 100 | すべてのログを無効にします。 | ログ出力をオフにします。 |
コアクラス
クラス | 説明 |
| ログセンターのユーティリティクラス。ログ出力のための統一エントリポイントです。 |
| ログレベルを定義するアノテーションです。 |
| 単一ログエントリの完全な情報をカプセル化するデータクラスです。 |
| ログコールバックを受信するためのコールバックインターフェイスです。 |
| SDK のロギング機能をカプセル化する、プレーヤーのグローバルなロギングインターフェイスです。 |
| プレーヤーロガーのデフォルト実装です。 |
| プレーヤーログ用のコールバックインターフェイスです。 |
基本的な使用方法
ロギングシステムは 3 つの使用戦略を提供します。ニーズに合ったものを選択してください。
戦略 | 説明 | シナリオ |
直接的なログ出力 | 最も簡単な方法です。LogHub メソッドを直接呼び出します。 | 開発、デバッグ、または一時的なロギング |
ログレベルの設定 | 環境に基づいて異なるログレベルを設定します。 | 開発環境と本番環境の区別 |
リスナーの登録 | リスナーを使用してログ処理をカスタマイズします。 | ログの永続化またはリモートレポート |
戦略 1:直接的なログ出力
LogHub の静的メソッドを直接呼び出すことができます。
// INFO ログを出力
LogHub.i(this, "playVideo", "Start playing video");
// WARN ログを出力
LogHub.w(this, "onBuffering", "Buffering. Current progress: ", progress);
// ERROR ログを出力
LogHub.e(this, "onError", "Playback failed. Error code: ", errorCode);
// 例外を含む ERROR ログを出力
LogHub.e(this, "onException", throwable, "An exception occurred");
// パフォーマンスタイミングログを出力
LogHub.t("loadVideoSource", costTime);ログ形式:
[クラス名] メソッド名:メッセージLogcat でのフィルタリングの推奨:
package:mine tag:AliPlayerKit戦略 2:ログレベルの設定
実行環境に基づいて異なるログレベルを設定できます。
// 開発環境:すべてのログを出力
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);戦略 3:リスナーの登録
リスナーを使用してログ処理をカスタマイズできます。
// リスナーを作成
LogHubListener listener = logInfo -> {
// ログを処理
String formattedLog = logInfo.getFormattedMessage();
// ファイルへの書き込み、サーバーへのアップロードなど
saveToFile(formattedLog);
};
// リスナーを登録
LogHub.addListener(listener);
// 不要になったらリスナーを削除
LogHub.removeListener(listener); 高度な使用方法
ログの永続化を実装する方法
トラブルシューティングに役立てるため、LogHubListener インターフェイスを使用してファイルへのログ記録を実装します。
ログリスナーを作成します。
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) { // 書き込み例外は無視 } }); } }Application クラスで登録します。
public class MyApplication extends Application { private FileLogListener mFileLogListener; @Overridepublic void onCreate() { super.onCreate(); // ファイルログリスナーを作成して登録 mFileLogListener = new FileLogListener(this); LogHub.addListener(mFileLogListener); } }ログファイルを取得します。
// ログファイルのパス File logDir = new File(getExternalFilesDir(null), "logs"); File[] logFiles = logDir.listFiles(); // 分析のためにテクニカルサポートと共有
参照例: playerkit-examples/example-log-system/FileLogListener.java
プレーヤー SDK ログを統合する方法
IPlayerLogger インターフェイスを使用して、プレーヤー SDK のログを統一的に管理します。
プレーヤーロガーのインスタンスを作成します。
// Application で初期化 IPlayerLogger playerLogger = new DefaultPlayerLogger(context); // SDK コンソールログを有効化 (デバッグ時のみ有効化) playerLogger.enableConsoleLog(BuildConfig.DEBUG); // SDK のログレベルを設定 playerLogger.setLogLevel(LogLevel.INFO);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 ログ表示 | 画面上にログをリアルタイムで表示します。 |
ファイルログリスナー | ログの永続化を示します。 |
サンプルの実行
デモアプリで Log System のサンプルを選択すると、結果を確認できます。
API リファレンス
コアインターフェイス
インターフェイス/クラス | 説明 |
| ログセンター。ログ出力のための統一エントリポイントです。 |
| ログコールバックを受信するためのコールバックインターフェイスです。 |
| ログ情報のデータクラスです。 |
| SDK のロギング機能をカプセル化する、グローバルなプレーヤーロギングインターフェイスです。 |
LogHub の主要なメソッド
メソッド | 説明 |
| ログ出力を有効または無効にします (グローバルな切り替え)。 |
| コンソールログ出力を有効または無効にします。 |
| ログレベルのフィルタリングを設定します。 |
| ログリスナーを追加します。 |
| ログリスナーを削除します。 |
| 各レベルでログを出力します。 |
| パフォーマンスタイミングログを出力します。 |
LogInfo の主要なメソッド
メソッド | 説明 |
| ログレベルを取得します。 |
| ログタグを取得します。 |
| ログメッセージを取得します。 |
| 例外情報を取得します。 |
| スレッド名を取得します。 |
| タイムスタンプ (ミリ秒単位) を取得します。 |
| フォーマットされたログ文字列を取得します。 |
技術的な原則
長文ログの分割メカニズム
Logcat は各ログ行を約 4000 文字に制限しています。LogHub は長文ログを分割して処理します。
private static final int BUFFER_SIZE = 3000;
// Logcat に分割して出力
while (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/[package name]/files/logs/リスナーのコールバックはどのスレッドで実行されますか?
リスナーのコールバックは、ログを出力したスレッドと同じスレッドで実行されます。UI スレッドでログを処理するには、runOnUiThread() を使用してスレッドを切り替えます。
よくある間違い
以下は、よく報告される間違いです。これらを避けるようにしてください。
間違い 1:リスナーでのブロッキング操作によるカクつき
不正なコード:
@Overridepublic void onLog(@NonNull LogInfo logInfo) {
// メインスレッドでの同期的なファイル書き込みが UI をブロック
try {
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 が破棄されてもリスナーが残っていると、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 は内部でリスナーの例外をキャッチしますが、安定性を保証するために、リスナー側で例外を処理することが推奨されます。
デバッグ方法は?
ログの有効/無効状態を確認します:
boolean enabled = LogHub.isLogEnabled(); boolean consoleEnabled = LogHub.isConsoleLogEnabled(); int level = LogHub.getLogLevel();Logcat の出力を表示します:
tag:AliPlayerKitを使用してログをフィルタリングします。リスナーの数を確認します:
リフレクションを使用して登録されているリスナーの数を確認できます (デバッグ専用)。リスナーが正しく登録および削除されていることを確認します。