瀏覽代碼

Split up Swift files, add support for Swift 6.0 and 6.1, drop support for Swift pre 5.10

Florian Friedrich 8 月之前
父節點
當前提交
29098357b6

+ 6 - 6
.github/workflows/cocoapods-release.yml

@@ -11,17 +11,17 @@ env:
 
 jobs:
   publish-podspec:
-    runs-on: macos-14
+    runs-on: macos-15
     steps:
       - uses: maxim-lobanov/setup-xcode@v1
         with:
-          xcode-version: ^15.3
+          xcode-version: ^16.4
       - name: Download visionOS
         run: |
-            sudo xcodebuild -runFirstLaunch
-            sudo xcrun simctl list # needed to boot the simulator
-            sudo xcodebuild -downloadPlatform visionOS
-            sudo xcodebuild -runFirstLaunch
+          sudo xcodebuild -runFirstLaunch
+          sudo xcrun simctl list # needed to boot the simulator
+          sudo xcodebuild -downloadPlatform visionOS
+          sudo xcodebuild -runFirstLaunch
       - uses: actions/checkout@v4
       - name: Generate Podspec
         env:

+ 1 - 1
.github/workflows/cocoapods-trunk-session-keepalive.yml

@@ -7,7 +7,7 @@ on:
 
 jobs:
   session-keepalive:
-    runs-on: macos-14
+    runs-on: macos-15
     steps:
       - name: Keep CocoaPods Trunk Session Alive
         env:

+ 8 - 8
.github/workflows/cocoapods-validations.yml

@@ -2,9 +2,9 @@ name: CocoaPods Validation
 
 on:
   push:
-    branches: [ master ]
+    branches: [master]
   pull_request:
-    branches: [ master ]
+    branches: [master]
 
 env:
   LC_CTYPE: en_US.UTF-8
@@ -12,17 +12,17 @@ env:
 
 jobs:
   validate-cocoapods:
-    runs-on: macos-14
+    runs-on: macos-15
     steps:
       - uses: maxim-lobanov/setup-xcode@v1
         with:
-          xcode-version: ^15.3
+          xcode-version: ^16.4
       - name: Download visionOS
         run: |
-            sudo xcodebuild -runFirstLaunch
-            sudo xcrun simctl list # needed to boot the simulator
-            sudo xcodebuild -downloadPlatform visionOS
-            sudo xcodebuild -runFirstLaunch
+          sudo xcodebuild -runFirstLaunch
+          sudo xcrun simctl list # needed to boot the simulator
+          sudo xcodebuild -downloadPlatform visionOS
+          sudo xcodebuild -runFirstLaunch
       - uses: actions/checkout@v4
       - name: Generate Podspec
         run: ./Scripts/generate-podspec.sh

+ 2 - 2
.github/workflows/danger.yml

@@ -2,7 +2,7 @@ name: Danger
 
 on:
   pull_request_target:
-    branches: [ master ]
+    branches: [master]
 
 env:
   LC_CTYPE: en_US.UTF-8
@@ -16,7 +16,7 @@ jobs:
         with:
           fetch-depth: 0
       - name: danger/swift
-        uses: docker://ghcr.io/danger/danger-swift-with-swiftlint:3.15.0
+        uses: danger/swift@3.15.0
         with:
           args: --failOnErrors --no-publish-check
         env:

+ 4 - 4
.github/workflows/demo-builds.yml

@@ -2,9 +2,9 @@ name: Demo Builds
 
 on:
   push:
-    branches: [ master ]
+    branches: [master]
   pull_request:
-    branches: [ master ]
+    branches: [master]
 
 env:
   LC_CTYPE: en_US.UTF-8
@@ -12,7 +12,7 @@ env:
 
 jobs:
   build-demos:
-    runs-on: macos-14
+    runs-on: macos-15
     strategy:
       matrix:
         scheme:
@@ -42,7 +42,7 @@ jobs:
     steps:
       - uses: maxim-lobanov/setup-xcode@v1
         with:
-          xcode-version: ^15.3
+          xcode-version: ^16.4
       - uses: actions/checkout@v4
       - uses: sersoft-gmbh/xcodebuild-action@v3
         with:

+ 6 - 6
.github/workflows/integration-tests.yml

@@ -2,9 +2,9 @@ name: Integration Tests
 
 on:
   push:
-    branches: [ master ]
+    branches: [master]
   pull_request:
-    branches: [ master ]
+    branches: [master]
 
 env:
   LC_CTYPE: en_US.UTF-8
@@ -12,11 +12,11 @@ env:
 
 jobs:
   static-lib:
-    runs-on: macos-14
+    runs-on: macos-15
     steps:
       - uses: maxim-lobanov/setup-xcode@v1
         with:
-          xcode-version: ^15.3
+          xcode-version: ^16.4
       - uses: actions/checkout@v4
       - uses: sersoft-gmbh/xcodebuild-action@v3
         with:
@@ -26,7 +26,7 @@ jobs:
           action: build
 
   dynamic-lib:
-    runs-on: macos-14
+    runs-on: macos-15
     strategy:
       matrix:
         scheme:
@@ -37,7 +37,7 @@ jobs:
     steps:
       - uses: maxim-lobanov/setup-xcode@v1
         with:
-          xcode-version: ^15.3
+          xcode-version: ^16.4
       - uses: actions/checkout@v4
       - uses: sersoft-gmbh/xcodebuild-action@v3
         with:

+ 9 - 9
.github/workflows/library-builds.yml

@@ -2,9 +2,9 @@ name: Library Builds
 
 on:
   push:
-    branches: [ master ]
+    branches: [master]
   pull_request:
-    branches: [ master ]
+    branches: [master]
 
 env:
   LC_CTYPE: en_US.UTF-8
@@ -12,14 +12,14 @@ env:
 
 jobs:
   static-lib:
-    runs-on: macos-14
+    runs-on: macos-15
     strategy:
       matrix:
-        sdk: [ iphonesimulator, macosx ]
+        sdk: [iphonesimulator, macosx]
     steps:
       - uses: maxim-lobanov/setup-xcode@v1
         with:
-          xcode-version: ^15.3
+          xcode-version: ^16.4
       - uses: actions/checkout@v4
       - uses: sersoft-gmbh/xcodebuild-action@v3
         with:
@@ -30,15 +30,15 @@ jobs:
           action: build
 
   dynamic-lib:
-    runs-on: macos-14
+    runs-on: macos-15
     strategy:
       matrix:
-        scheme: [ CocoaLumberjack, CocoaLumberjackSwift ]
-        sdk: [ iphonesimulator, macosx, watchsimulator, appletvsimulator ]
+        scheme: [CocoaLumberjack, CocoaLumberjackSwift]
+        sdk: [iphonesimulator, macosx, watchsimulator, appletvsimulator]
     steps:
       - uses: maxim-lobanov/setup-xcode@v1
         with:
-          xcode-version: ^15.3
+          xcode-version: ^16.4
       - uses: actions/checkout@v4
       - uses: sersoft-gmbh/xcodebuild-action@v3
         with:

+ 11 - 11
.github/workflows/unit-tests.yml

@@ -2,9 +2,9 @@ name: Unit Tests
 
 on:
   push:
-    branches: [ master ]
+    branches: [master]
   pull_request:
-    branches: [ master ]
+    branches: [master]
 
 env:
   LC_CTYPE: en_US.UTF-8
@@ -12,11 +12,11 @@ env:
 
 jobs:
   swiftpm:
-    runs-on: macos-14
+    runs-on: macos-15
     steps:
       - uses: maxim-lobanov/setup-xcode@v1
         with:
-          xcode-version: ^15.3
+          xcode-version: ^16.4
       - uses: actions/checkout@v4
       - name: Run SwiftPM Tests
         run: swift test --enable-code-coverage --parallel
@@ -28,10 +28,10 @@ jobs:
           files: ${{ join(fromJSON(steps.coverage-files.outputs.files), ',') }}
 
   xcode:
-    runs-on: macos-14
+    runs-on: macos-15
     strategy:
       matrix:
-        scheme: [ Swift Tests ]
+        scheme: [Swift Tests]
         platform:
           - macOS
           - iOS
@@ -49,7 +49,7 @@ jobs:
     steps:
       - uses: maxim-lobanov/setup-xcode@v1
         with:
