-
Notifications
You must be signed in to change notification settings - Fork 68
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adds (structured) logging via the iOS `Logger` API. Under the hood, this uses `os_log` for both in-memory and on-disk logging, depending on which level is used; this means _where_ is handled by the system, we just provide the level. The API is a little interesting; you can't have a "generic" `log` function that takes in the message. iOS requires that you use interpolation when logging. Logging is structured so that various categories are set under one subsystem. These categories are: view model, state machine, artboard, file, and view. Each of these can log one of debug, info, default, error, and fault levels. The developer can filter which categories and levels can be logged; Xcode also supports filtering within the console. Logging itself is split into three things: the categories, the levels, and the logging. Within each "category" of logging, there exist events that can be logged. These are enums with associated values. When using Objective-C, there are helper functions that under-the-hood call logging functions with these events. Since there are a few categories, and various events for each category, these categories are split into extensions on `RiveLogger`. At the end of the day, there exists a single log function, which ensures a log category and level are available for logging, and then a log function that is essentially a switch statement on each event, logging the (interpolated) message. These logging events are then utilized in the files mentioned above; the categories match the files where logging has been added. Primarily setters are called, or errors that may not be handled, but may be useful. Fatal errors are also logged. When adding new logging: 1. Check if an existing extension exists. If not, create one. 2. Create an enum of possible events. 3. Create a `log` function that takes the model, and the event. 4. Create a `_log` function that verifies that an event has been called. ## Example usage ```swift // Somewhere early in the app lifecycle… RiveLogger.isEnabled = true RiveLogger.isVerbose = true // advances are considered verbose RiveLogger.levels = [.fatal] // filter for only specific levels, such as fatal errors RiveLogger.categories = [.stateMachine, .viewModel] // filter for only specific categories ``` Diffs= e1fc239974 Add logging to rive-ios (#8252) Co-authored-by: David Skuza <[email protected]>
- Loading branch information
Showing
19 changed files
with
951 additions
and
77 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
9d5076b88363c7811a9a70107ee43013b8bfd0cb | ||
e1fc239974df492517f74f30a456f765d4bea96c |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
// | ||
// RiveLogger+Artboard.swift | ||
// RiveRuntime | ||
// | ||
// Created by David Skuza on 9/26/24. | ||
// Copyright © 2024 Rive. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
import OSLog | ||
|
||
enum RiveLoggerArtboardEvent { | ||
case advance(Double) | ||
case error(String) | ||
} | ||
|
||
extension RiveLogger { | ||
private static let artboard = Logger(subsystem: subsystem, category: "rive-artboard") | ||
|
||
@objc(logArtboard:advance:) static func log(artboard: RiveArtboard, advance: Double) { | ||
log(artboard: artboard, event: .advance(advance)) | ||
} | ||
|
||
@objc(logArtboard:error:) static func log(artboard: RiveArtboard, error: String) { | ||
log(artboard: artboard, event: .error(error)) | ||
} | ||
|
||
static func log(artboard: RiveArtboard, event: RiveLoggerArtboardEvent) { | ||
switch event { | ||
case .advance(let elapsed): | ||
guard isVerbose else { return } | ||
_log(event: event, level: .debug) { | ||
Self.artboard.debug("\(self.prefix(for: artboard))Advanced by \(elapsed)s") | ||
} | ||
case .error(let error): | ||
_log(event: event, level: .error) { | ||
Self.artboard.error("\(error)") | ||
} | ||
} | ||
} | ||
|
||
private static func _log(event: RiveLoggerArtboardEvent, level: RiveLogLevel, log: () -> Void) { | ||
guard isEnabled, | ||
categories.contains(.artboard), | ||
levels.contains(level) | ||
else { return } | ||
|
||
log() | ||
} | ||
|
||
private static func prefix(for artboard: RiveArtboard) -> String { | ||
return "\(artboard.name()): " | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
// | ||
// RiveLogger+File.swift | ||
// RiveRuntime | ||
// | ||
// Created by David Skuza on 9/26/24. | ||
// Copyright © 2024 Rive. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
import OSLog | ||
|
||
enum RiveLoggerFileEvent { | ||
case fatalError(String) | ||
case error(String) | ||
case loadingAsset(RiveFileAsset) | ||
case loadedFontAssetFromURL(URL, RiveFontAsset) | ||
case loadedImageAssetFromURL(URL, RiveImageAsset) | ||
case loadedAsset(RiveFileAsset) | ||
case loadedFromURL(URL) | ||
case loadingFromResource(String) | ||
} | ||
|
||
extension RiveLogger { | ||
private static let file = Logger(subsystem: subsystem, category: "rive-file") | ||
|
||
@objc(logFile:error:) static func log(file: RiveFile?, error message: String) { | ||
log(file: file, event: .error(message)) | ||
} | ||
|
||
@objc(logLoadingAsset:) static func log(loadingAsset asset: RiveFileAsset) { | ||
log(file: nil, event: .loadingAsset(asset)) | ||
} | ||
|
||
@objc(logFontAssetLoad:fromURL:) static func log(fontAssetLoad fontAsset: RiveFontAsset, from url: URL) { | ||
log(file: nil, event: .loadedFontAssetFromURL(url, fontAsset)) | ||
} | ||
|
||
@objc(logImageAssetLoad:fromURL:) static func log(imageAssetLoad imageAsset: RiveImageAsset, from url: URL) { | ||
log(file: nil, event: .loadedImageAssetFromURL(url, imageAsset)) | ||
} | ||
|
||
@objc(logAssetLoaded:) static func log(assetLoaded asset: RiveFileAsset) { | ||
log(file: nil, event: .loadedAsset(asset)) | ||
} | ||
|
||
@objc(logLoadedFromURL:) static func log(loadedFromURL url: URL) { | ||
log(file: nil, event: .loadedFromURL(url)) | ||
} | ||
|
||
@objc(logLoadingFromResource:) static func log(loadingFromResource name: String) { | ||
log(file: nil, event: .loadingFromResource(name)) | ||
} | ||
|
||
static func log(file: RiveFile?, event: RiveLoggerFileEvent) { | ||
switch event { | ||
case .fatalError(let message): | ||
_log(event: event, level: .fault) { | ||
Self.file.fault("\(message)") | ||
} | ||
case .error(let message): | ||
_log(event: event, level: .error) { | ||
Self.file.error("\(message)") | ||
} | ||
case .loadingAsset(let asset): | ||
_log(event: event, level: .debug) { | ||
Self.file.debug("Loading asset \(asset.name())") | ||
} | ||
case .loadedAsset(let asset): | ||
_log(event: event, level: .debug) { | ||
Self.file.debug("Loaded asset \(asset.name())") | ||
} | ||
case .loadedFontAssetFromURL(let url, let asset): | ||
_log(event: event, level: .debug) { | ||
Self.file.debug("Loaded font asset \(asset.name()) from URL: \(url)") | ||
} | ||
case .loadedImageAssetFromURL(let url, let asset): | ||
_log(event: event, level: .debug) { | ||
Self.file.debug("Loaded image asset \(asset.name()) from URL: \(url)") | ||
} | ||
case .loadedFromURL(let url): | ||
_log(event: event, level: .debug) { | ||
Self.file.debug("Loaded file \(url)") | ||
} | ||
case .loadingFromResource(let name): | ||
_log(event: event, level: .debug) { | ||
Self.file.debug("Loading resource \(name)") | ||
} | ||
} | ||
} | ||
|
||
private static func _log(event: RiveLoggerFileEvent, level: RiveLogLevel, log: () -> Void) { | ||
guard isEnabled, | ||
categories.contains(.file), | ||
levels.contains(level) | ||
else { return } | ||
|
||
log() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
// | ||
// RiveLogger+Model.swift | ||
// RiveRuntime | ||
// | ||
// Created by David Skuza on 9/26/24. | ||
// Copyright © 2024 Rive. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
import OSLog | ||
|
||
enum RiveLoggerModelEvent { | ||
case volume(Float) | ||
case artboardByName(String) | ||
case artboardByIndex(Int) | ||
case defaultArtboard | ||
case error(String) | ||
case stateMachineByName(String) | ||
case stateMachineByIndex(Int) | ||
case defaultStateMachine | ||
case animationByName(String) | ||
case animationByIndex(Int) | ||
} | ||
|
||
extension RiveLogger { | ||
private static let model = Logger(subsystem: subsystem, category: "rive-model") | ||
|
||
static func log(model: RiveModel, event: RiveLoggerModelEvent) { | ||
switch event { | ||
case .volume(let volume): | ||
_log(event: event, level: .debug) { | ||
Self.model.debug( | ||
"\(self.prefix(for: model))Volume set to \(volume)" | ||
) | ||
} | ||
case .artboardByName(let name): | ||
_log(event: event, level: .debug) { | ||
Self.model.debug( | ||
"\(self.prefix(for: model))Artboard set to artboard named \(name)" | ||
) | ||
} | ||
case .artboardByIndex(let index): | ||
_log(event: event, level: .debug) { | ||
Self.model.debug( | ||
"\(self.prefix(for: model))Artboard set to artboard at index \(index)" | ||
) | ||
} | ||
case .defaultArtboard: | ||
_log(event: event, level: .debug) { | ||
Self.model.debug( | ||
"\(self.prefix(for: model))Artboard set to default artboard" | ||
) | ||
} | ||
case .error(let message): | ||
_log(event: event, level: .debug) { | ||
Self.model.debug( | ||
"\(self.prefix(for: model))\(message)" | ||
) | ||
} | ||
case .stateMachineByName(let name): | ||
_log(event: event, level: .debug) { | ||
Self.model.debug( | ||
"\(self.prefix(for: model))State machine set to state machine named \(name)" | ||
) | ||
|
||
} | ||
case .stateMachineByIndex(let index): | ||
_log(event: event, level: .debug) { | ||
Self.model.debug( | ||
"\(self.prefix(for: model))State machine set to state machine at index \(index)" | ||
) | ||
} | ||
case .defaultStateMachine: | ||
_log(event: event, level: .debug) { | ||
Self.model.debug( | ||
"\(self.prefix(for: model))State machine set to default state machine" | ||
) | ||
} | ||
case .animationByName(let name): | ||
_log(event: event, level: .debug) { | ||
Self.model.debug( | ||
"\(self.prefix(for: model))Animation set to animation named \(name)" | ||
) | ||
} | ||
case .animationByIndex(let index): | ||
_log(event: event, level: .debug) { | ||
Self.model.debug( | ||
"\(self.prefix(for: model))Animation set to animation at index \(index)" | ||
) | ||
} | ||
} | ||
} | ||
|
||
private static func _log(event: RiveLoggerModelEvent, level: RiveLogLevel, log: () -> Void) { | ||
guard isEnabled, | ||
categories.contains(.model), | ||
levels.contains(level) | ||
else { return } | ||
|
||
log() | ||
} | ||
|
||
private static func prefix(for model: RiveModel) -> String { | ||
if let stateMachine = model.stateMachine { | ||
return "[\(stateMachine.name())]: " | ||
} else if let animation = model.animation { | ||
return "[\(animation.name())]: " | ||
} else { | ||
return "" | ||
} | ||
} | ||
} |
Oops, something went wrong.