This topic introduces the core concepts, features, and usage of the AliPlayerKit logging system, LogHub.
Concepts
What is LogHub?
LogHub is the unified entry point for all log output in the PlayerKit framework. It is a static utility class that provides consistent logging functionality.
LogHub wraps the Android native Log API. It retains all original capabilities and adds the following core features:
Capabilities | Description |
Log toggle | Controls whether logs are output. |
Console toggle | Controls whether logs are output to Logcat. |
Level filtering | Outputs only logs at the specified level or higher. |
Listener mechanism | Supports external listeners for custom log handling, such as writing to files. |
What is a log listener?
LogHubListener is a callback interface that receives logs from LogHub. Implement this interface to customize how logs are handled. For example:
Write logs to a file to share with technical support during troubleshooting.
Upload logs to a remote server for centralized collection.
Display logs in the UI for a debugging panel.
Features
Problems solved
Logs are scattered across the codebase, making them hard to manage and filter.
Release builds need to disable debug logs, but no unified toggle exists.
No built-in log file support for troubleshooting.
Player SDK logs and business logs are separate, making association analysis difficult.
Core value
Usage | Description | Advantage |
Basic usage | Use LogHub directly to output logs. | No configuration required. Works out of the box. |
Level control | Set log level filtering. | Detailed logs for development. Minimal logs for production. |
Listener extension | Register a listener to handle logs. | Enables log persistence and remote reporting. |
Architectural advantages:
Unified entry: All logs flow through LogHub, simplifying management and control.
Flexible control: Supports three layers of control: global toggle, console toggle, and level filtering.
Extensible: The listener mechanism supports custom log handling, such as file writing and remote reporting.
Thread safe: The listener list uses CopyOnWriteArrayList for safe concurrent access.
Core capabilities
Capability | Description |
Multi-level logging | Supports VERBOSE, DEBUG, INFO, WARN, and ERROR levels. |
Exception logging | Supports outputting Throwable exception details. |
Long-log segmentation | Automatically segments long logs to avoid Logcat truncation. |
Performance logging | Provides performance timing logs for analysis. |
SDK log integration | Manages player SDK logs uniformly via the IPlayerLogger interface. |
Built-in components
Log levels
Level | Value | Description | Scenario |
VERBOSE | 0 | The most detailed level. Outputs all debug information. | Development and debugging. |
DEBUG | 1 | Debug information level. | Debugging specific features. |
INFO | 2 | Information level. Outputs general messages. | Default level. Recommended for production. |
WARN | 3 | Warning level. Indicates potential issues. | Monitor potential risks. |
ERROR | 4 | Error level. Reports errors. | Monitor critical issues. |
NONE | 100 | Disables all logs. | Turns off log output. |
Core classes
Class | Description |
| Utility class for the log center. The unified entry point for log output. |
| Annotation that defines log levels. |
| Data class that encapsulates complete information for a single log entry. |
| Callback interface for receiving log callbacks. |
| Global player logging interface that encapsulates SDK logging functionality. |
| Default implementation of the player logger. |
| Callback interface for player logs. |
Basic usage
The logging system offers three usage strategies. Choose the one that fits your needs.
Strategy | Description | Scenario |
Direct log output | The simplest method. Call LogHub methods directly. | Development, debugging, or temporary logging. |
Configure log level | Set different log levels based on environment. | Distinguish between development and production environments. |
Register a listener | Customize log handling using a listener. | Log persistence or remote reporting. |
Strategy 1: Direct log output
You can call LogHub static methods directly:
// Output an INFO log
LogHub.i(this, "playVideo", "Start playing video");
// Output a WARN log
LogHub.w(this, "onBuffering", "Buffering. Current progress: ", progress);
// Output an ERROR log
LogHub.e(this, "onError", "Playback failed. Error code: ", errorCode);
// Output an ERROR log with an exception
LogHub.e(this, "onException", throwable, "An exception occurred");
// Output a performance timing log
LogHub.t("loadVideoSource", costTime);Log format:
[Class name] Method name: MessageLogcat filtering suggestion:
package:mine tag:AliPlayerKitStrategy 2: Configure log level
You can set different log levels based on the runtime environment:
// Development environment: Output all logs
if (BuildConfig.DEBUG) {
LogHub.setEnableLog(true);
LogHub.setEnableConsoleLog(true);
LogHub.setLogLevel(LogLevel.VERBOSE);
}
// Production environment: Output only INFO and above
else {
LogHub.setEnableLog(true);
LogHub.setEnableConsoleLog(false); // Disable console output
LogHub.setLogLevel(LogLevel.INFO);
}
// Disable all logs (for sensitive scenarios)
LogHub.setEnableLog(false);Strategy 3: Register a listener
You can customize log handling using a listener:
// Create a listener
LogHubListener listener = logInfo -> {
// Process the log
String formattedLog = logInfo.getFormattedMessage();
// Write to file, upload to server, etc.
saveToFile(formattedLog);
};
// Register the listener
LogHub.addListener(listener);
// Remove the listener when no longer needed
LogHub.removeListener(listener); Advanced usage
How to implement log persistence?
Implement file-based logging using the LogHubListener interface to aid troubleshooting.
Create a log listener.
public class FileLogListener implements LogHubListener { private final ExecutorService mExecutor = Executors.newSingleThreadExecutor(); private final File mLogFile; public FileLogListener(Context context) { // Log file path 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) { // Write to file asynchronously to avoid blocking the calling thread 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) { // Ignore write exceptions } }); } }Register it in your Application class.
public class MyApplication extends Application { private FileLogListener mFileLogListener; @Overridepublic void onCreate() { super.onCreate(); // Create and register the file log listener mFileLogListener = new FileLogListener(this); LogHub.addListener(mFileLogListener); } }Retrieve the log file.
// Log file path File logDir = new File(getExternalFilesDir(null), "logs"); File[] logFiles = logDir.listFiles(); // Share with technical support for analysis
Example reference: playerkit-examples/example-log-system/FileLogListener.java.
How to integrate player SDK logs?
Use the IPlayerLogger interface to manage player SDK logs uniformly.
Create a player logger instance.
// Initialize in Application IPlayerLogger playerLogger = new DefaultPlayerLogger(context); // Enable SDK console logs (enable only for debugging) playerLogger.enableConsoleLog(BuildConfig.DEBUG); // Set SDK log level playerLogger.setLogLevel(LogLevel.INFO);Set the SDK log callback.
playerLogger.setLogCallback((level, message) -> { // Handle SDK logs, such as writing to file or reporting remotely LogHub.log(level, "PlayerSDK", message); });
How to display logs in the UI?
You can use a listener to show logs in real time on screen.
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);
// Create a UI log listener
mLogListener = logInfo -> runOnUiThread(() -> {
mTvLogOutput.append(logInfo.getFormattedMessage() + "\n");
});
// Register the listener
LogHub.addListener(mLogListener);
}
@Overrideprotected void onDestroy() {
super.onDestroy();
// Remove the listener to prevent memory leaks
LogHub.removeListener(mLogListener);
}
}Best practices
Selecting log levels
Scenario | Recommended level | Description |
Detailed debug information | VERBOSE | Trace workflows during development. |
Debug key points | DEBUG | Record key state changes. |
Important business information | INFO | Keep important information in production. |
Potential issue warnings | WARN | Recoverable exceptions or performance warnings. |
Critical errors | ERROR | Errors that cause feature failures. |
Production environment configuration
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
if (BuildConfig.DEBUG) {
// Development: Detailed logs + console output
LogHub.setEnableLog(true);
LogHub.setEnableConsoleLog(true);
LogHub.setLogLevel(LogLevel.VERBOSE);
} else {
// Production: Minimal logs + no console + file persistence
LogHub.setEnableLog(true);
LogHub.setEnableConsoleLog(false); // Disable Logcat output
LogHub.setLogLevel(LogLevel.INFO); // Keep only INFO and above
LogHub.addListener(new FileLogListener(this));
}
}
} Notes
Note | Description |
Avoid sensitive information | Do not log user privacy, keys, or other sensitive information. |
Asynchronous file writes | Write files asynchronously in listeners to avoid blocking. |
Handle listener exceptions | Listener exceptions must not affect normal log output. |
Remove listeners promptly | Remove listeners when pages are destroyed to prevent memory leaks. |
Global listener location | Place file log listeners in the Application class to ensure full-lifecycle collection. |
Examples
The project includes a complete example at playerkit-examples/example-log-system.
Example features
Feature | Description |
Log output demo | Demonstrates log output at each level. |
UI log display | Displays logs in real time on screen. |
File log listener | Demonstrates log persistence. |
Running Example
In the Demo App, select the Log System example to view the results.
API reference
Core interfaces
Interface/class | Description |
| Log center. The unified entry point for log output. |
| Callback interface for receiving log callbacks. |
| Data class for log information. |
| Global player logging interface. |
Main LogHub methods
Method | Description |
| Enable or disable log output (global toggle). |
| Enable or disable console log output. |
| Set log level filtering. |
| Add a log listener. |
| Remove a log listener. |
| Output logs at each level. |
| Output performance timing logs. |
Main LogInfo methods
Method | Description |
| Get the log level. |
| Get the log tag. |
| Get the log message. |
| Get the exception information. |
| Get the thread name. |
| Get the timestamp (in milliseconds). |
| Get the formatted log string. |
Technical principles
Long-log segmentation
Logcat limits each log line to about 4000 characters. LogHub handles long logs by segmenting them:
private static final int BUFFER_SIZE = 3000;
// Segment and output to Logcatwhile (startIndex < length) {
int endIndex = Math.min(length, startIndex + BUFFER_SIZE);
String sub = s.substring(startIndex, endIndex);
// Output segmented log...
startIndex = endIndex;
}Optimization: Send full logs to listeners. Segment only for Logcat output.
Thread safety
The listener list uses CopyOnWriteArrayList to ensure thread safety:
private static final List<LogHubListener> sListeners = new CopyOnWriteArrayList<>();Characteristics:
Read operations require no locks. Performance is high.
Write operations copy a new array. Ongoing reads are unaffected.
Ideal for listener registration and removal, where reads far outnumber writes.
FAQ
How do I filter PlayerKit logs in Logcat?
Use this filter:
package:mine tag:AliPlayerKitOr filter by level:
package:mine tag:AliPlayerKit level:info Where are production environment log files stored?
Default path (if using the FileLogListener example):
/sdcard/Android/data/[package name]/files/logs/Which thread runs listener callbacks?
Listener callbacks run on the same thread that outputs the log. To process logs on the UI thread, use runOnUiThread() to switch threads.
Common mistakes
These are the most frequent issues reported by customers. Avoid them:
Mistake 1: Blocking operations in listeners cause stuttering
Incorrect code:
@Overridepublic void onLog(@NonNull LogInfo logInfo) {
// Synchronous file write on main thread blocks the UItry {
FileOutputStream fos = new FileOutputStream(mLogFile, true);
fos.write(logInfo.getFormattedMessage().getBytes());
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
Correct code:
private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
@Overridepublic void onLog(@NonNull LogInfo logInfo) {
// Write to file asynchronously
mExecutor.execute(() -> {
try {
// File write logic...
} catch (IOException e) {
// Ignore
}
});
}
Root cause: If logs are output on the main thread, synchronous file writes block the UI. This causes stuttering or ANR.
Mistake 2: Forgetting to remove listeners causes memory leaks
Incorrect code:
@Overrideprotected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Registered a listener but never removed it
LogHub.addListener(logInfo -> {
mTvLogOutput.append(logInfo.getMessage());
});
}
Correct code:
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();
// Remove the listener promptlyif (mLogListener != null) {
LogHub.removeListener(mLogListener);
}
}
Root cause: The listener holds a reference to the Activity. If the Activity is destroyed but the listener remains, the Activity cannot be garbage collected. This causes a memory leak.
Mistake 3: Listener exceptions affect other listeners
Incorrect code:
@Overridepublic void onLog(@NonNull LogInfo logInfo) {
// Unhandled exception. If thrown here, it affects other listeners
String data = logInfo.getMessage();
int result = 1 / 0; // Throws exception
}
Correct code:
@Overridepublic void onLog(@NonNull LogInfo logInfo) {
try {
// Handle exceptions inside the listener
processLog(logInfo);
} catch (Exception e) {
// Handle listener-specific exceptions without affecting the logging system
android.util.Log.e("FileLogListener", "Error processing log", e);
}
}Root cause: Although LogHub catches listener exceptions internally, listeners should handle their own exceptions to guarantee stability.
How do I debug?
Check log toggle status:
boolean enabled = LogHub.isLogEnabled(); boolean consoleEnabled = LogHub.isConsoleLogEnabled(); int level = LogHub.getLogLevel();View Logcat output:
Filter logs using
tag:AliPlayerKit.Check listener count:
You can inspect the number of registered listeners using reflection (for debugging only). Verify listeners are registered and removed correctly.