diff --git a/Package.swift b/Package.swift index 37e0222f6..6ba166613 100644 --- a/Package.swift +++ b/Package.swift @@ -62,13 +62,17 @@ let package = Package( .package(url: "https://github.com/pointfreeco/swift-perception", "1.5.0" ..< "3.0.0"), .package(url: "https://github.com/pointfreeco/swift-custom-dump", from: "1.3.3"), .package(url: "https://github.com/pointfreeco/xctest-dynamic-overlay", from: "1.0.0"), + .package(url: "https://github.com/apple/swift-log.git", from: "1.0.0"), ], targets: [ // MARK: Workflow .target( name: "Workflow", - dependencies: ["ReactiveSwift"], + dependencies: [ + .product(name: "Logging", package: "swift-log"), + "ReactiveSwift", + ], path: "Workflow/Sources" ), .target( diff --git a/Samples/Tuist/Package.resolved b/Samples/Tuist/Package.resolved index 7dae328cf..269f3a5bd 100644 --- a/Samples/Tuist/Package.resolved +++ b/Samples/Tuist/Package.resolved @@ -63,6 +63,15 @@ "version" : "1.1.0" } }, + { + "identity" : "swift-log", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-log.git", + "state" : { + "revision" : "ce592ae52f982c847a4efc0dd881cc9eb32d29f2", + "version" : "1.6.4" + } + }, { "identity" : "swift-perception", "kind" : "remoteSourceControl", diff --git a/Workflow/Sources/RenderContext.swift b/Workflow/Sources/RenderContext.swift index 60fe2af4a..e89d33716 100644 --- a/Workflow/Sources/RenderContext.swift +++ b/Workflow/Sources/RenderContext.swift @@ -153,7 +153,10 @@ public class RenderContext: 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.") + if !isValid { + WorkflowLogger.logExternal(as: .error, "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.") + assertionFailure("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.") + } } } } diff --git a/Workflow/Sources/SubtreeManager.swift b/Workflow/Sources/SubtreeManager.swift index cf262e8c3..1a2861477 100644 --- a/Workflow/Sources/SubtreeManager.swift +++ b/Workflow/Sources/SubtreeManager.swift @@ -409,27 +409,25 @@ extension WorkflowNode.SubtreeManager { case .valid(let handler): handler(event) - case .invalid: - #if DEBUG + #if DEBUG + case .invalid where isReentrantCall: // Reentrancy seems to often be due to UIKit behaviors over // which we have little control (e.g. synchronous resignation // of first responder after a new Rendering is assigned). Emit // some debug info in these cases. - if isReentrantCall { - print("[\(WorkflowType.self)]: ℹ️ Sink sent another action after it was invalidated but before its original action handling was resolved. This new action will be ignored. If this is unexpected, set a Swift error breakpoint on `\(InvalidSinkSentAction.self)` to debug.") - } + WorkflowLogger.logExternal(as: .info, "[\(WorkflowType.self)]: ℹ️ Sink sent another action after it was invalidated but before its original action handling was resolved. This new action will be ignored. If this is unexpected, set a Swift error breakpoint on `\(InvalidSinkSentAction.self)` to debug.") + do { throw InvalidSinkSentAction() } catch {} + #endif - do { - throw InvalidSinkSentAction() - } catch {} - #endif - - // 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. - """) + case .invalid where !isReentrantCall: + // If we've already been invalidated when `handle()` is initially + // called, then it's likely we've somehow been inadvertently retained + // from the 'outside world'. Fail more loudly in this case. + WorkflowLogger.logExternal(as: .warning, "[\(WorkflowType.self)]: Sink sent an action after it was invalidated. This action will be ignored.") + assertionFailure("[\(WorkflowType.self)]: Sink sent an action after it was invalidated. This action will be ignored.") + + case .invalid: + break } } diff --git a/Workflow/Sources/WorkflowLogger.swift b/Workflow/Sources/WorkflowLogger.swift index 4f75eb45b..c28f42a6e 100644 --- a/Workflow/Sources/WorkflowLogger.swift +++ b/Workflow/Sources/WorkflowLogger.swift @@ -15,6 +15,7 @@ */ import Foundation +import Logging import os.signpost extension OSLog { @@ -207,3 +208,33 @@ enum WorkflowLogger { } } } + +// MARK: - External Logging + +typealias ExternalLogger = Logging.Logger + +extension WorkflowLogger { + static func logExternal( + as level: ExternalLogger.Level, + _ message: @autoclosure () -> ExternalLogger.Message, + metadata: @autoclosure () -> ExternalLogger.Metadata? = nil, + source: @autoclosure () -> String? = nil, + file: String = #fileID, + function: String = #function, + line: UInt = #line + ) { + ExternalLogger.workflow.log( + level: level, + message(), + metadata: metadata(), + source: source(), + file: file, + function: function, + line: line + ) + } +} + +extension ExternalLogger { + static let workflow = Logger(label: "com.squareup.workflow") +}