commit 3b0bf5d595fe5c476228544530bf513ddc9c03e3 Author: T. R. Bernstein Date: Fri Feb 27 22:18:00 2026 +0100 Initial implementation diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b7f1399 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.build \ No newline at end of file diff --git a/Package.resolved b/Package.resolved new file mode 100644 index 0000000..b05e4dd --- /dev/null +++ b/Package.resolved @@ -0,0 +1,15 @@ +{ + "originHash" : "041d5a9b2664fe8fba9d0ff5826e7f1955cca0b22d288fe2f2383df27258b52a", + "pins" : [ + { + "identity" : "swift-log", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-log", + "state" : { + "revision" : "bbd81b6725ae874c69e9b8c8804d462356b55523", + "version" : "1.10.1" + } + } + ], + "version" : 3 +} diff --git a/Package.swift b/Package.swift new file mode 100644 index 0000000..dd80349 --- /dev/null +++ b/Package.swift @@ -0,0 +1,31 @@ +// swift-tools-version: 6.0 +import PackageDescription + +let package = Package( + name: "swift-log-oslog", + platforms: [.macOS(.v11), .iOS(.v14), .tvOS(.v14), .watchOS(.v6)], + products: [ + .library( + name: "LoggingOSLog", + targets: ["LoggingOSLog"] + ) + ], + dependencies: [ + .package(url: "https://github.com/apple/swift-log", from: "1.10.1"), + ], + targets: [ + .target( + name: "LoggingOSLog", + dependencies: [.product(name: "Logging", package: "swift-log")], + path: "Sources/Logging OSLog" + ), + .testTarget( + name: "LoggingOSLogTests", + dependencies: [ + .product(name: "Logging", package: "swift-log"), + "LoggingOSLog" + ], + path: "Tests/Logging OSLog Tests", + ), + ] +) diff --git a/Sources/Logging OSLog/Logging OSLog.swift b/Sources/Logging OSLog/Logging OSLog.swift new file mode 100644 index 0000000..a393951 --- /dev/null +++ b/Sources/Logging OSLog/Logging OSLog.swift @@ -0,0 +1,75 @@ +import Foundation +import os +import Logging + +public struct LoggingOSLog: LogHandler { + public var logLevel: Logging.Logger.Level = .info + public var metadata: Logging.Logger.Metadata = [:] + public var metadataProvider: Logging.Logger.MetadataProvider? + private let oslogger: os.Logger + + public init(label: String) { + let lastDotPosition = label.lastIndex(of: ".") ?? label.startIndex + let frontPart = label.prefix(upTo: lastDotPosition) + let backPart = label.suffix(from: lastDotPosition) + guard frontPart.count > 0 else { + let subsystem = Bundle.main.bundleIdentifier ?? "SwiftLogToOsLog" + self.init(subsystem: subsystem, category: label) + return + } + self.init(subsystem: String(frontPart), category: String(backPart)) + } + + public init( + subsystem: String = Bundle.main.bundleIdentifier ?? "SwiftLogToOsLog", + category: String, + logLevel: Logging.Logger.Level = .debug + ) { + self.oslogger = os.Logger(subsystem: subsystem, category: category) + self.logLevel = logLevel + } + + public init(oslog: os.Logger, logLevel: Logging.Logger.Level = .debug) { + self.oslogger = oslog + self.logLevel = logLevel + } + + public func log( + level: Logging.Logger.Level, + message: Logging.Logger.Message, + metadata: Logging.Logger.Metadata?, + source: String, + file: String, + function: String, + line: UInt + ) { + let metadataCSV = Self.joinMetadata(self.metadata, self.metadataProvider?.get(), metadata) + let messageParts = [message.description, metadataCSV] + + let message = messageParts.compactMap { $0 }.joined(separator: " -> ") + self.oslogger.log(level: OSLogType.from(loggerLevel: level), "\(message)") + } + + public subscript(metadataKey metadataKey: String) -> Logging.Logger.Metadata.Value? { + get { + return self.metadata[metadataKey] + } + set { + self.metadata[metadataKey] = newValue + } + } + + private static func joinMetadata(_ metadataList: Logging.Logger.Metadata?...) -> String? { + var metadataAggregator: Logging.Logger.Metadata = [:] + for metadata in metadataList { + guard let metadata = metadata else { continue } + metadataAggregator.merge(metadata) { return $1 } + } + return Self.joinMetadata(metadataAggregator) + } + + private static func joinMetadata(_ metadata: Logging.Logger.Metadata, with separator: String = ", ") -> String? { + guard !metadata.isEmpty else { return nil } + return metadata.map { "\($0) = \($1)" }.joined(separator: separator) + } +} \ No newline at end of file diff --git a/Sources/Logging OSLog/OSLog Extension.swift b/Sources/Logging OSLog/OSLog Extension.swift new file mode 100644 index 0000000..4b5f24b --- /dev/null +++ b/Sources/Logging OSLog/OSLog Extension.swift @@ -0,0 +1,23 @@ +import Logging +import os + +extension OSLogType { + static func from(loggerLevel: Logging.Logger.Level) -> Self { + switch loggerLevel { + case .trace: + return .debug + case .debug: + return .debug + case .info: + return .info + case .notice: + return .default + case .warning: + return .info + case .error: + return .error + case .critical: + return .fault + } + } +} \ No newline at end of file diff --git a/Tests/Logging OSLog Tests/Logging Test.swift b/Tests/Logging OSLog Tests/Logging Test.swift new file mode 100644 index 0000000..e475144 --- /dev/null +++ b/Tests/Logging OSLog Tests/Logging Test.swift @@ -0,0 +1,16 @@ +import Testing +import Logging +import LoggingOSLog + +@Test func isConstructable() { + let _ = LoggingOSLog(subsystem: "de.astzweig.app", category: "Image Processing") +} + +@Test func canAddAsLoggingBackend() { + LoggingSystem.bootstrap(LoggingOSLog.init) +} + +@Test func canLogMessage() { + let logger = Logging.Logger(label: "de.astzweig.loggingoslog.Test") + logger.info("Test message") +} \ No newline at end of file