Logging in the iOS & tvOS Player SDK

Troubleshooting streams on mobile devices can be challenging, especially for issues that occur rarely and only in specific circumstances. A common approach in such a scenario is to add log statements to the code in order to observe configurations and states at the time of the failure.

Built-in logging

The Bitmovin Player for iOS/tvOS has built-in logging support to aid such use cases.

Enabling or Disabling logging

Logging is enabled by default with level .warning, printing entries to the console.

Logging can be disabled by setting the logger to nil:

DebugConfig.logging.logger = nil

Default logger can be restored by:

DebugConfig.logging.logger = ConsoleLogger()

Configuration

Log levels

Our SDK supports multiple log levels to allow gathering insights with fine-grained control:

LevelDescription
.verboseProvides detailed information, primarily for debugging purposes.
.infoIndicates something has happened and is purely informative.
.warningIndicates something unexpected happened, however, the player can continue to function.
.errorIndicates an issue that disrupts the proper functioning of the player.

📘

Log levels are hierarchical

This means that selecting a certain level also includes logs with higher levels.

For example, choosing the .info level will include logs with levels .info, .warning, and .error.

On the other hand, selecting the.warning level will only include logs with levels .warning and .error.

The current logger's log level can be changed by simply assigning a new level:

DebugConfig.logging.logger?.level = .info

Exhaustive debug logs

To enable exhaustive debug logging across the whole Player SDK, use the following line of code:

DebugConfig.logging.logger?.level = .verbose

Advanced logger support

We support custom logger implementations via the Logger protocol.

Logger instances get LogEntry objects delivered by the Bitmovin Player.

📘

Log level support within custom Logger implementations

Custom loggers need to respect the set log level themselves.

All produced LogEntry objects will be delivered to Logger implementations and it is the task of the custom logger itself to filter undesired entries.

Custom logger example

The below example of a custom logger implementation uses the Logger from Apple's Logging framework:

import BitmovinPlayer
import OSLog

class AppleLogger: BitmovinPlayer.Logger {
    typealias OSLogger = os.Logger

    var level: LogLevel = .warning

    private let logger = OSLogger(subsystem: "BitmovinPlayer", category: "video playback")

    func log(_ logEntry: LogEntry) {
        guard logEntry.level.rawValue >= level.rawValue else { return }

        switch logEntry.level {
        case .info:
            logger.info("[\(logEntry.sender)] \(logEntry.message)")
        case .warning:
            logger.warning("[\(logEntry.sender)] \(logEntry.message) Reason: \(logEntry.data?.message ?? "N/A")")
        case .error:
            logger.error("[\(logEntry.sender)] \(logEntry.message) Reason: \(logEntry.data?.message ?? "N/A")")
        @unknown default:
            break
        }
    }
}

This logger would produce log messages such as:

2023-06-14 08:53:35.461189+0200 YOUR-APP-NAME [video playback] [Source] Downloading playlist finished with error. Reason: Invalid response received from manifest request. Status code: 404