-          xcode-version: ^15.3
+          xcode-version: ^16.4
       - name: Determine Xcode destination
         id: xcode-destination
         env:
@@ -69,10 +69,10 @@ jobs:
       - name: Download visionOS
         if: ${{ matrix.platform == 'visionOS' }}
         run: |
-            sudo xcodebuild -runFirstLaunch
-            sudo xcrun simctl list # needed to boot the simulator
-            sudo xcodebuild -downloadPlatform visionOS
-            sudo xcodebuild -runFirstLaunch
+          sudo xcodebuild -runFirstLaunch
+          sudo xcrun simctl list # needed to boot the simulator
+          sudo xcodebuild -downloadPlatform visionOS
+          sudo xcodebuild -runFirstLaunch
       - uses: actions/checkout@v4
       - uses: sersoft-gmbh/xcodebuild-action@v3
         with:

+ 2 - 2
Dangerfile.swift

@@ -44,8 +44,8 @@ let isDeclaredTrivial = danger.github?.pullRequest.title.contains("#trivial") ??
 let hasSourceChanges = (git.modifiedFiles + git.createdFiles).contains { $0.isInSources }
 
 // Make it more obvious that a PR is a work in progress and shouldn't be merged yet
-if danger.github?.pullRequest.title.contains("WIP") == true {
-    warn("PR is marked as Work in Progress")
+if danger.github?.pullRequest.title.contains("WIP") == true && danger.github?.pullRequest.draft !== true {
+    warn("PR is marked as Work in Progress. Please consider marking the PR as Draft in GitHub directly.")
 }
 
 // Warn when there is a big PR

+ 4 - 3
Package.resolved

@@ -1,14 +1,15 @@
 {
+  "originHash" : "b0930a81330aeb03b7dbd0ba4250562c0ae1e72b9088a7161b5d5ce00142176a",
   "pins" : [
     {
       "identity" : "swift-log",
       "kind" : "remoteSourceControl",
       "location" : "https://github.com/apple/swift-log",
       "state" : {
-        "revision" : "532d8b529501fb73a2455b179e0bbb6d49b652ed",
-        "version" : "1.5.3"
+        "revision" : "3d8596ed08bd13520157f0355e35caed215ffbfa",
+        "version" : "1.6.3"
       }
     }
   ],
-  "version" : 2
+  "version" : 3
 }

+ 3 - 8
Package.swift

@@ -1,17 +1,12 @@
-// swift-tools-version:5.10
+// swift-tools-version:6.1
 // The swift-tools-version declares the minimum version of Swift required to build this package.
 
 import PackageDescription
 
 let swiftSettings: Array<SwiftSetting> = [
-    .enableUpcomingFeature("ConciseMagicFile"),
+    .swiftLanguageMode(.v6),
     .enableUpcomingFeature("ExistentialAny"),
-    .enableUpcomingFeature("BareSlashRegexLiterals"),
-    .enableUpcomingFeature("DisableOutwardActorInference"),
-    .enableUpcomingFeature("IsolatedDefaultValues"),
-    .enableUpcomingFeature("DeprecateApplicationMain"),
-    .enableExperimentalFeature("StrictConcurrency"),
-    .enableExperimentalFeature("GlobalConcurrency"),
+    .enableUpcomingFeature("InternalImportsByDefault"),
 ]
 
 let package = Package(

+ 22 - 5
Package@swift-5.8.swift → Package@swift-5.10.swift

@@ -1,8 +1,20 @@
-// swift-tools-version:5.8
+// swift-tools-version:5.10
 // The swift-tools-version declares the minimum version of Swift required to build this package.
 
 import PackageDescription
 
+let swiftSettings: Array<SwiftSetting> = [
+    .enableUpcomingFeature("ConciseMagicFile"),
+    .enableUpcomingFeature("ExistentialAny"),
+    .enableUpcomingFeature("BareSlashRegexLiterals"),
+    .enableUpcomingFeature("DisableOutwardActorInference"),
+    .enableUpcomingFeature("IsolatedDefaultValues"),
+    .enableUpcomingFeature("DeprecateApplicationMain"),
+    .enableExperimentalFeature("StrictConcurrency"),
+    .enableExperimentalFeature("GlobalConcurrency"),
+    .enableExperimentalFeature("AccessLevelOnImport"),
+]
+
 let package = Package(
     name: "CocoaLumberjack",
     platforms: [
@@ -10,6 +22,7 @@ let package = Package(
         .iOS(.v12),
         .tvOS(.v12),
         .watchOS(.v5),
+        .visionOS(.v1),
     ],
     products: [
         // Products define the executables and libraries produced by a package, and make them visible to other packages.
@@ -44,21 +57,25 @@ let package = Package(
                 "CocoaLumberjack",
                 "CocoaLumberjackSwiftSupport",
             ],
-            exclude: ["Supporting Files"]),
+            exclude: ["Supporting Files"],
+            swiftSettings: swiftSettings),
         .target(
             name: "CocoaLumberjackSwiftLogBackend",
             dependencies: [
                 "CocoaLumberjack",
                 .product(name: "Logging", package: "swift-log"),
-            ]),
+            ],
+            swiftSettings: swiftSettings),
         .testTarget(
             name: "CocoaLumberjackTests",
             dependencies: ["CocoaLumberjack"]),
         .testTarget(
             name: "CocoaLumberjackSwiftTests",
-            dependencies: ["CocoaLumberjackSwift"]),
+            dependencies: ["CocoaLumberjackSwift"],
+            swiftSettings: swiftSettings),
         .testTarget(
             name: "CocoaLumberjackSwiftLogBackendTests",
-            dependencies: ["CocoaLumberjackSwiftLogBackend"]),
+            dependencies: ["CocoaLumberjackSwiftLogBackend"],
+            swiftSettings: swiftSettings),
     ]
 )

+ 15 - 5
Package@swift-5.9.swift → Package@swift-6.0.swift

@@ -1,8 +1,14 @@
-// swift-tools-version:5.9
+// swift-tools-version:6.0
 // The swift-tools-version declares the minimum version of Swift required to build this package.
 
 import PackageDescription
 
+let swiftSettings: Array<SwiftSetting> = [
+    .swiftLanguageMode(.v6),
+    .enableUpcomingFeature("ExistentialAny"),
+    .enableUpcomingFeature("InternalImportsByDefault"),
+]
+
 let package = Package(
     name: "CocoaLumberjack",
     platforms: [
@@ -45,21 +51,25 @@ let package = Package(
                 "CocoaLumberjack",
                 "CocoaLumberjackSwiftSupport",
             ],
-            exclude: ["Supporting Files"]),
+            exclude: ["Supporting Files"],
+            swiftSettings: swiftSettings),
         .target(
             name: "CocoaLumberjackSwiftLogBackend",
             dependencies: [
                 "CocoaLumberjack",
                 .product(name: "Logging", package: "swift-log"),
-            ]),
+            ],
+            swiftSettings: swiftSettings),
         .testTarget(
             name: "CocoaLumberjackTests",
             dependencies: ["CocoaLumberjack"]),
         .testTarget(
             name: "CocoaLumberjackSwiftTests",
-            dependencies: ["CocoaLumberjackSwift"]),
+            dependencies: ["CocoaLumberjackSwift"],
+            swiftSettings: swiftSettings),
         .testTarget(
             name: "CocoaLumberjackSwiftLogBackendTests",
-            dependencies: ["CocoaLumberjackSwiftLogBackend"]),
+            dependencies: ["CocoaLumberjackSwiftLogBackend"],
+            swiftSettings: swiftSettings),
     ]
 )

+ 2 - 436
Sources/CocoaLumberjackSwift/CocoaLumberjack.swift

@@ -13,421 +13,10 @@
 //   to endorse or promote products derived from this software without specific
 //   prior written permission of Deusty, LLC.
 
-// swiftlint:disable file_length
-
-#if canImport(Synchronization)
-import Synchronization
-#endif
-@_exported import CocoaLumberjack
+@_exported public import CocoaLumberjack
 #if SWIFT_PACKAGE
