Logging System

更新时间:
复制 MD 格式

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

LogHub

Utility class for the log center. The unified entry point for log output.

LogLevel

Annotation that defines log levels.

LogInfo

Data class that encapsulates complete information for a single log entry.

LogHubListener

Callback interface for receiving log callbacks.

IPlayerLogger

Global player logging interface that encapsulates SDK logging functionality.

DefaultPlayerLogger

Default implementation of the player logger.

LoggerCallback

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: Message

Logcat filtering suggestion:

package:mine tag:AliPlayerKit

Strategy 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.

  1. 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
                }
            });
        }
    }
  2. 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);
        }
    }
  3. 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.

  1. 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);
  2. 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

LogHub

Log center. The unified entry point for log output.

LogHubListener

Callback interface for receiving log callbacks.

LogInfo

Data class for log information.

IPlayerLogger

Global player logging interface.

Main LogHub methods

Method

Description

setEnableLog(boolean)

Enable or disable log output (global toggle).

setEnableConsoleLog(boolean)

Enable or disable console log output.

setLogLevel(int)

Set log level filtering.

addListener(LogHubListener)

Add a log listener.

removeListener(LogHubListener)

Remove a log listener.

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

Output logs at each level.

t(String, long)

Output performance timing logs.

Main LogInfo methods

Method

Description

getLevel()

Get the log level.

getTag()

Get the log tag.

getMessage()

Get the log message.

getThrowable()

Get the exception information.

getThreadName()

Get the thread name.

getTimestamp()

Get the timestamp (in milliseconds).

getFormattedMessage()

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:AliPlayerKit

Or 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?

  1. Check log toggle status:

    boolean enabled = LogHub.isLogEnabled();
    boolean consoleEnabled = LogHub.isConsoleLogEnabled();
    int level = LogHub.getLogLevel();
  2. View Logcat output:

    Filter logs using tag:AliPlayerKit.

  3. Check listener count:

    You can inspect the number of registered listeners using reflection (for debugging only). Verify listeners are registered and removed correctly.