Skip to content
Merged
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
12 changes: 12 additions & 0 deletions .github/workflows/pull_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
1 change: 1 addition & 0 deletions .swiftformat
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
--disable blankLinesAtStartOfScope
--disable hoistPatternLet
--disable redundantType
--disable swiftTestingTestCaseNames
--disable unusedArguments
--disable wrapArguments
--disable wrapPropertyBodies
6 changes: 3 additions & 3 deletions Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand Down
4 changes: 2 additions & 2 deletions Sources/Occurrence/CoreData/CoreDataLogProvider.swift
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
#if canImport(CoreData)
@preconcurrency import CoreData
import Foundation
import Logging
import Mutex
#if canImport(CoreData)
@preconcurrency import CoreData

final class CoreDataLogProvider: LogProvider {

let storeUrl: URL
private let persistentContainer: Mutex<NSPersistentContainer>

Check warning on line 10 in Sources/Occurrence/CoreData/CoreDataLogProvider.swift

View workflow job for this annotation

GitHub Actions / Xcode (tvOS)

'Mutex' was deprecated in tvOS 15.0: use Mutex from Synchronization module

Check warning on line 10 in Sources/Occurrence/CoreData/CoreDataLogProvider.swift

View workflow job for this annotation

GitHub Actions / Xcode (tvOS)

'Mutex' was deprecated in tvOS 15.0: use Mutex from Synchronization module
private let context: Mutex<NSManagedObjectContext>

Check warning on line 11 in Sources/Occurrence/CoreData/CoreDataLogProvider.swift

View workflow job for this annotation

GitHub Actions / Xcode (tvOS)

'Mutex' was deprecated in tvOS 15.0: use Mutex from Synchronization module

init(url: URL? = nil) throws {
storeUrl = try url ?? FileManager.default.defaultDatabaseUrl()
Expand Down
4 changes: 2 additions & 2 deletions Sources/Occurrence/CoreData/LogModel.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Foundation
import Mutex
#if canImport(CoreData)
import CoreData
import Foundation
import Mutex

enum LogModel {
case version_1_0_0
Expand All @@ -16,7 +16,7 @@
}

class Version_1_0_0: NSManagedObjectModel, NSSecureCoding {
private static let protectedState = Mutex(Version_1_0_0())

Check warning on line 19 in Sources/Occurrence/CoreData/LogModel.swift

View workflow job for this annotation

GitHub Actions / Xcode (tvOS)

'Mutex' was deprecated in tvOS 15.0: use Mutex from Synchronization module
/// Provide a singular instance of the model to be referenced. There is a known issue where when referencing
/// a model in an app target, as well as unit tests, a model - and therefore its entities - can be loaded twice.
static var instance: Version_1_0_0 {
Expand Down
4 changes: 2 additions & 2 deletions Sources/Occurrence/CoreData/Logger.Filter+Predicate.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Foundation
import Logging
#if canImport(CoreData)
import CoreData
import Foundation
import Logging

extension Logger.Filter {
var predicate: NSPredicate {
Expand Down
4 changes: 2 additions & 2 deletions Sources/Occurrence/CoreData/ManagedEntry.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Foundation
import Logging
#if canImport(CoreData)
import CoreData
import Foundation
import Logging

@objc(ManagedEntry)
class ManagedEntry: NSManagedObject {
Expand Down
1 change: 0 additions & 1 deletion Sources/Occurrence/Extensions/Array+Occurrence.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import Foundation
import Logging

extension [Any] {
Expand Down
18 changes: 16 additions & 2 deletions Sources/Occurrence/Extensions/Logger+Entry.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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: ", ")
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import Foundation
import Logging

#if hasFeature(RetroactiveAttribute)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import Foundation
import Logging

#if hasFeature(RetroactiveAttribute)
Expand Down
1 change: 0 additions & 1 deletion Sources/Occurrence/LazyLogger.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import Foundation
import Logging

/// Provides an inline way to reference a lazy-loaded instance of `Logger`.
Expand Down
1 change: 0 additions & 1 deletion Sources/Occurrence/LogProvider.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import Foundation
import Logging

/// A source of logging data.
Expand Down
1 change: 0 additions & 1 deletion Sources/Occurrence/LogStreamer.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import Foundation
import Logging

public protocol LogStreamer: Sendable {
Expand Down
3 changes: 0 additions & 3 deletions Sources/Occurrence/LoggableError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
24 changes: 8 additions & 16 deletions Sources/Occurrence/Occurrence.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
public var outputToStorage: Bool = true
}

private static let configurationState: Mutex<Configuration> = Mutex(Configuration())

Check warning on line 13 in Sources/Occurrence/Occurrence.swift

View workflow job for this annotation

GitHub Actions / Xcode (tvOS)

'Mutex' was deprecated in tvOS 15.0: use Mutex from Synchronization module

Check warning on line 13 in Sources/Occurrence/Occurrence.swift

View workflow job for this annotation

GitHub Actions / Xcode (tvOS)

'Mutex' was deprecated in tvOS 15.0: use Mutex from Synchronization module
private static let bootstrapped: Mutex<Bool> = Mutex(false)

Check warning on line 14 in Sources/Occurrence/Occurrence.swift

View workflow job for this annotation

GitHub Actions / Xcode (tvOS)

'Mutex' was deprecated in tvOS 15.0: use Mutex from Synchronization module

Check warning on line 14 in Sources/Occurrence/Occurrence.swift

View workflow job for this annotation

GitHub Actions / Xcode (tvOS)

'Mutex' was deprecated in tvOS 15.0: use Mutex from Synchronization module

public static var configuration: Configuration {
get {
Expand Down Expand Up @@ -73,16 +73,8 @@
}
}

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):
Expand All @@ -95,13 +87,13 @@

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 {
Expand Down
1 change: 0 additions & 1 deletion Sources/Occurrence/SQLite/Logger.Filter+Where.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import Foundation
import Logging
import Statement
import StatementSQLite
Expand Down
2 changes: 1 addition & 1 deletion Sources/Occurrence/SwiftUI/LogEntryView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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))

Expand Down
7 changes: 6 additions & 1 deletion Tests/OccurrenceTests/LogProviderTests.swift
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -83,7 +87,7 @@ final class LogProviderTests {
}
}
} catch {
print(error.localizedDescription)
print(String(describing: error))
}
}

Expand Down Expand Up @@ -398,3 +402,4 @@ final class LogProviderTests {
#expect(entries == ["fourteen", "thirteen"])
}
}
#endif
16 changes: 2 additions & 14 deletions Tests/OccurrenceTests/LoggableErrorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
}
}
6 changes: 4 additions & 2 deletions Tests/OccurrenceTests/OccurrenceTests.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#if !canImport(Android)
import Logging
@testable import Occurrence
import Testing
Expand Down Expand Up @@ -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: <REDACTED> }
[🔎 INFO | com.richardpiazza.occurrence | OccurrenceTests OccurrenceTests.swift | dictionaryConvenience() 40] Dictionary { context: XCTestCase, label: count, value: <REDACTED> }
"""

#expect(description == output)
Expand All @@ -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: <REDACTED> }
[🔎 INFO | com.richardpiazza.occurrence | OccurrenceTests OccurrenceTests.swift | encodableConvenience() 63] Encodable { context: XCTestCase, id: 123, name: <REDACTED> }
"""

#expect(description == output)
}
}
#endif
Loading