-import CocoaLumberjackSwiftSupport
-#endif
-
-extension DDLogFlag {
-    public static func from(_ logLevel: DDLogLevel) -> DDLogFlag {
-        DDLogFlag(rawValue: logLevel.rawValue)
-    }
-
-	public init(_ logLevel: DDLogLevel) {
-        self = DDLogFlag(rawValue: logLevel.rawValue)
-	}
-
-    /// Returns the log level, or the lowest equivalent.
-    public func toLogLevel() -> DDLogLevel {
-        if let ourValid = DDLogLevel(rawValue: rawValue) {
-            return ourValid
-        } else {
-            if contains(.verbose) {
-                return .verbose
-            } else if contains(.debug) {
-                return .debug
-            } else if contains(.info) {
-                return .info
-            } else if contains(.warning) {
-                return .warning
-            } else if contains(.error) {
-                return .error
-            } else {
-                return .off
-            }
-        }
-    }
-}
-
-#if canImport(Synchronization)
-#if compiler(>=6.0)
-@available(macOS 15, iOS 18, tvOS 18, watchOS 11, visionOS 2, *)
-extension DDLogLevel: @retroactive AtomicRepresentable {}
-#else
-extension DDLogLevel: AtomicRepresentable {}
-#endif
-
-@available(macOS 15, iOS 18, tvOS 18, watchOS 11, visionOS 2, *)
-private let _dynamicLogLevel = Atomic(DDLogLevel.all)
-#endif
-
-private let _dynamicLogLevelLock = NSLock()
-#if swift(>=5.9)
-nonisolated(unsafe) private var _dynamicLogLevelStorage = DDLogLevel.all
-#else
-private var _dynamicLogLevelStorage = DDLogLevel.all
-#endif
-
-private func _readDynamicLogLevel() -> DDLogLevel {
-#if canImport(Synchronization)
-    if #available(macOS 15, iOS 18, tvOS 18, watchOS 11, visionOS 2, *) {
-        return _dynamicLogLevel.load(ordering: .relaxed)
-    }
-#endif
-    _dynamicLogLevelLock.lock()
-    defer { _dynamicLogLevelLock.unlock() }
-    return _dynamicLogLevelStorage
-}
-
-private func _writeDynamicLogLevel(_ newValue: DDLogLevel) {
-#if canImport(Synchronization)
-    if #available(macOS 15, iOS 18, tvOS 18, watchOS 11, visionOS 2, *) {
-        _dynamicLogLevel.store(newValue, ordering: .relaxed)
-        return
-    }
-#endif
-    _dynamicLogLevelLock.lock()
-    defer { _dynamicLogLevelLock.unlock() }
-    _dynamicLogLevelStorage = newValue
-}
-
-#if swift(>=5.9)
-/// The log level that can dynamically limit log messages (vs. the static ``DDDefaultLogLevel``). This log level will only be checked, if the message passes the ``DDDefaultLogLevel``.
-public nonisolated(unsafe) var dynamicLogLevel: DDLogLevel {
-    get {
-        _readDynamicLogLevel()
-    }
-    set {
-        _writeDynamicLogLevel(newValue)
-    }
-}
-#else
-/// The log level that can dynamically limit log messages (vs. the static ``DDDefaultLogLevel``). This log level will only be checked, if the message passes the ``DDDefaultLogLevel``.
-public var dynamicLogLevel: DDLogLevel {
-    get {
-        _readDynamicLogLevel()
-    }
-    set {
-        _writeDynamicLogLevel(newValue)
-    }
-}
-#endif
-
-/// Resets the ``dynamicLogLevel`` to ``DDLogLevel/all``.
-/// - SeeAlso: ``dynamicLogLevel``
-@inlinable
-public func resetDynamicLogLevel() {
-    dynamicLogLevel = .all
-}
-
-@available(*, deprecated, message: "Please use dynamicLogLevel", renamed: "dynamicLogLevel")
-public var defaultDebugLevel: DDLogLevel {
-    get {
-        dynamicLogLevel
-    }
-    set {
-        dynamicLogLevel = newValue
-    }
-}
-
-@available(*, deprecated, message: "Please use resetDynamicLogLevel", renamed: "resetDynamicLogLevel")
-public func resetDefaultDebugLevel() {
-    resetDynamicLogLevel()
-}
-
-#if canImport(Synchronization)
-@available(macOS 15, iOS 18, tvOS 18, watchOS 11, visionOS 2, *)
-private let _asyncLoggingEnabled = Atomic(true)
-#endif
-
-private let _asyncLoggingEnabledLock = NSLock()
-#if swift(>=5.9)
-nonisolated(unsafe) private var _asyncLoggingEnabledStorage = true
-#else
-private var _asyncLoggingEnabledStorage = true
-#endif
-
-private func _readAsyncLoggingEnabled() -> Bool {
-#if canImport(Synchronization)
-    if #available(macOS 15, iOS 18, tvOS 18, watchOS 11, visionOS 2, *) {
-        return _asyncLoggingEnabled.load(ordering: .relaxed)
-    }
-#endif
-    _asyncLoggingEnabledLock.lock()
-    defer { _asyncLoggingEnabledLock.unlock() }
-    return _asyncLoggingEnabledStorage
-}
-
-private func _writeAsyncLoggingEnabled(_ newValue: Bool) {
-#if canImport(Synchronization)
-    if #available(macOS 15, iOS 18, tvOS 18, watchOS 11, visionOS 2, *) {
-        _asyncLoggingEnabled.store(newValue, ordering: .relaxed)
-        return
-    }
-#endif
-    _asyncLoggingEnabledLock.lock()
-    defer { _asyncLoggingEnabledLock.unlock() }
-    _asyncLoggingEnabledStorage = newValue
-}
-
-#if swift(>=5.9)
-/// If `true`, all logs (except errors) are logged asynchronously by default.
-public nonisolated(unsafe) var asyncLoggingEnabled: Bool {
-    get {
-        _readAsyncLoggingEnabled()
-    }
-    set {
-        _writeAsyncLoggingEnabled(newValue)
-    }
-}
-#else
-/// If `true`, all logs (except errors) are logged asynchronously by default.
-public var asyncLoggingEnabled: Bool {
-    get {
-        _readAsyncLoggingEnabled()
-    }
-    set {
-        _writeAsyncLoggingEnabled(newValue)
-    }
-}
-#endif
-
-@frozen
-public struct DDLogMessageFormat: ExpressibleByStringInterpolation {
-    public typealias StringLiteralType = String
-
-    @usableFromInline
-    struct Storage {
-#if swift(>=5.6)
-        @usableFromInline
-        typealias VArg = any CVarArg
-#else
-        @usableFromInline
-        typealias VArg = CVarArg
+public import CocoaLumberjackSwiftSupport
 #endif
-        @usableFromInline
-        let requiresArgumentParsing: Bool
-        @usableFromInline
-        var format: String
-        @usableFromInline
-        var args: Array<VArg> {
-            willSet {
-                // We only assert here to let the compiler optimize it away.
-                // The setter will be used repeatedly during string interpolation, thus should stay fast.
-                assert(requiresArgumentParsing || newValue.isEmpty, "Non-empty arguments always require argument parsing!")
-            }
-        }
-
-        @usableFromInline
-        init(requiresArgumentParsing: Bool, format: String, args: Array<VArg>) {
-            precondition(requiresArgumentParsing || args.isEmpty, "Non-empty arguments always require argument parsing!")
-            self.requiresArgumentParsing = requiresArgumentParsing
-            self.format = format
-            self.args = args
-        }
-
-        @available(*, deprecated, message: "Use initializer specifying the need for argument parsing: init(requiresArgumentParsing:format:args:)")
-        @usableFromInline
-        init(format: String, args: Array<VArg>) {
-            self.init(requiresArgumentParsing: !args.isEmpty, format: format, args: args)
-        }
-
-        @usableFromInline
-        mutating func addString(_ string: String) {
-            format.append(string.replacingOccurrences(of: "%", with: "%%"))
-        }
-
-        @inlinable
-        mutating func addValue(_ arg: VArg, withSpecifier specifier: String) {
-            format.append(specifier)
-            args.append(arg)
-        }
-    }
-
-    @frozen
-    public struct StringInterpolation: StringInterpolationProtocol {
-        @usableFromInline
-        var storage: Storage
-
-        @inlinable
-        public init(literalCapacity: Int, interpolationCount: Int) {
-            var format = String()
-            format.reserveCapacity(literalCapacity)
-            var args = Array<Storage.VArg>()
-            args.reserveCapacity(interpolationCount)
-            storage = .init(requiresArgumentParsing: true, format: format, args: args)
-        }
-
-        @inlinable
-        public mutating func appendLiteral(_ literal: StringLiteralType) {
-            storage.addString(literal)
-        }
-
-        @inlinable
-        public mutating func appendInterpolation<S: StringProtocol>(_ string: S) {
-            storage.addValue(String(string), withSpecifier: "%@")
-        }
-
-        @inlinable
-        public mutating func appendInterpolation(_ int: Int8) {
-            storage.addValue(int, withSpecifier: "%c")
-        }
-
-        @inlinable
-        public mutating func appendInterpolation(_ int: UInt8) {
-            storage.addValue(int, withSpecifier: "%c")
-        }
-
-        @inlinable
-        public mutating func appendInterpolation(_ int: Int16) {
-            storage.addValue(int, withSpecifier: "%i")
-        }
-
-        @inlinable
-        public mutating func appendInterpolation(_ int: UInt16) {
-            storage.addValue(int, withSpecifier: "%u")
-        }
-
-        @inlinable
-        public mutating func appendInterpolation(_ int: Int32) {
-            storage.addValue(int, withSpecifier: "%li")
-        }
-
-        @inlinable
-        public mutating func appendInterpolation(_ int: UInt32) {
-            storage.addValue(int, withSpecifier: "%lu")
-        }
-
-        @inlinable
-        public mutating func appendInterpolation(_ int: Int64) {
-            storage.addValue(int, withSpecifier: "%lli")
-        }
-
-        @inlinable
-        public mutating func appendInterpolation(_ int: UInt64) {
-            storage.addValue(int, withSpecifier: "%llu")
-        }
-
-        @inlinable
-        public mutating func appendInterpolation(_ int: Int) {
-#if arch(arm64) || arch(x86_64)
-            storage.addValue(int, withSpecifier: "%lli")
-#else
-            storage.addValue(int, withSpecifier: "%li")
-#endif
-        }
-
-        @inlinable
-        public mutating func appendInterpolation(_ int: UInt) {
-#if arch(arm64) || arch(x86_64)
-            storage.addValue(int, withSpecifier: "%llu")
-#else
-            storage.addValue(int, withSpecifier: "%lu")
-#endif
-        }
-
-        @inlinable
-        public mutating func appendInterpolation(_ flt: Float) {
-            storage.addValue(flt, withSpecifier: "%f")
-        }
-
-        @inlinable
-        public mutating func appendInterpolation(_ dbl: Double) {
-            storage.addValue(dbl, withSpecifier: "%lf")
-        }
-
-        @inlinable
-        public mutating func appendInterpolation(_ bool: Bool) {
-            storage.addValue(bool, withSpecifier: "%i") // bools are printed as ints
-        }
-
-        @inlinable
-        public mutating func appendInterpolation<Convertible: ReferenceConvertible>(_ convertible: Convertible) {
-            if convertible is Storage.VArg {
-                print("""
-                [WARNING]: CocoaLumberjackSwift is creating a \(DDLogMessageFormat.self) with an interpolation conforming to `CVarArg` \
-                using the overload for `ReferenceConvertible` interpolations!
-                Please report this as a bug, including the following snippet:
-                ```
-                Convertible: \(Convertible.self), ReferenceType: \(Convertible.ReferenceType.self), type(of: convertible): \(type(of: convertible))
-                ```
-                """)
-            }
-            // This should be safe, sine the compiler should convert it to the reference.
-            // swiftlint:disable:next force_cast
-            storage.addValue(convertible as? Storage.VArg ?? convertible as! Convertible.ReferenceType, withSpecifier: "%@")
-        }
-
-        @inlinable
-        public mutating func appendInterpolation<Obj: NSObject>(_ object: Obj) {
-            storage.addValue(object, withSpecifier: "%@")
-        }
-
-        @_disfavoredOverload
-        public mutating func appendInterpolation(_ any: Any) {
-            appendInterpolation(String(describing: any))
-        }
-    }
-
-    @usableFromInline
-    let storage: Storage
-
-    @inlinable
-    var format: String { storage.format }
-    @inlinable
-    var args: Array<Storage.VArg> { storage.args }
-
-    @inlinable
-    var formatted: String {
-        guard storage.requiresArgumentParsing else { return storage.format }
-        return String(format: storage.format, arguments: storage.args)
-    }
-
-    @inlinable
-    public init(stringLiteral value: StringLiteralType) {
-        storage = .init(requiresArgumentParsing: false, format: value, args: [])
-    }
-
-    @inlinable
-    public init(stringInterpolation: StringInterpolation) {
-        storage = stringInterpolation.storage
-    }
-
-    @inlinable
-    internal init(_formattedMessage: String) {
-        storage = .init(requiresArgumentParsing: false, format: _formattedMessage, args: [])
-    }
-}
-
-extension DDLogMessage {
-    @inlinable
-    public convenience init(_ format: DDLogMessageFormat,
-                            level: DDLogLevel,
-                            flag: DDLogFlag,
-                            context: Int = 0,
-                            file: StaticString = #file,
-                            function: StaticString = #function,
-                            line: UInt = #line,
-                            tag: Any? = nil,
-                            timestamp: Date? = nil) {
-        self.init(format: format.format,
-                  formatted: format.formatted,
-                  level: level,
-                  flag: flag,
-                  context: context,
-                  file: String(describing: file),
-                  function: String(describing: function),
-                  line: line,
-                  tag: tag,
-                  options: [.dontCopyMessage],
-                  timestamp: timestamp)
-    }
-}
 
 @inlinable
 public func _DDLogMessage(_ messageFormat: @autoclosure () -> DDLogMessageFormat,
@@ -711,26 +300,3 @@ public func DDLogError(_ message: @autoclosure () -> Any,
                   asynchronous: async ?? false,
                   ddlog: ddlog)
 }
