Skip to content

Commit

Permalink
[feat]: external logging API
Browse files Browse the repository at this point in the history
  • Loading branch information
jamieQ committed Jan 10, 2024
1 parent 132567f commit 7bf5a6c
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 4 deletions.
70 changes: 70 additions & 0 deletions Workflow/Sources/ExternalLogging.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import Foundation

/// Namespace for logging API used to propagate internal Workflow-related logging to external consumers
public enum ExternalLogging {}

extension ExternalLogging {
/// Log level indicating 'severity' of the corresponding `LogEvent`
public enum LogLevel {
case info
case error
}

/// A log event
public struct LogEvent {
public let message: String
public let level: LogLevel
}

/// Wrapper that allows for propagating log events to outside consumers.
internal struct ExternalLogger {
private let implementation: (LogEvent) -> Void

internal init(_ implementation: @escaping (LogEvent) -> Void) {
self.implementation = implementation
}

internal func log(_ payload: LogEvent) { implementation(payload) }
}

/// Shared external logger variable
internal static var logger: ExternalLogger?

/// External logging bootstrapping method.
/// Call once with the desired log handler.
/// - Parameter logHandler: Callback to handle logging events.
public static func configure(
_ logHandler: @escaping (LogEvent) -> Void
) {
assert(
logger == nil,
"Workflow external logger already configured."
)

logger = ExternalLogger(logHandler)
}
}

extension ExternalLogging.LogEvent {
/// Convenience to create an info-level `LogEvent`
static func info(_ message: String) -> Self {
.init(message: message, level: .info)
}

/// Convenience to create an error-level `LogEvent`
static func error(_ message: String) -> Self {
.init(message: message, level: .error)
}
}

extension ExternalLogging {
// Logs an info message via the global logger (if set)
static func logInfo(_ message: @autoclosure () -> String) {
logger?.log(.info(message()))
}

// Logs an error message via the global logger (if set)
static func logError(_ message: @autoclosure () -> String) {
logger?.log(.error(message()))
}
}
8 changes: 7 additions & 1 deletion Workflow/Sources/RenderContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,13 @@ public class RenderContext<WorkflowType: Workflow>: RenderContextType {
}

private func assertStillValid() {
assert(isValid, "A `RenderContext` instance was used outside of the workflow's `render` method. It is a programmer error to capture a context in a closure or otherwise cause it to be used outside of the `render` method.")
guard isValid else {
ExternalLogging.logError("""
Detected an attempt to use an invalidated RenderContext for a workflow of type \(WorkflowType.self).
""")
assertionFailure("A `RenderContext` instance for a workflow of type \(WorkflowType.self) was used outside of the workflow's `render` method. It is a programmer error to capture a context in a closure or otherwise cause it to be used outside of the `render` method.")
return
}
}
}
}
Expand Down
10 changes: 7 additions & 3 deletions Workflow/Sources/SubtreeManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -373,9 +373,13 @@ extension WorkflowNode.SubtreeManager {
// If we're invalid and this is the first time `handle()` has
// been called, then it's likely we've somehow been inadvertently
// retained from the 'outside world'. Fail more loudly in this case.
assert(isReentrantCall, """
[\(WorkflowType.self)]: Sink sent an action after it was invalidated. This action will be ignored.
""")
if !isReentrantCall {
var message: String {
"[\(WorkflowType.self)]: Sink sent an action after it was invalidated. This action will be ignored."
}
ExternalLogging.logError(message)
assertionFailure(message)
}
}
}

Expand Down

0 comments on commit 7bf5a6c

Please sign in to comment.