Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feat]: external logging API #249

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 {}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Love the use of the name space.


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