-
-/// Returns a String of the current filename, without full path or extension.
-/// Analogous to the C preprocessor macro `THIS_FILE`.
-public func currentFileName(_ fileName: StaticString = #file) -> String {
-    var str = String(describing: fileName)
-    if let idx = str.range(of: "/", options: .backwards)?.upperBound {
-        str = String(str[idx...])
-    }
-    if let idx = str.range(of: ".", options: .backwards)?.lowerBound {
-        str = String(str[..<idx])
-    }
-    return str
-}
-
-// swiftlint:disable identifier_name
-// swiftlint doesn't like func names that begin with a capital letter - deprecated
-@available(*, deprecated, message: "Please use currentFileName", renamed: "currentFileName")
-public func CurrentFileName(_ fileName: StaticString = #file) -> String {
-    currentFileName(fileName)
-}
-// swiftlint:enable identifier_name
-
-// swiftlint:enable file_length

+ 130 - 0
Sources/CocoaLumberjackSwift/ConfigurationGlobals.swift

@@ -0,0 +1,130 @@
+// Software License Agreement (BSD License)
+//
+// Copyright (c) 2010-2025, Deusty, LLC
+// All rights reserved.
+//
+// Redistribution and use of this software in source and binary forms,
+// with or without modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+//   this list of conditions and the following disclaimer.
+//
+// * Neither the name of Deusty nor the names of its contributors may be used
+//   to endorse or promote products derived from this software without specific
+//   prior written permission of Deusty, LLC.
+
+#if canImport(Synchronization)
+public import Synchronization
+#endif
+
+#if canImport(Synchronization)
+#if compiler(>=6.0)
+@available(macOS 15, iOS 18, tvOS 18, watchOS 11, visionOS 2, *)
+extension DDLogLevel: @retroactive AtomicRepresentable {}
+#else
+@available(macOS 15, iOS 18, tvOS 18, watchOS 11, visionOS 2, *)
+extension DDLogLevel: AtomicRepresentable {}
+#endif
+
+@available(macOS 15, iOS 18, tvOS 18, watchOS 11, visionOS 2, *)
+private let _dynamicLogLevel = Atomic(DDLogLevel.all)
+#endif
+
+private let _dynamicLogLevelLock = NSLock()
+nonisolated(unsafe) private var _dynamicLogLevelStorage = DDLogLevel.all
+
+private func _readDynamicLogLevel() -> DDLogLevel {
+#if canImport(Synchronization)
+    if #available(macOS 15, iOS 18, tvOS 18, watchOS 11, visionOS 2, *) {
+        return _dynamicLogLevel.load(ordering: .relaxed)
+    }
+#endif
+    _dynamicLogLevelLock.lock()
+    defer { _dynamicLogLevelLock.unlock() }
+    return _dynamicLogLevelStorage
+}
+
+private func _writeDynamicLogLevel(_ newValue: DDLogLevel) {
+#if canImport(Synchronization)
+    if #available(macOS 15, iOS 18, tvOS 18, watchOS 11, visionOS 2, *) {
+        _dynamicLogLevel.store(newValue, ordering: .relaxed)
+        return
+    }
+#endif
+    _dynamicLogLevelLock.lock()
+    defer { _dynamicLogLevelLock.unlock() }
+    _dynamicLogLevelStorage = newValue
+}
+
+/// The log level that can dynamically limit log messages (vs. the static ``DDDefaultLogLevel``). This log level will only be checked, if the message passes the ``DDDefaultLogLevel``.
+public nonisolated(unsafe) var dynamicLogLevel: DDLogLevel {
+    get {
+        _readDynamicLogLevel()
+    }
+    set {
+        _writeDynamicLogLevel(newValue)
+    }
+}
+
+/// Resets the ``dynamicLogLevel`` to ``DDLogLevel/all``.
+/// - SeeAlso: ``dynamicLogLevel``
+@inlinable
+public func resetDynamicLogLevel() {
+    dynamicLogLevel = .all
+}
+
+@available(*, deprecated, message: "Please use dynamicLogLevel", renamed: "dynamicLogLevel")
+public var defaultDebugLevel: DDLogLevel {
+    get {
+        dynamicLogLevel
+    }
+    set {
+        dynamicLogLevel = newValue
+    }
+}
+
+@available(*, deprecated, message: "Please use resetDynamicLogLevel", renamed: "resetDynamicLogLevel")
+public func resetDefaultDebugLevel() {
+    resetDynamicLogLevel()
+}
+
+#if canImport(Synchronization)
+@available(macOS 15, iOS 18, tvOS 18, watchOS 11, visionOS 2, *)
+private let _asyncLoggingEnabled = Atomic(true)
+#endif
+
+private let _asyncLoggingEnabledLock = NSLock()
+nonisolated(unsafe) private var _asyncLoggingEnabledStorage = true
+
+private func _readAsyncLoggingEnabled() -> Bool {
+#if canImport(Synchronization)
+    if #available(macOS 15, iOS 18, tvOS 18, watchOS 11, visionOS 2, *) {
+        return _asyncLoggingEnabled.load(ordering: .relaxed)
+    }
+#endif
+    _asyncLoggingEnabledLock.lock()
+    defer { _asyncLoggingEnabledLock.unlock() }
+    return _asyncLoggingEnabledStorage
+}
+
+private func _writeAsyncLoggingEnabled(_ newValue: Bool) {
+#if canImport(Synchronization)
+    if #available(macOS 15, iOS 18, tvOS 18, watchOS 11, visionOS 2, *) {
+        _asyncLoggingEnabled.store(newValue, ordering: .relaxed)
+        return
+    }
+#endif
+    _asyncLoggingEnabledLock.lock()
+    defer { _asyncLoggingEnabledLock.unlock() }
+    _asyncLoggingEnabledStorage = newValue
+}
+
+/// If `true`, all logs (except errors) are logged asynchronously by default.
+public nonisolated(unsafe) var asyncLoggingEnabled: Bool {
+    get {
+        _readAsyncLoggingEnabled()
+    }
+    set {
+        _writeAsyncLoggingEnabled(newValue)
+    }
+}

