diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index d7db51c..a6042cf 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -84,3 +84,15 @@ jobs: env: DESTINATION: "generic/platform=${{ matrix.platform }}" run: set -o pipefail && xcodebuild build -scheme "$SCHEME" -destination "$DESTINATION" | xcbeautify + + Android: + strategy: + fail-fast: false + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v6 + - name: Build & Test + uses: skiptools/swift-android-action@v2 + with: + free-disk-space: true diff --git a/.swiftformat b/.swiftformat index 321250d..f47882e 100644 --- a/.swiftformat +++ b/.swiftformat @@ -11,6 +11,7 @@ --disable blankLinesAtStartOfScope --disable hoistPatternLet --disable redundantType +--disable swiftTestingTestCaseNames --disable unusedArguments --disable wrapArguments --disable wrapPropertyBodies diff --git a/Package.resolved b/Package.resolved index 534d0c6..41e32e6 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "48c9daa9bc6b9b0058632d48d5d17f18202b68c77a6fdce81dd258d2321ca03c", + "originHash" : "bda727af496f33ba05f6c8424a8dd2dbd27ee363794fc499fb819c3e97dbfd8a", "pins" : [ { "identity" : "sqlite.swift", @@ -24,8 +24,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-log.git", "state" : { - "revision" : "2778fd4e5a12a8aaa30a3ee8285f4ce54c5f3181", - "version" : "1.9.1" + "revision" : "3ffafb9722d5d918c614feb496c8789a3b59d222", + "version" : "1.15.0" } }, { diff --git a/Package.swift b/Package.swift index ad6df6f..a9716f3 100644 --- a/Package.swift +++ b/Package.swift @@ -22,7 +22,7 @@ let package = Package( dependencies: [ // Dependencies declare other packages that this package depends on. // .package(url: /* package url */, from: "1.0.0"), - .package(url: "https://github.com/apple/swift-log.git", from: "1.9.1"), + .package(url: "https://github.com/apple/swift-log.git", from: "1.15.0"), .package(url: "https://github.com/richardpiazza/Statement.git", from: "0.8.1"), .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.16.0"), .package(url: "https://github.com/swhitty/swift-mutex.git", from: "0.0.6"), diff --git a/Sources/Occurrence/CoreData/CoreDataLogProvider.swift b/Sources/Occurrence/CoreData/CoreDataLogProvider.swift index 4225a8e..7a5fa5a 100644 --- a/Sources/Occurrence/CoreData/CoreDataLogProvider.swift +++ b/Sources/Occurrence/CoreData/CoreDataLogProvider.swift @@ -1,8 +1,8 @@ +#if canImport(CoreData) +@preconcurrency import CoreData import Foundation import Logging import Mutex -#if canImport(CoreData) -@preconcurrency import CoreData final class CoreDataLogProvider: LogProvider { diff --git a/Sources/Occurrence/CoreData/LogModel.swift b/Sources/Occurrence/CoreData/LogModel.swift index 3086da1..b228f61 100644 --- a/Sources/Occurrence/CoreData/LogModel.swift +++ b/Sources/Occurrence/CoreData/LogModel.swift @@ -1,7 +1,7 @@ -import Foundation -import Mutex #if canImport(CoreData) import CoreData +import Foundation +import Mutex enum LogModel { case version_1_0_0 diff --git a/Sources/Occurrence/CoreData/Logger.Filter+Predicate.swift b/Sources/Occurrence/CoreData/Logger.Filter+Predicate.swift index 665880f..25ff567 100644 --- a/Sources/Occurrence/CoreData/Logger.Filter+Predicate.swift +++ b/Sources/Occurrence/CoreData/Logger.Filter+Predicate.swift @@ -1,7 +1,7 @@ -import Foundation -import Logging #if canImport(CoreData) import CoreData +import Foundation +import Logging extension Logger.Filter { var predicate: NSPredicate { diff --git a/Sources/Occurrence/CoreData/ManagedEntry.swift b/Sources/Occurrence/CoreData/ManagedEntry.swift index 85eb94d..2723e7e 100644 --- a/Sources/Occurrence/CoreData/ManagedEntry.swift +++ b/Sources/Occurrence/CoreData/ManagedEntry.swift @@ -1,7 +1,7 @@ -import Foundation -import Logging #if canImport(CoreData) import CoreData +import Foundation +import Logging @objc(ManagedEntry) class ManagedEntry: NSManagedObject { diff --git a/Sources/Occurrence/Extensions/Array+Occurrence.swift b/Sources/Occurrence/Extensions/Array+Occurrence.swift index f4b3e7a..85d5b92 100644 --- a/Sources/Occurrence/Extensions/Array+Occurrence.swift +++ b/Sources/Occurrence/Extensions/Array+Occurrence.swift @@ -1,4 +1,3 @@ -import Foundation import Logging extension [Any] { diff --git a/Sources/Occurrence/Extensions/Logger+Entry.swift b/Sources/Occurrence/Extensions/Logger+Entry.swift index 73bb579..0f73540 100644 --- a/Sources/Occurrence/Extensions/Logger+Entry.swift +++ b/Sources/Occurrence/Extensions/Logger+Entry.swift @@ -4,12 +4,15 @@ import Logging public extension Logger { struct Entry: Codable, Sendable { + #if canImport(ObjectiveC) + @available(*, deprecated) public static let gmtDateFormatter: DateFormatter = { let formatter = DateFormatter() formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" formatter.timeZone = TimeZone(secondsFromGMT: 0) return formatter }() + #endif public let date: Date public let subsystem: Subsystem @@ -72,9 +75,8 @@ public extension Logger { extension Logger.Entry: CustomStringConvertible { public var description: String { - let _date = Self.gmtDateFormatter.string(from: date) let sourceFile = [source, fileName].filter { !$0.isEmpty }.joined(separator: " ") - let output = "[\(_date) \(level.fancyDescription) | \(subsystem) | \(sourceFile) | \(function) \(line)] \(message)" + let output = "[\(date.formatted(Self.formatStyle)) \(level.fancyDescription) | \(subsystem) | \(sourceFile) | \(function) \(line)] \(message)" if let metadata { let sortedMetadata = metadata.sorted(by: { $0.key < $1.key }) let values = sortedMetadata.map { "\($0.key): \($0.value)" }.joined(separator: ", ") @@ -86,6 +88,18 @@ extension Logger.Entry: CustomStringConvertible { } public extension Logger.Entry { + /// Format style that uses: `yyyy-MM-dd'T'HH:mm:ss.SSS'Z'` + static var formatStyle: Date.ISO8601FormatStyle { + .iso8601 + .month() + .day() + .year() + .dateSeparator(.dash) + .time(includingFractionalSeconds: true) + .timeSeparator(.colon) + .timeZone(separator: .omitted) + } + /// Attempts to extract only the last path component of the `file` var fileName: String { URL(fileURLWithPath: file).lastPathComponent diff --git a/Sources/Occurrence/Extensions/Logger.Message+Occurrence.swift b/Sources/Occurrence/Extensions/Logger.Message+Occurrence.swift index 87f4591..8d75ea2 100644 --- a/Sources/Occurrence/Extensions/Logger.Message+Occurrence.swift +++ b/Sources/Occurrence/Extensions/Logger.Message+Occurrence.swift @@ -1,4 +1,3 @@ -import Foundation import Logging #if hasFeature(RetroactiveAttribute) diff --git a/Sources/Occurrence/Extensions/Logger.MetadataValue+Occurrence.swift b/Sources/Occurrence/Extensions/Logger.MetadataValue+Occurrence.swift index 7ec6e57..91264d4 100644 --- a/Sources/Occurrence/Extensions/Logger.MetadataValue+Occurrence.swift +++ b/Sources/Occurrence/Extensions/Logger.MetadataValue+Occurrence.swift @@ -1,4 +1,3 @@ -import Foundation import Logging #if hasFeature(RetroactiveAttribute) diff --git a/Sources/Occurrence/LazyLogger.swift b/Sources/Occurrence/LazyLogger.swift index 9dcb41c..7bd7f34 100644 --- a/Sources/Occurrence/LazyLogger.swift +++ b/Sources/Occurrence/LazyLogger.swift @@ -1,4 +1,3 @@ -import Foundation import Logging /// Provides an inline way to reference a lazy-loaded instance of `Logger`. diff --git a/Sources/Occurrence/LogProvider.swift b/Sources/Occurrence/LogProvider.swift index 840abba..5f304b8 100644 --- a/Sources/Occurrence/LogProvider.swift +++ b/Sources/Occurrence/LogProvider.swift @@ -1,4 +1,3 @@ -import Foundation import Logging /// A source of logging data. diff --git a/Sources/Occurrence/LogStreamer.swift b/Sources/Occurrence/LogStreamer.swift index 3d2a448..c0b9251 100644 --- a/Sources/Occurrence/LogStreamer.swift +++ b/Sources/Occurrence/LogStreamer.swift @@ -1,4 +1,3 @@ -import Foundation import Logging public protocol LogStreamer: Sendable { diff --git a/Sources/Occurrence/LoggableError.swift b/Sources/Occurrence/LoggableError.swift index d93ee1f..5c428ac 100644 --- a/Sources/Occurrence/LoggableError.swift +++ b/Sources/Occurrence/LoggableError.swift @@ -55,10 +55,7 @@ public extension LoggableError { if let localizedError = self as? any LocalizedError { if let errorDescription = localizedError.errorDescription { meta[.localizedDescription] = .string(errorDescription) - } else { - meta[.localizedDescription] = .string(localizedError.localizedDescription) } - if let failureReason = localizedError.failureReason { meta[.localizedFailureReason] = .string(failureReason) } diff --git a/Sources/Occurrence/Occurrence.swift b/Sources/Occurrence/Occurrence.swift index e5d2688..98fce28 100644 --- a/Sources/Occurrence/Occurrence.swift +++ b/Sources/Occurrence/Occurrence.swift @@ -73,16 +73,8 @@ public struct Occurrence: LogHandler { } } - public func log( - level: Logger.Level, - message: Logger.Message, - metadata: Logger.Metadata?, - source: String, - file: String, - function: String, - line: UInt, - ) { - let joinedMetadata: Logger.Metadata? = switch (metadata, metadataProvider?.get()) { + public func log(event: LogEvent) { + let joinedMetadata: Logger.Metadata? = switch (event.metadata, metadataProvider?.get()) { case (.some(let instance), .some(let context)): instance.merging(context, uniquingKeysWith: { instanceValue, _ in instanceValue }) case (.some(let instance), .none): @@ -95,13 +87,13 @@ public struct Occurrence: LogHandler { let entry = Logger.Entry( subsystem: Logger.Subsystem(stringLiteral: label), - level: level, - message: message, + level: event.level, + message: event.message, metadata: joinedMetadata, - source: source, - file: file, - function: function, - line: line, + source: event.source, + file: event.file, + function: event.function, + line: event.line, ) if Self.configuration.outputToConsole { diff --git a/Sources/Occurrence/SQLite/Logger.Filter+Where.swift b/Sources/Occurrence/SQLite/Logger.Filter+Where.swift index da51aa9..2f3032a 100644 --- a/Sources/Occurrence/SQLite/Logger.Filter+Where.swift +++ b/Sources/Occurrence/SQLite/Logger.Filter+Where.swift @@ -1,4 +1,3 @@ -import Foundation import Logging import Statement import StatementSQLite diff --git a/Sources/Occurrence/SwiftUI/LogEntryView.swift b/Sources/Occurrence/SwiftUI/LogEntryView.swift index f3e329e..ea4d28b 100644 --- a/Sources/Occurrence/SwiftUI/LogEntryView.swift +++ b/Sources/Occurrence/SwiftUI/LogEntryView.swift @@ -23,7 +23,7 @@ struct LogEntryView: View { HStack { Text(entry.level.fancyDescription) - Text(Logger.Entry.gmtDateFormatter.string(from: entry.date)) + Text(entry.date, format: Logger.Entry.formatStyle) } .font(.system(size: 10, weight: .semibold, design: .monospaced)) diff --git a/Tests/OccurrenceTests/LogProviderTests.swift b/Tests/OccurrenceTests/LogProviderTests.swift index a8e509c..c565f40 100644 --- a/Tests/OccurrenceTests/LogProviderTests.swift +++ b/Tests/OccurrenceTests/LogProviderTests.swift @@ -1,7 +1,11 @@ +#if !canImport(Android) #if canImport(CoreData) import CoreData #endif import Foundation +#if canImport(FoundationNetworking) +import FoundationNetworking +#endif import Logging @testable import Occurrence import Testing @@ -83,7 +87,7 @@ final class LogProviderTests { } } } catch { - print(error.localizedDescription) + print(String(describing: error)) } } @@ -398,3 +402,4 @@ final class LogProviderTests { #expect(entries == ["fourteen", "thirteen"]) } } +#endif diff --git a/Tests/OccurrenceTests/LoggableErrorTests.swift b/Tests/OccurrenceTests/LoggableErrorTests.swift index 0797db6..c3c2bfe 100644 --- a/Tests/OccurrenceTests/LoggableErrorTests.swift +++ b/Tests/OccurrenceTests/LoggableErrorTests.swift @@ -95,23 +95,17 @@ struct LoggableErrorTests { ) let error = EncodingError.invalidValue(FileRef(), context) let metadata = error.metadata - #expect(metadata.count == 4) + #expect(metadata.count == 3) let domain = try #require(metadata[.domain]?.stringValue) let code = try #require(metadata[.code]?.intValue) let userInfo = try #require(metadata[.userInfo]?.dictionaryValue) let description = try #require(userInfo[.description]?.stringValue) - let localizedDescription = try #require(metadata[.localizedDescription]?.stringValue) #expect(domain == "SwiftEncodingErrorDomain") #expect(code == 0) #expect(userInfo.count == 1) #expect(description == #"Encoding (Invalid Value) - Value: FileRef(description: "Some File"), Context: Bad data. ["description", "file"]"#) - #if os(Linux) - #expect(localizedDescription == "The operation could not be completed. (SwiftEncodingErrorDomain error 0.)") - #else - #expect(localizedDescription == "The data couldn’t be written because it isn’t in the correct format.") - #endif } @Test func decodingError() throws { @@ -121,22 +115,16 @@ struct LoggableErrorTests { ) let error = DecodingError.typeMismatch(Int.self, context) let metadata = error.metadata - #expect(metadata.count == 4) + #expect(metadata.count == 3) let domain = try #require(metadata[.domain]?.stringValue) let code = try #require(metadata[.code]?.intValue) let userInfo = try #require(metadata[.userInfo]?.dictionaryValue) let description = try #require(userInfo[.description]?.stringValue) - let localizedDescription = try #require(metadata[.localizedDescription]?.stringValue) #expect(domain == "SwiftDecodingErrorDomain") #expect(code == 0) #expect(userInfo.count == 1) #expect(description == #"Decoding (Type Mismatch) - Type: Int, Context: Unexpected type. ["code"]"#) - #if os(Linux) - #expect(localizedDescription == "The operation could not be completed. (SwiftDecodingErrorDomain error 0.)") - #else - #expect(localizedDescription == "The data couldn’t be read because it isn’t in the correct format.") - #endif } } diff --git a/Tests/OccurrenceTests/OccurrenceTests.swift b/Tests/OccurrenceTests/OccurrenceTests.swift index ef6097f..d341208 100644 --- a/Tests/OccurrenceTests/OccurrenceTests.swift +++ b/Tests/OccurrenceTests/OccurrenceTests.swift @@ -1,3 +1,4 @@ +#if !canImport(Android) import Logging @testable import Occurrence import Testing @@ -47,7 +48,7 @@ final class OccurrenceTests { description.replaceSubrange(first ... last, with: "") let output = """ - [🔎 INFO | com.richardpiazza.occurrence | OccurrenceTests OccurrenceTests.swift | dictionaryConvenience() 39] Dictionary { context: XCTestCase, label: count, value: } + [🔎 INFO | com.richardpiazza.occurrence | OccurrenceTests OccurrenceTests.swift | dictionaryConvenience() 40] Dictionary { context: XCTestCase, label: count, value: } """ #expect(description == output) @@ -70,9 +71,10 @@ final class OccurrenceTests { description.replaceSubrange(first ... last, with: "") let output = """ - [🔎 INFO | com.richardpiazza.occurrence | OccurrenceTests OccurrenceTests.swift | encodableConvenience() 62] Encodable { context: XCTestCase, id: 123, name: } + [🔎 INFO | com.richardpiazza.occurrence | OccurrenceTests OccurrenceTests.swift | encodableConvenience() 63] Encodable { context: XCTestCase, id: 123, name: } """ #expect(description == output) } } +#endif