Skip to content

Commit

Permalink
Add logging to rive-ios
Browse files Browse the repository at this point in the history
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
dskuza and dskuza committed Oct 16, 2024
1 parent 4fa81c5 commit c2f8738
Show file tree
Hide file tree
Showing 19 changed files with 951 additions and 77 deletions.
2 changes: 1 addition & 1 deletion .rive_head
Original file line number Diff line number Diff line change
@@ -1 +1 @@
9d5076b88363c7811a9a70107ee43013b8bfd0cb
e1fc239974df492517f74f30a456f765d4bea96c
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,13 @@
ReferencedContainer = "container:RiveExample.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<EnvironmentVariables>
<EnvironmentVariable
key = "IDEPreferLogStreaming"
value = "YES"
isEnabled = "YES">
</EnvironmentVariable>
</EnvironmentVariables>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
Expand Down
3 changes: 3 additions & 0 deletions Example-iOS/Source/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
RenderContextManager.shared().defaultRenderer = RendererType.riveRenderer
RiveLogger.isEnabled = true
RiveLogger.categories = [.viewModel, .model]
RiveLogger.levels = [.debug]
return true
}

Expand Down
36 changes: 36 additions & 0 deletions RiveRuntime.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,15 @@
E599DCFA2AAFA06100D1E49A /* rating_animation.riv in Resources */ = {isa = PBXBuildFile; fileRef = E599DCF82AAFA06100D1E49A /* rating_animation.riv */; };
F21F08142C66526D00FFA205 /* RiveFallbackFontDescriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = F21F08132C66526D00FFA205 /* RiveFallbackFontDescriptor.swift */; };
F23626AA2C8F90FA00727D9A /* nested_text_run.riv in Resources */ = {isa = PBXBuildFile; fileRef = F23626A92C8F90FA00727D9A /* nested_text_run.riv */; };
F2610DD22CA5B4C40090D50B /* RiveLogger+StateMachine.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2610DD12CA5B4C40090D50B /* RiveLogger+StateMachine.swift */; };
F2610DD42CA5B5460090D50B /* RiveLogger+Artboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2610DD32CA5B5460090D50B /* RiveLogger+Artboard.swift */; };
F2610DD62CA5B5DD0090D50B /* RiveLogger+ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2610DD52CA5B5DD0090D50B /* RiveLogger+ViewModel.swift */; };
F2610DD82CA5B6570090D50B /* RiveLogger+Model.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2610DD72CA5B6570090D50B /* RiveLogger+Model.swift */; };
F2610DDA2CA5B84B0090D50B /* RiveLogger+File.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2610DD92CA5B84B0090D50B /* RiveLogger+File.swift */; };
F2610DE12CA5FBE30090D50B /* RiveLogger+View.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2610DE02CA5FBE30090D50B /* RiveLogger+View.swift */; };
F28DE4532C5002D900F3C379 /* RiveModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F28DE4522C5002D900F3C379 /* RiveModelTests.swift */; };
F2CCA9792C9B2799007DC0D2 /* referenced_image_asset.riv in Resources */ = {isa = PBXBuildFile; fileRef = F2CCA9782C9B2799007DC0D2 /* referenced_image_asset.riv */; };
F2CCA9C22C9E13BA007DC0D2 /* RiveLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2CCA9C12C9E13BA007DC0D2 /* RiveLogger.swift */; };
F2D285492C6D469900728340 /* RiveFallbackFontProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2D285482C6D469900728340 /* RiveFallbackFontProvider.swift */; };
F2ECC2312C666824008B20E5 /* RiveFallbackFontDescriptor+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2ECC2302C666824008B20E5 /* RiveFallbackFontDescriptor+Extensions.swift */; };
F2ECC23A2C66B949008B20E5 /* RiveFontTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2ECC2382C66B920008B20E5 /* RiveFontTests.swift */; };
Expand Down Expand Up @@ -203,8 +210,15 @@
E599DCF82AAFA06100D1E49A /* rating_animation.riv */ = {isa = PBXFileReference; lastKnownFileType = file; name = rating_animation.riv; path = "Example-iOS/Assets/rating_animation.riv"; sourceTree = SOURCE_ROOT; };
F21F08132C66526D00FFA205 /* RiveFallbackFontDescriptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RiveFallbackFontDescriptor.swift; sourceTree = "<group>"; };
F23626A92C8F90FA00727D9A /* nested_text_run.riv */ = {isa = PBXFileReference; lastKnownFileType = file; path = nested_text_run.riv; sourceTree = "<group>"; };
F2610DD12CA5B4C40090D50B /* RiveLogger+StateMachine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RiveLogger+StateMachine.swift"; sourceTree = "<group>"; };
F2610DD32CA5B5460090D50B /* RiveLogger+Artboard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RiveLogger+Artboard.swift"; sourceTree = "<group>"; };
F2610DD52CA5B5DD0090D50B /* RiveLogger+ViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RiveLogger+ViewModel.swift"; sourceTree = "<group>"; };
F2610DD72CA5B6570090D50B /* RiveLogger+Model.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RiveLogger+Model.swift"; sourceTree = "<group>"; };
F2610DD92CA5B84B0090D50B /* RiveLogger+File.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RiveLogger+File.swift"; sourceTree = "<group>"; };
F2610DE02CA5FBE30090D50B /* RiveLogger+View.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RiveLogger+View.swift"; sourceTree = "<group>"; };
F28DE4522C5002D900F3C379 /* RiveModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RiveModelTests.swift; sourceTree = "<group>"; };
F2CCA9782C9B2799007DC0D2 /* referenced_image_asset.riv */ = {isa = PBXFileReference; lastKnownFileType = file; path = referenced_image_asset.riv; sourceTree = "<group>"; };
F2CCA9C12C9E13BA007DC0D2 /* RiveLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RiveLogger.swift; sourceTree = "<group>"; };
F2D285482C6D469900728340 /* RiveFallbackFontProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RiveFallbackFontProvider.swift; sourceTree = "<group>"; };
F2ECC2302C666824008B20E5 /* RiveFallbackFontDescriptor+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RiveFallbackFontDescriptor+Extensions.swift"; sourceTree = "<group>"; };
F2ECC2382C66B920008B20E5 /* RiveFontTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RiveFontTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -370,6 +384,7 @@
C9C73ED324FC478800EF9516 /* Source */ = {
isa = PBXGroup;
children = (
F2CCA9C02C9E13B2007DC0D2 /* Logging */,
C3468E5727EB9887008652FD /* RiveView.swift */,
C3468E5927ECC7C6008652FD /* RiveViewModel.swift */,
C3468E5B27ED4C41008652FD /* RiveModel.swift */,
Expand Down Expand Up @@ -416,6 +431,20 @@
path = Fonts;
sourceTree = "<group>";
};
F2CCA9C02C9E13B2007DC0D2 /* Logging */ = {
isa = PBXGroup;
children = (
F2CCA9C12C9E13BA007DC0D2 /* RiveLogger.swift */,
F2610DD52CA5B5DD0090D50B /* RiveLogger+ViewModel.swift */,
F2610DD12CA5B4C40090D50B /* RiveLogger+StateMachine.swift */,
F2610DD32CA5B5460090D50B /* RiveLogger+Artboard.swift */,
F2610DD72CA5B6570090D50B /* RiveLogger+Model.swift */,
F2610DD92CA5B84B0090D50B /* RiveLogger+File.swift */,
F2610DE02CA5FBE30090D50B /* RiveLogger+View.swift */,
);
path = Logging;
sourceTree = "<group>";
};
/* End PBXGroup section */

/* Begin PBXHeadersBuildPhase section */
Expand Down Expand Up @@ -581,28 +610,35 @@
2A707937272628AD00C035A1 /* rive_renderer_view.mm in Sources */,
C34609FC27FF9114002DBCB7 /* RiveFile+Extensions.swift in Sources */,
C3468E5827EB9887008652FD /* RiveView.swift in Sources */,
F2610DD82CA5B6570090D50B /* RiveLogger+Model.swift in Sources */,
F2610DD62CA5B5DD0090D50B /* RiveLogger+ViewModel.swift in Sources */,
E5964A982A9697B600140479 /* RiveEvent.mm in Sources */,
F21F08142C66526D00FFA205 /* RiveFallbackFontDescriptor.swift in Sources */,
F2CCA9C22C9E13BA007DC0D2 /* RiveLogger.swift in Sources */,
043025F82AFA46EF00320F2E /* CDNFileAssetLoader.mm in Sources */,
043026042AFBA04100320F2E /* RiveFactory.mm in Sources */,
04BE5434264D267900427B39 /* LayerState.mm in Sources */,
F2610DDA2CA5B84B0090D50B /* RiveLogger+File.swift in Sources */,
C9601F2B250C25930032AA07 /* CoreGraphicsRenderer.mm in Sources */,
C9601F2B250C25930032AA07 /* CoreGraphicsRenderer.mm in Sources */,
043025F42AF90EAC00320F2E /* RiveFileAssetLoader.mm in Sources */,
F2D285492C6D469900728340 /* RiveFallbackFontProvider.swift in Sources */,
043025FC2AFA862E00320F2E /* FileAssetLoaderAdapter.mm in Sources */,
83DE4C912AA8DD7B00B88B72 /* RenderContextManager.mm in Sources */,
F2610DE12CA5FBE30090D50B /* RiveLogger+View.swift in Sources */,
C3E2B580282F242400A8651B /* RiveStateMachineInstance+Extensions.swift in Sources */,
046FB7F5264EAA60000129B1 /* RiveSMIInput.mm in Sources */,
043026002AFA915B00320F2E /* RiveFileAsset.mm in Sources */,
C3468E5A27ECC7C6008652FD /* RiveViewModel.swift in Sources */,
C3745FD3282BFAB90087F4AF /* FPSCounterView.swift in Sources */,
F2ECC2312C666824008B20E5 /* RiveFallbackFontDescriptor+Extensions.swift in Sources */,
C9C741F524FC510200EF9516 /* Rive.mm in Sources */,
F2610DD42CA5B5460090D50B /* RiveLogger+Artboard.swift in Sources */,
046FB7F8264EAA60000129B1 /* RiveStateMachineInstance.mm in Sources */,
C3468E5C27ED4C41008652FD /* RiveModel.swift in Sources */,
046FB7FF264EAA61000129B1 /* RiveFile.mm in Sources */,
046FB7F2264EAA60000129B1 /* RiveArtboard.mm in Sources */,
F2610DD22CA5B4C40090D50B /* RiveLogger+StateMachine.swift in Sources */,
046FB7F4264EAA60000129B1 /* RiveLinearAnimationInstance.mm in Sources */,
E57798A62A72C9C500FF25C3 /* RiveTextValueRun.mm in Sources */,
);
Expand Down
54 changes: 54 additions & 0 deletions Source/Logging/RiveLogger+Artboard.swift
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()): "
}
}
99 changes: 99 additions & 0 deletions Source/Logging/RiveLogger+File.swift
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()
}
}
112 changes: 112 additions & 0 deletions Source/Logging/RiveLogger+Model.swift
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 ""
}
}
}
Loading

0 comments on commit c2f8738

Please sign in to comment.