+ 35 - 0
Sources/CocoaLumberjackSwift/CurrentFileNameHelper.swift

@@ -0,0 +1,35 @@
+// Software License Agreement (BSD License)
+//
+// Copyright (c) 2010-2025, Deusty, LLC
+// All rights reserved.
+//
+// Redistribution and use of this software in source and binary forms,
+// with or without modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+//   this list of conditions and the following disclaimer.
+//
+// * Neither the name of Deusty nor the names of its contributors may be used
+//   to endorse or promote products derived from this software without specific
+//   prior written permission of Deusty, LLC.
+
+/// Returns a String of the current filename, without full path or extension.
+/// Analogous to the C preprocessor macro `THIS_FILE`.
+public func currentFileName(_ fileName: StaticString = #file) -> String {
+    var str = String(describing: fileName)
+    if let idx = str.range(of: "/", options: .backwards)?.upperBound {
+        str = String(str[idx...])
+    }
+    if let idx = str.range(of: ".", options: .backwards)?.lowerBound {
+        str = String(str[..<idx])
+    }
+    return str
+}
+
+// swiftlint:disable identifier_name
+// swiftlint doesn't like func names that begin with a capital letter - deprecated
+@available(*, deprecated, message: "Please use currentFileName", renamed: "currentFileName")
+public func CurrentFileName(_ fileName: StaticString = #file) -> String {
+    currentFileName(fileName)
+}
+// swiftlint:enable identifier_name

+ 2 - 2
Sources/CocoaLumberjackSwift/DDAssert.swift

@@ -14,8 +14,8 @@
 //   prior written permission of Deusty, LLC.
 
 #if SWIFT_PACKAGE
-import CocoaLumberjack
-import CocoaLumberjackSwiftSupport
+public import CocoaLumberjack
+public import CocoaLumberjackSwiftSupport
 #endif
 
 /**

+ 2 - 16
Sources/CocoaLumberjackSwift/DDLog+Combine.swift

@@ -15,12 +15,8 @@
 
 #if arch(arm64) || arch(x86_64)
 #if canImport(Combine)
-import Combine
-
-#if SWIFT_PACKAGE
-import CocoaLumberjack
-import CocoaLumberjackSwiftSupport
-#endif
+public import Combine
+public import CocoaLumberjack
 
 @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
 extension DDLog {
@@ -39,11 +35,7 @@ extension DDLog {
         ///     .map { message in /* format message */ }
         ///     .sink(receiveValue: { formattedMessage in /* process formattedMessage */ })
         /// ```
-#if compiler(>=5.7)
         var logFormatter: (any DDLogFormatter)?
