The Log System is a fundamental module of AliPlayerKit. It uses a unified log center, LogHub, to provide core features such as log output, level filtering, and an observer mechanism. This provides visibility and traceability for player runtime information.
Concepts
What is LogHub?
LogHub is the unified entry point for all log output in AliPlayerKit. It is designed with the singleton pattern. LogHub encapsulates the native iOS log API, NSLog, and adds the following core features:
Capabilities | Description |
Log switch | Controls whether log output is enabled (master switch). |
Console switch | Controls whether logs are output to the Xcode Console. |
Level filtering | Outputs only logs of a specified level or higher. |
Observer mechanism | Lets external observers receive log output for custom processing, such as writing to a file. |
What is a log observer?
A log observer (LogObserver) is a callback protocol used to receive logs from LogHub. By implementing this protocol, you can customize how logs are processed. For example:
Write logs to a file for the technical support team to use for troubleshooting.
Upload logs to a remote server to enable remote log collection.
Display logs in the UI to create a debug panel.
Hybrid thread model
LogHub uses a hybrid thread model:
Operation | Thread |
Thread name and timestamp capture | Synchronous execution in the calling thread |
Console output (NSLog) | Synchronous execution in the calling thread |
Observer notification | Asynchronous dispatch to a background serial queue |
The LogObserver.onLog: callback is executed in a background serial queue. You must switch to the main thread to update the UI.
Features
Core features
Capabilities | Description |
Multi-level logging | Supports five levels: Verbose, Debug, Info, Warn, and Error. |
Triple control | Global switch, level filtering, and console switch for precise control over the output scope. |
Observer mechanism | Lets you register custom observers for log persistence, remote reporting, and more. |
Helper macros | Provides |
Log panel | Displays logs in real time within the player view using |
Global shortcut API | Quickly configure log levels and console output using |
Core components
Component | Type | Description |
| Singleton | Log center, the unified entry point for log output. |
| Enumeration | Log level definition. |
| Data class | Log information model that encapsulates the complete information of a single log entry. |
| Protocol | Log observer interface. |
| Slot | An embedded log panel in the player that displays key logs in real time. |
Usage
Basic usage: Global quick configuration
You can quickly configure the log system using the recommended AliPlayerKit class methods:
// In AppDelegate, configure after setup
[AliPlayerKit setup];
// Set the log level
[AliPlayerKit setLogLevel:LogLevelVerbose];
// Enable or disable console logs
[AliPlayerKit enableConsoleLog:YES];AliPlayerKit method | Description |
| Sets the log level. Logs below this level are ignored. The default is |
| Gets the current log level. |
| Enables or disables console output. Enabled by default. |
| Checks if console logging is enabled. |
| Enables or disables debug mode. |
| Checks if debug mode is enabled. |
| Enables or disables the log panel slot. Disabled by default. |
| Checks if the log panel is enabled. |
Basic usage: Log output
You can use the SLOG series of helper macros to output logs at different levels. These macros automatically capture the source file name and line number:
#import <AliPlayerKit/LogMacros.h>
// Output logs at different levels (automatically captures __FILE_NAME__ and __LINE__)
SLOGV(@"viewDidLoad called");
SLOGD(@"Configure player: videoId=%@", videoId);
SLOGI(@"Start playing video: %@", videoTitle);
SLOGW(@"Buffering, current progress: %.1f%%", progress * 100);
SLOGE(@"Playback failed, error code: %ld", (long)errorCode);List of helper macros:
Macro | Level | Description |
| Verbose | Most detailed log level. |
| Debug | Debug information. |
| Info | General information. |
| Warn | Potential issues. |
| Error | Error messages. |
The SLOG macros call the LogHub logLevel:file:line:message:error: method. They automatically capture the source code location using __FILE_NAME__ and __LINE__, so you do not need to manually pass a tag.
You can also call LogHub instance methods directly and specify a tag manually:
// Use a tag to identify the log source
[[LogHub sharedHub] i:@"PlayerVC" msg:@"Start playback"];
[[LogHub sharedHub] e:@"MediaPlayer" msg:@"Playback failed" error:error];Basic usage: Configure log levels
You can configure log properties directly through LogHub:
LogHub *logHub = [LogHub sharedHub];
#ifdef DEBUG
// Development environment: Verbose logs + console output
logHub.logEnabled = YES;
logHub.consoleLogEnabled = YES;
logHub.logLevel = LogLevelVerbose;
#else
// Production environment: Concise logs + console disabled
logHub.logEnabled = YES;
logHub.consoleLogEnabled = NO;
logHub.logLevel = LogLevelInfo;
#endifTriple control mechanism:
Control layer | Property | Description |
Master switch |
| When disabled, all logging is silenced, including observer notifications. |
Level filtering |
| Logs below this level are ignored. The default is |
Console switch |
| Controls whether logs are output to the Xcode Console using NSLog. |
If you disable consoleLogEnabled, logs are still dispatched through observer notifications, but the console output is suppressed. If you disable logEnabled, all log requests are ignored, and observers do not receive notifications.
Advanced usage: Register a log observer
You can customize log processing by implementing the LogObserver protocol:
// Create and register a file log observer
FileLogObserver *fileObserver = [[FileLogObserver alloc] init];
[[LogHub sharedHub] addObserver:fileObserver];
// Remove the observer when it is no longer needed
[[LogHub sharedHub] removeObserver:fileObserver];LogHub maintains a strong reference to observers until they are explicitly removed using removeObserver:. You must call removeObserver: at an appropriate time, such as in dealloc or viewWillDisappear:, to prevent circular references.
Advanced usage: Log panel
LogPanelSlot is a built-in debug slot that displays key log information in real time within the player view:
// Enable the log panel (recommended only for debugging scenarios)
[AliPlayerKit setLogPanelEnabled:YES];Interaction | Description |
Click the title bar | Expands or collapses the log content area. |
Click the "Clear" button | Clears the displayed logs. |
Long-press the log area | Copies the log content to the clipboard. |
The log panel displays only logs with a level of Debug or higher. You must call setLogPanelEnabled: before you call rebuildSlots. The panel is not visible in the Minimal scenario.
Custom development
LogObserver protocol
@protocol LogObserver <NSObject>
@required
/// Log output callback
/// @param logInfo The log information object, which contains the level, tag, message, and timestamp.
/// @warning This callback is executed in a background serial queue. Dispatch to the main thread to update the UI.
- (void)onLog:(LogInfo *)logInfo;
@endLogInfo data model
Property | Type | Description |
|
| Log level (readonly). |
|
| Log tag (readonly). |
|
| Log message content (readonly). |
|
| Timestamp when the log was generated (readonly). |
|
| Name of the thread that generated the log (readonly). |
|
| Source file name, such as |
|
| Source code line number (readonly). |
|
| Associated error object, which can be nil (readonly). |
Method | Description |
| Gets the formatted log string. |
| Designated initializer. |
| Convenience initializer that automatically captures the timestamp and thread name. |
Formatted output format:
2024-04-22 10:30:00.123 I/AliPlayerKit [version] [FileName:line] [threadName] : messageIf an error exists, an extra line is appended:
Error: domain=xxx code=xxx userInfo=xxxExample: Custom file log observer
The following is a complete implementation of a file log observer:
@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) {
// Ignore write exceptions
}
});
}
- (void)dealloc {
[_fileHandle closeFile];
}
@endThe onLog: callback is executed in a background serial queue. However, for file I/O operations, we recommend that you use a separate write queue to prevent blocking the log dispatch queue. Blocking the queue could affect other observers.
Example: Full integration
// AppDelegate.m
#import <AliPlayerKit/AliPlayerKit.h>
@interface AppDelegate ()
@property (nonatomic, strong) MyFileLogObserver *fileLogObserver;
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// 1. Initialize AliPlayerKit
[AliPlayerKit setup];
// 2. Configure the log system
#ifdef DEBUG
[AliPlayerKit setLogLevel:LogLevelVerbose];
[AliPlayerKit enableConsoleLog:YES];
// Optional: Enable the log panel
[AliPlayerKit setLogPanelEnabled:YES];
#else
[AliPlayerKit setLogLevel:LogLevelInfo];
[AliPlayerKit enableConsoleLog:NO];
// 3. Register a file log observer (production environment only)
NSString *logDir = [NSSearchPathForDirectoriesInDomains(
NSDocumentDirectory, NSUserDomainMask, YES).firstObject
stringByAppendingPathComponent:@"PlayerLogs"];
self.fileLogObserver = [[MyFileLogObserver alloc] initWithLogDirectory:logDir];
[[LogHub sharedHub] addObserver:self.fileLogObserver];
#endif
return YES;
}
@endThe PlayerKitUsages/UsageLogSystem module provides a complete example of how to use the log system. The example includes a FileLogObserver and a UI for displaying logs.
Best practices
Environment configuration recommendations
Environment | Master Switch | Console | Level | Observer | Log Panel |
Development and debugging | YES | YES | Verbose | Optional (UI panel) | Optional |
Internal testing | YES | YES | Debug | File logging | NO |
Production environment | YES | NO | Info | File logging + remote reporting | NO |
Sensitive scenarios | NO | — | — | — | NO |
Common errors
Error 1: Updating the UI directly in the observer callback
// ✗ Incorrect: onLog: is executed in a background serial queue. Updating the UI directly will cause a crash.
- (void)onLog:(LogInfo *)logInfo {
self.logLabel.text = [logInfo formattedString]; // Crash!
}
// ✓ Correct: Switch to the main thread to update the UI.
- (void)onLog:(LogInfo *)logInfo {
NSString *formatted = [logInfo formattedString];
dispatch_async(dispatch_get_main_queue(), ^{
self.logLabel.text = formatted;
});
}Error 2: Synchronous blocking operations in the observer
// ✗ Incorrect: Synchronously writing to a file in the callback blocks the log dispatch queue.
- (void)onLog:(LogInfo *)logInfo {
NSString *log = [logInfo formattedString];
[log writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:nil];
}
// ✓ Correct: Asynchronously write to the file.
- (void)onLog:(LogInfo *)logInfo {
NSString *log = [logInfo formattedString];
dispatch_async(_writeQueue, ^{
// Asynchronous write...
});
}Error 3: Forgetting to remove the observer, which causes a circular reference
// ✗ Incorrect: LogHub strongly references the observer. Failure to remove it causes a circular reference.
- (void)viewDidLoad {
[[LogHub sharedHub] addObserver:self];
// Never released...
}
// ✓ Correct: Remove the observer at the end of the lifecycle.
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[[LogHub sharedHub] removeObserver:self];
}Unlike the notification system's Dispatcher, LogHub maintains a strong reference to observers. If you do not call removeObserver:, the observer is not released.
API reference
Log levels (LogLevel)
Level | Enumeration value | Numeric value | Description |
Verbose |
| 0 | Most detailed, for development and debugging. |
Debug |
| 1 | Debug information. |
Info |
| 2 | Default level, recommended for production. |
Warn |
| 3 | Potential issues. |
Error |
| 4 | Error messages. |
None |
| 100 | Disables all logs. |
LogLevel.h also provides the LogLevelToString() inline function, which converts a log level to an abbreviated string (V, D, I, W, E, or N).
LogHub core API
Property / Method | Type | Description |
| Class method | Gets the singleton instance. |
|
| Master log switch (default: YES). |
|
| Log level (default: |
|
| Console switch (default: YES). |
| Instance method | Outputs a Verbose log. |
| Instance method | Outputs a Debug log. |
| Instance method | Outputs an Info log. |
| Instance method | Outputs a Warn log. |
| Instance method | Outputs an Error log. |
| Instance method | Outputs an Error log with an |
| Instance method | Outputs a log at the specified level. |
| Instance method | Core logging method (used by SLOG macros). |
| Instance method | Adds an observer (strong reference, no effect if added repeatedly). |
| Instance method | Removes an observer. |
| Class method | Converts a level to a string (V/D/I/W/E/N). |
| Class method | Converts a string to a level (case-insensitive, returns |
AliPlayerKit log shortcut API
Method | Description |
| Sets the log level (must be called after |
| Gets the current log level. |
| Enables or disables console log output. |
| Checks if console logging is enabled. |
| Enables or disables debug mode. |
| Checks if debug mode is enabled. |
| Enables or disables the log panel slot. |
| Checks if the log panel is enabled. |
SLOG helper macros
Macro | Level | Expanded form |
| Verbose |
|
| Debug | Same as above, with level Debug. |
| Info | Same as above, with level Info. |
| Warn | Same as above, with level Warn. |
| Error | Same as above, with level Error. |
LogInfo properties
Property | Type | Description |
|
| Log level. |
|
| Log tag. |
|
| Log message content. |
|
| Timestamp when the log was generated. |
|
| Thread name. |
|
| Source file name. |
|
| Source code line number. |
|
| Associated error object (nullable). |
| Method | Gets the formatted log string. |
LogObserver protocol
Method | Required | Description |
| Yes ( | Log output callback, executed in a background serial queue. |