-#else
-        var logFormatter: DDLogFormatter?
-#endif
 
         init(log: DDLog, with logLevel: DDLogLevel, subscriber: S) {
             self.subscriber = subscriber
@@ -113,15 +105,9 @@ extension DDLog {
 
 @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
 extension Publisher where Output == DDLogMessage {
-#if compiler(>=5.7)
     public func formatted(with formatter: any DDLogFormatter) -> Publishers.CompactMap<Self, String> {
         compactMap { formatter.format(message: $0) }
     }
-#else
-    public func formatted(with formatter: DDLogFormatter) -> Publishers.CompactMap<Self, String> {
-        compactMap { formatter.format(message: $0) }
-    }
-#endif
 }
 #endif
 #endif

+ 47 - 0
Sources/CocoaLumberjackSwift/DDLogFlag+DDLogLevel.swift

@@ -0,0 +1,47 @@
+// Software License Agreement (BSD License)
+//
+// Copyright (c) 2010-2025, Deusty, LLC
+// All rights reserved.
+//
+// Redistribution and use of this software in source and binary forms,
+// with or without modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+//   this list of conditions and the following disclaimer.
+//
+// * Neither the name of Deusty nor the names of its contributors may be used
+//   to endorse or promote products derived from this software without specific
+//   prior written permission of Deusty, LLC.
+
+public import CocoaLumberjack
+
+extension DDLogFlag {
+    public static func from(_ logLevel: DDLogLevel) -> DDLogFlag {
+        DDLogFlag(rawValue: logLevel.rawValue)
+    }
+
+	public init(_ logLevel: DDLogLevel) {
+        self = DDLogFlag(rawValue: logLevel.rawValue)
+	}
+
+    /// Returns the log level, or the lowest equivalent.
+    public func toLogLevel() -> DDLogLevel {
+        if let ourValid = DDLogLevel(rawValue: rawValue) {
+            return ourValid
+        } else {
+            if contains(.verbose) {
+                return .verbose
+            } else if contains(.debug) {
+                return .debug
+            } else if contains(.info) {
+                return .info
+            } else if contains(.warning) {
+                return .warning
+            } else if contains(.error) {
+                return .error
+            } else {
+                return .off
+            }
+        }
+    }
+}

+ 250 - 0
Sources/CocoaLumberjackSwift/DDLogMessageFormat.swift

@@ -0,0 +1,250 @@
+// Software License Agreement (BSD License)
+//
+// Copyright (c) 2010-2025, Deusty, LLC
+// All rights reserved.
+//
+// Redistribution and use of this software in source and binary forms,
+// with or without modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+//   this list of conditions and the following disclaimer.
+//
+// * Neither the name of Deusty nor the names of its contributors may be used
+//   to endorse or promote products derived from this software without specific
+//   prior written permission of Deusty, LLC.
+
+import CocoaLumberjack
+
+@frozen
+public struct DDLogMessageFormat: ExpressibleByStringInterpolation {
+    public typealias StringLiteralType = String
+
+    @usableFromInline
+    struct Storage {
+        @usableFromInline
+        typealias VArg = any CVarArg
+
+        @usableFromInline
+        let requiresArgumentParsing: Bool
+        @usableFromInline
+        var format: String
+        @usableFromInline
+        var args: Array<VArg> {
+            willSet {
+                // We only assert here to let the compiler optimize it away.
+                // The setter will be used repeatedly during string interpolation, thus should stay fast.
+                assert(requiresArgumentParsing || newValue.isEmpty, "Non-empty arguments always require argument parsing!")
+            }
+        }
+
+        @usableFromInline
+        init(requiresArgumentParsing: Bool, format: String, args: Array<VArg>) {
+            precondition(requiresArgumentParsing || args.isEmpty, "Non-empty arguments always require argument parsing!")
+            self.requiresArgumentParsing = requiresArgumentParsing
+            self.format = format
+            self.args = args
+        }
+
+        @available(*, deprecated, message: "Use initializer specifying the need for argument parsing: init(requiresArgumentParsing:format:args:)")
+        @usableFromInline
+        init(format: String, args: Array<VArg>) {
+            self.init(requiresArgumentParsing: !args.isEmpty, format: format, args: args)
+        }
+
+        @usableFromInline
+        mutating func addString(_ string: String) {
+            format.append(string.replacingOccurrences(of: "%", with: "%%"))
+        }
+
+        @inlinable
+        mutating func addValue(_ arg: VArg, withSpecifier specifier: String) {
+            format.append(specifier)
+            args.append(arg)
+        }
+    }
+
+    @frozen
+    public struct StringInterpolation: StringInterpolationProtocol {
+        @usableFromInline
+        var storage: Storage
+
+        @inlinable
+        public init(literalCapacity: Int, interpolationCount: Int) {
+            var format = String()
+            format.reserveCapacity(literalCapacity)
+            var args = Array<Storage.VArg>()
+            args.reserveCapacity(interpolationCount)
+            storage = .init(requiresArgumentParsing: true, format: format, args: args)
+        }
+
+        @inlinable
+        public mutating func appendLiteral(_ literal: StringLiteralType) {
+            storage.addString(literal)
+        }
+
+        @inlinable
+        public mutating func appendInterpolation<S: StringProtocol>(_ string: S) {
+            storage.addValue(String(string), withSpecifier: "%@")
+        }
+
+        @inlinable
+        public mutating func appendInterpolation(_ int: Int8) {
+            storage.addValue(int, withSpecifier: "%c")
+        }
+
+        @inlinable
+        public mutating func appendInterpolation(_ int: UInt8) {
+            storage.addValue(int, withSpecifier: "%c")
+        }
+
+        @inlinable
+        public mutating func appendInterpolation(_ int: Int16) {
+            storage.addValue(int, withSpecifier: "%i")
+        }
+
+        @inlinable
+        public mutating func appendInterpolation(_ int: UInt16) {
+            storage.addValue(int, withSpecifier: "%u")
+        }
+
+        @inlinable
+        public mutating func appendInterpolation(_ int: Int32) {
+            storage.addValue(int, withSpecifier: "%li")
+        }
+
+        @inlinable
+        public mutating func appendInterpolation(_ int: UInt32) {
+            storage.addValue(int, withSpecifier: "%lu")
+        }
+
+        @inlinable
+        public mutating func appendInterpolation(_ int: Int64) {
+            storage.addValue(int, withSpecifier: "%lli")
+        }
+
+        @inlinable
+        public mutating func appendInterpolation(_ int: UInt64) {
+            storage.addValue(int, withSpecifier: "%llu")
+        }
+
+        @inlinable
+        public mutating func appendInterpolation(_ int: Int) {
+#if arch(arm64) || arch(x86_64)
+            storage.addValue(int, withSpecifier: "%lli")
+#else
+            storage.addValue(int, withSpecifier: "%li")
+#endif
+        }
+
+        @inlinable
+        public mutating func appendInterpolation(_ int: UInt) {
+#if arch(arm64) || arch(x86_64)
+            storage.addValue(int, withSpecifier: "%llu")
+#else
+            storage.addValue(int, withSpecifier: "%lu")
+#endif
+        }
+
+        @inlinable
+        public mutating func appendInterpolation(_ flt: Float) {
+            storage.addValue(flt, withSpecifier: "%f")
+        }
+
+        @inlinable
+        public mutating func appendInterpolation(_ dbl: Double) {
+            storage.addValue(dbl, withSpecifier: "%lf")
+        }
+
+        @inlinable
+        public mutating func appendInterpolation(_ bool: Bool) {
+            storage.addValue(bool, withSpecifier: "%i") // bools are printed as ints
+        }
+
+        // Move printed string out of inlinable code portion
+        @usableFromInline
+        func _warnReferenceConvertibleCVarArgInterpolation<Convertible: ReferenceConvertible>(_ convertible: Convertible) {
+            print("""
+            [WARNING]: CocoaLumberjackSwift is creating a \(DDLogMessageFormat.self) with an interpolation conforming to `CVarArg` \
+            using the overload for `ReferenceConvertible` interpolations!
+            Please report this as a bug, including the following snippet:
+            ```
+            Convertible: \(Convertible.self), ReferenceType: \(Convertible.ReferenceType.self), type(of: convertible): \(type(of: convertible))
+            ```
+            """)
+        }
+
+        @inlinable
+        public mutating func appendInterpolation<Convertible: ReferenceConvertible>(_ convertible: Convertible) {
+            if convertible is Storage.VArg {
+               _warnReferenceConvertibleCVarArgInterpolation(convertible)
+            }
+            // This should be safe, sine the compiler should convert it to the reference.
+            // swiftlint:disable:next force_cast
+            storage.addValue(convertible as? Storage.VArg ?? convertible as! Convertible.ReferenceType, withSpecifier: "%@")
+        }
+
+        @inlinable
+        public mutating func appendInterpolation<Obj: NSObject>(_ object: Obj) {
+            storage.addValue(object, withSpecifier: "%@")
+        }
+
+        @_disfavoredOverload
+        public mutating func appendInterpolation(_ any: Any) {
+            appendInterpolation(String(describing: any))
+        }
+    }
+
+    @usableFromInline
+    let storage: Storage
+
+    @inlinable
+    var format: String { storage.format }
+    @inlinable
+    var args: Array<Storage.VArg> { storage.args }
+
+    @inlinable
+    var formatted: String {
+        guard storage.requiresArgumentParsing else { return storage.format }
+        return String(format: storage.format, arguments: storage.args)
+    }
+
+    @inlinable
+    public init(stringLiteral value: StringLiteralType) {
+        storage = .init(requiresArgumentParsing: false, format: value, args: [])
+    }
+
+    @inlinable
+    public init(stringInterpolation: StringInterpolation) {
+        storage = stringInterpolation.storage
+    }
+
+    @inlinable
+    internal init(_formattedMessage: String) {
+        storage = .init(requiresArgumentParsing: false, format: _formattedMessage, args: [])
+    }
+}
+
+extension DDLogMessage {
+    @inlinable
+    public convenience init(_ format: DDLogMessageFormat,
+                            level: DDLogLevel,
+                            flag: DDLogFlag,
+                            context: Int = 0,
+                            file: StaticString = #file,
+                            function: StaticString = #function,
+                            line: UInt = #line,
+                            tag: Any? = nil,
+                            timestamp: Date? = nil) {
+        self.init(format: format.format,
+                  formatted: format.formatted,
+                  level: level,
+                  flag: flag,
+                  context: context,
+                  file: String(describing: file),
+                  function: String(describing: function),
+                  line: line,
+                  tag: tag,
+                  options: [.dontCopyMessage],
+                  timestamp: timestamp)
+    }
+}

+ 2 - 0
Sources/CocoaLumberjackSwift/Supporting Files/CocoaLumberjackSwift.h

@@ -16,5 +16,7 @@
 // This header is mostly blank because all of the declarations are in Swift.
 // Still, this header may still be needed so Swift doesn't complain when importing CocoaLumberjackSwift.
 
+// Make CocoaLumberjack (ObjC) symbols visible.
 #import <CocoaLumberjack/CocoaLumberjack.h>
+// In the Xcode project, this header belongs to CocoaLumberjackSwift instead of a separate target.
 #import <CocoaLumberjackSwift/SwiftLogLevel.h>

+ 6 - 160
Sources/CocoaLumberjackSwiftLogBackend/DDLogHandler.swift

@@ -13,157 +13,8 @@
 //   to endorse or promote products derived from this software without specific
 //   prior written permission of Deusty, LLC.
 
-import CocoaLumberjack
-import Logging
-
-extension Logging.Logger.Level {
-    @inlinable
-    var ddLogLevelAndFlag: (DDLogLevel, DDLogFlag) {
-        switch self {
-        case .trace: return (.verbose, .verbose)
-        case .debug: return (.debug, .debug)
-        case .info, .notice: return (.info, .info)
-        case .warning: return (.warning, .warning)
-        case .error, .critical: return (.error, .error)
-        }
-    }
-}
-
-extension DDLogMessage {
-    /// Contains the swift-log details of a given log message.
-    public struct SwiftLogInformation: Equatable, Sendable {
-        /// Contains information about the swift-log logger that logged this message.
-        public struct LoggerInformation: Equatable, Sendable {
-            /// Contains the metadata from the various sources of on a logger.
-            /// Currently this can be the logger itself, as well as its metadata provider
-            public struct MetadataSources: Equatable, Sendable {
-                /// The metadata of the swift-log logger that logged this message.
-                public let logger: Logging.Logger.Metadata
-                /// The metadata of the metadata provider on the swift-log logger that logged this message.
-                public let provider: Logging.Logger.Metadata?
-            }
-
-            /// The label of the swift-log logger that logged this message.
-            public let label: String
-            /// The metadata of the swift-log logger that logged this message.
-            public let metadataSources: MetadataSources
-
-            /// The metadata of the swift-log logger that logged this message.
-            @available(*, deprecated, renamed: "metadataSources.logger")
-            public var metadata: Logging.Logger.Metadata { metadataSources.logger }
-        }
-
-        /// Contains information about the swift-log message thas was logged.
-        public struct MessageInformation: Equatable, Sendable {
-            /// The original swift-log message.
-            public let message: Logging.Logger.Message
-            /// The original swift-log level of the message. This could be more fine-grained than ``DDLogMessage/level``  & ``DDLogMessage/flag``.
-            public let level: Logging.Logger.Level
-            /// The original swift-log metadata of the message.
-            public let metadata: Logging.Logger.Metadata?
-            /// The original swift-log source of the message.
-            public let source: String
-        }
-
-        /// The information about the swift-log logger that logged this message.
-        public let logger: LoggerInformation
-        /// The information about the swift-log message that was logged.
-        public let message: MessageInformation
-
-        /// Merges the metadata from all layers together.
-        /// The metadata on the logger provides the base.
-        /// Metadata from the logger's metadata provider (if any) trumps the base.
-        /// Metadata from the logged message again trumps both the base and the metadata from the logger's metadata provider.
-        /// Essentially: `logger.metadata < logger.metadataProvider < message.metadata`
-        /// - Note: Accessing this property performs the merge! Accessing it multiple times can be a performance issue!
-        public var mergedMetadata: Logging.Logger.Metadata {
-            var merged = logger.metadataSources.logger
-            if let providerMetadata = logger.metadataSources.provider {
-                merged.merge(providerMetadata, uniquingKeysWith: { $1 })
-            }
-            if let messageMetadata = message.metadata {
-                merged.merge(messageMetadata, uniquingKeysWith: { $1 })
-            }
-            return merged
-        }
-    }
-
-    /// The swift-log information of this log message. This only exists for messages logged via swift-log.
-    /// - SeeAlso: ``DDLogMessage/SwiftLogInformation``
-    @inlinable
-    public var swiftLogInfo: SwiftLogInformation? {
-        (self as? SwiftLogMessage)?._swiftLogInfo
-    }
-}
-
-/// This class (intentionally internal) is only an "encapsulation" layer above ``DDLogMessage``.
-/// It's basically an implementation detail of ``DDLogMessage/swiftLogInfo``.
-@usableFromInline
-final class SwiftLogMessage: DDLogMessage, @unchecked Sendable {
-    @usableFromInline
-    let _swiftLogInfo: SwiftLogInformation
-
-    @usableFromInline
-    init(loggerLabel: String,
-         loggerMetadata: Logging.Logger.Metadata,
-         loggerProvidedMetadata: Logging.Logger.Metadata?,
-         message: Logging.Logger.Message,
-         level: Logging.Logger.Level,
-         metadata: Logging.Logger.Metadata?,
-         source: String,
-         file: String,
-         function: String,
-         line: UInt) {
-        _swiftLogInfo = .init(logger: .init(label: loggerLabel,
-                                            metadataSources: .init(logger: loggerMetadata,
-                                                                   provider: loggerProvidedMetadata)),
-                              message: .init(message: message,
-                                             level: level,
-                                             metadata: metadata,
-                                             source: source))
-        let (ddLogLevel, ddLogFlag) = level.ddLogLevelAndFlag
-        let msg = String(describing: message)
-        super.init(format: msg,
-                   formatted: msg, // We have no chance in retrieving the original format here.
-                   level: ddLogLevel,
-                   flag: ddLogFlag,
-                   context: 0,
-                   file: file,
-                   function: function,
-                   line: line,
-                   tag: nil,
-                   options: .dontCopyMessage, // Swift will bridge to NSString. No need to make an additional copy.
-                   timestamp: nil) // Passing nil will make DDLogMessage create the timestamp which saves us the bridging between Date and NSDate.
-    }
-
-    // Not removed due to `@usableFromInline`.
-    @usableFromInline
-    @available(*, deprecated, renamed: "init(loggerLabel:loggerMetadata:loggerMetadata:message:level:metadata:source:file:function:line:)")
-    convenience init(loggerLabel: String,
-                     loggerMetadata: Logging.Logger.Metadata,
-                     message: Logging.Logger.Message,
-                     level: Logging.Logger.Level,
-                     metadata: Logging.Logger.Metadata?,
-                     source: String,
-                     file: String,
-                     function: String,
-                     line: UInt) {
-        self.init(loggerLabel: loggerLabel,
-                  loggerMetadata: loggerMetadata,
-                  loggerProvidedMetadata: nil,
-                  message: message,
-                  level: level,
-                  metadata: metadata,
-                  source: source,
-                  file: file,
-                  function: function,
-                  line: line)
-    }
-
-    override func isEqual(_ object: Any?) -> Bool {
-        super.isEqual(object) && (object as? SwiftLogMessage)?._swiftLogInfo == _swiftLogInfo
-    }
-}
+public import CocoaLumberjack
+public import Logging
 
 /// A swift-log ``LogHandler`` implementation that forwards messages to a given ``DDLog`` instance.
 public struct DDLogHandler: LogHandler {
@@ -282,17 +133,12 @@ public struct DDLogHandler: LogHandler {
     }
 }
 
-#if swift(>=5.6)
-/// A typealias for the "old" log handler factory.
-public typealias OldLogHandlerFactory = (String) -> any LogHandler
-/// A typealias for the log handler factory.
-public typealias LogHandlerFactory = (String, Logging.Logger.MetadataProvider?) -> any LogHandler
-#else
 /// A typealias for the "old" log handler factory.
-public typealias OldLogHandlerFactory = (String) -> LogHandler
+@preconcurrency
+public typealias OldLogHandlerFactory = @Sendable (String) -> any LogHandler
 /// A typealias for the log handler factory.
-public typealias LogHandlerFactory = (String, Logging.Logger.MetadataProvider?) -> LogHandler
-#endif
+@preconcurrency
+public typealias LogHandlerFactory = @Sendable (String, Logging.Logger.MetadataProvider?) -> any LogHandler
 
 extension DDLogHandler {
     /// The default key to control per message whether to log it synchronous or asynchronous.

+ 83 - 0
Sources/CocoaLumberjackSwiftLogBackend/DDLogMessage+SwiftLogInformation.swift

@@ -0,0 +1,83 @@
+// Software License Agreement (BSD License)
+//
+// Copyright (c) 2010-2025, Deusty, LLC
+// All rights reserved.
+//
+// Redistribution and use of this software in source and binary forms,
+// with or without modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+//   this list of conditions and the following disclaimer.
+//
+// * Neither the name of Deusty nor the names of its contributors may be used
+//   to endorse or promote products derived from this software without specific
+//   prior written permission of Deusty, LLC.
+
+public import CocoaLumberjack
+public import Logging
+
+extension DDLogMessage {
+    /// Contains the swift-log details of a given log message.
+    public struct SwiftLogInformation: Equatable, Sendable {
+        /// Contains information about the swift-log logger that logged this message.
+        public struct LoggerInformation: Equatable, Sendable {
+            /// Contains the metadata from the various sources of on a logger.
+            /// Currently this can be the logger itself, as well as its metadata provider
+            public struct MetadataSources: Equatable, Sendable {
+                /// The metadata of the swift-log logger that logged this message.
+                public let logger: Logging.Logger.Metadata
+                /// The metadata of the metadata provider on the swift-log logger that logged this message.
+                public let provider: Logging.Logger.Metadata?
+            }
+
+            /// The label of the swift-log logger that logged this message.
+            public let label: String
+            /// The metadata of the swift-log logger that logged this message.
+            public let metadataSources: MetadataSources
+
+            /// The metadata of the swift-log logger that logged this message.
+            @available(*, deprecated, renamed: "metadataSources.logger")
+            public var metadata: Logging.Logger.Metadata { metadataSources.logger }
+        }
+
+        /// Contains information about the swift-log message thas was logged.
+        public struct MessageInformation: Equatable, Sendable {
+            /// The original swift-log message.
+            public let message: Logging.Logger.Message
+            /// The original swift-log level of the message. This could be more fine-grained than ``DDLogMessage/level``  & ``DDLogMessage/flag``.
+            public let level: Logging.Logger.Level
+            /// The original swift-log metadata of the message.
+            public let metadata: Logging.Logger.Metadata?
+            /// The original swift-log source of the message.
+            public let source: String
+        }
+
+        /// The information about the swift-log logger that logged this message.
+        public let logger: LoggerInformation
+        /// The information about the swift-log message that was logged.
+        public let message: MessageInformation
+
+        /// Merges the metadata from all layers together.
+        /// The metadata on the logger provides the base.
+        /// Metadata from the logger's metadata provider (if any) trumps the base.
+        /// Metadata from the logged message again trumps both the base and the metadata from the logger's metadata provider.
+        /// Essentially: `logger.metadata < logger.metadataProvider < message.metadata`
+        /// - Note: Accessing this property performs the merge! Accessing it multiple times can be a performance issue!
+        public var mergedMetadata: Logging.Logger.Metadata {
+            var merged = logger.metadataSources.logger
+            if let providerMetadata = logger.metadataSources.provider {
+                merged.merge(providerMetadata, uniquingKeysWith: { $1 })
+            }
+            if let messageMetadata = message.metadata {
+                merged.merge(messageMetadata, uniquingKeysWith: { $1 })
+            }
+            return merged
+        }
+    }
+
+    /// The swift-log information of this log message. This only exists for messages logged via swift-log.
+    @inlinable
+    public var swiftLogInfo: SwiftLogInformation? {
+        (self as? SwiftLogMessage)?._swiftLogInfo
+    }
+}

+ 100 - 0
Sources/CocoaLumberjackSwiftLogBackend/SwiftLogMessage.swift

@@ -0,0 +1,100 @@
+// Software License Agreement (BSD License)
+//
+// Copyright (c) 2010-2025, Deusty, LLC
+// All rights reserved.
+//
+// Redistribution and use of this software in source and binary forms,
+// with or without modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+//   this list of conditions and the following disclaimer.
+//
+// * Neither the name of Deusty nor the names of its contributors may be used
+//   to endorse or promote products derived from this software without specific
+//   prior written permission of Deusty, LLC.
+
+public import CocoaLumberjack
+public import Logging
+
+extension Logging.Logger.Level {
+    @inlinable
+    var ddLogLevelAndFlag: (DDLogLevel, DDLogFlag) {
+        switch self {
+        case .trace: return (.verbose, .verbose)
+        case .debug: return (.debug, .debug)
+        case .info, .notice: return (.info, .info)
+        case .warning: return (.warning, .warning)
+        case .error, .critical: return (.error, .error)
+        @unknown default: return (.error, .error) // better safe than sorry
+        }
+    }
+}
+
+/// This class (intentionally internal) is only an "encapsulation" layer above ``DDLogMessage``.
+/// It's basically an implementation detail of ``DDLogMessage/swiftLogInfo``.
+@usableFromInline
+final class SwiftLogMessage: DDLogMessage, @unchecked Sendable {
+    @usableFromInline
+    let _swiftLogInfo: SwiftLogInformation
+
+    @usableFromInline
+    init(loggerLabel: String,
+         loggerMetadata: Logging.Logger.Metadata,
+         loggerProvidedMetadata: Logging.Logger.Metadata?,
+         message: Logging.Logger.Message,
+         level: Logging.Logger.Level,
+         metadata: Logging.Logger.Metadata?,
+         source: String,
+         file: String,
+         function: String,
+         line: UInt) {
+        _swiftLogInfo = .init(logger: .init(label: loggerLabel,
+                                            metadataSources: .init(logger: loggerMetadata,
+                                                                   provider: loggerProvidedMetadata)),
+                              message: .init(message: message,
+                                             level: level,
+                                             metadata: metadata,
+                                             source: source))
+        let (ddLogLevel, ddLogFlag) = level.ddLogLevelAndFlag
+        let msg = String(describing: message)
+        super.init(format: msg,
+                   formatted: msg, // We have no chance in retrieving the original format here.
+                   level: ddLogLevel,
+                   flag: ddLogFlag,
+                   context: 0,
+                   file: file,
+                   function: function,
+                   line: line,
+                   tag: nil,
+                   options: .dontCopyMessage, // Swift will bridge to NSString. No need to make an additional copy.
+                   timestamp: nil) // Passing nil will make DDLogMessage create the timestamp which saves us the bridging between Date and NSDate.
+    }
+
+    // Not removed due to `@usableFromInline`.
+    @usableFromInline
+    @available(*, deprecated, renamed: "init(loggerLabel:loggerMetadata:loggerMetadata:message:level:metadata:source:file:function:line:)")
+    convenience init(loggerLabel: String,
+                     loggerMetadata: Logging.Logger.Metadata,
+                     message: Logging.Logger.Message,
+                     level: Logging.Logger.Level,
+                     metadata: Logging.Logger.Metadata?,
+                     source: String,
+                     file: String,
+                     function: String,
+                     line: UInt) {
+        self.init(loggerLabel: loggerLabel,
+                  loggerMetadata: loggerMetadata,
+                  loggerProvidedMetadata: nil,
+                  message: message,
+                  level: level,
+                  metadata: metadata,
+                  source: source,
+                  file: file,
+                  function: function,
+                  line: line)
+    }
+
+    override func isEqual(_ object: Any?) -> Bool {
+        super.isEqual(object) && (object as? SwiftLogMessage)?._swiftLogInfo == _swiftLogInfo
+    }
+}