diff --git a/shell_integration/MacOSX/NextcloudFileProviderKit/Sources/NextcloudFileProviderKit/Log/FileProviderLog.swift b/shell_integration/MacOSX/NextcloudFileProviderKit/Sources/NextcloudFileProviderKit/Log/FileProviderLog.swift index 487154684fc9b..19c5cfb557aba 100644 --- a/shell_integration/MacOSX/NextcloudFileProviderKit/Sources/NextcloudFileProviderKit/Log/FileProviderLog.swift +++ b/shell_integration/MacOSX/NextcloudFileProviderKit/Sources/NextcloudFileProviderKit/Log/FileProviderLog.swift @@ -153,18 +153,18 @@ public actor FileProviderLog: FileProviderLogging { let newFile = logsDirectory.appendingPathComponent(name, isDirectory: false) if fileManager.createFile(atPath: newFile.path, contents: nil) == false { - logger.error("Failed to create new log file at: \"\(newFile.path)\".") + logger.error("Failed to create new log file at: \"\(newFile.path, privacy: .public)\".") return } else { - logger.debug("Created new log file at: \"\(newFile.path)\".") + logger.debug("Created new log file at: \"\(newFile.path, privacy: .public)\".") } do { file = newFile handle = try FileHandle(forWritingTo: newFile) - logger.debug("Opened new log file for writing at: \"\(newFile.path)\".") + logger.debug("Opened new log file for writing at: \"\(newFile.path, privacy: .public)\".") } catch { - logger.error("Failed to open new log file at \"\(newFile.path)\" for writing: \(error.localizedDescription, privacy: .public)") + logger.error("Failed to open new log file at \"\(newFile.path, privacy: .public)\" for writing: \(error.localizedDescription, privacy: .public)") } // Clean up old log files (older than 24 hours) @@ -212,9 +212,9 @@ public actor FileProviderLog: FileProviderLogging { } } - private func writeToUnifiedLoggingSystem(level: OSLogType, message: String, details: [FileProviderLogDetailKey: (any Sendable)?]) { + private func writeToUnifiedLoggingSystem(level: OSLogType, message: String, details: [FileProviderLogDetailKey: (any Sendable)?], file: StaticString, function: StaticString, line: UInt) { if details.isEmpty { - logger.log(level: level, "\(message, privacy: .public)") + logger.log(level: level, "\(message, privacy: .public)\n\n\(file, privacy: .public):\(line, privacy: .public) \(function, privacy: .public)") return } @@ -249,13 +249,13 @@ public actor FileProviderLog: FileProviderLogging { return "- \(key.rawValue): \(valueDescription)" } - logger.log(level: level, "\(message, privacy: .public)\n\n\(detailDescriptions.joined(separator: "\n"), privacy: .public)") + logger.log(level: level, "\(message, privacy: .public)\n\n\(detailDescriptions.joined(separator: "\n"), privacy: .public)\n\n\(file, privacy: .public):\(line, privacy: .public) \(function, privacy: .public)") } - public func write(category: String, level: OSLogType, message: String, details: [FileProviderLogDetailKey: (any Sendable)?]) { + public func write(category: String, level: OSLogType, message: String, details: [FileProviderLogDetailKey: (any Sendable)?], file: StaticString, function: StaticString, line: UInt) { #if DEBUG - writeToUnifiedLoggingSystem(level: level, message: message, details: details) + writeToUnifiedLoggingSystem(level: level, message: message, details: details, file: file, function: function, line: line) #else @@ -288,7 +288,7 @@ public actor FileProviderLog: FileProviderLogging { let date = Date() let formattedDate = messageDateFormatter.string(from: date) - let entry = FileProviderLogMessage(category: category, date: formattedDate, details: details, level: levelDescription, message: message) + let entry = FileProviderLogMessage(category: category, date: formattedDate, details: details, level: levelDescription, message: message, file: file, function: function, line: line) do { let object = try encoder.encode(entry) diff --git a/shell_integration/MacOSX/NextcloudFileProviderKit/Sources/NextcloudFileProviderKit/Log/FileProviderLogMessage.swift b/shell_integration/MacOSX/NextcloudFileProviderKit/Sources/NextcloudFileProviderKit/Log/FileProviderLogMessage.swift index 22f8e17ecd87b..9faca7d425368 100644 --- a/shell_integration/MacOSX/NextcloudFileProviderKit/Sources/NextcloudFileProviderKit/Log/FileProviderLogMessage.swift +++ b/shell_integration/MacOSX/NextcloudFileProviderKit/Sources/NextcloudFileProviderKit/Log/FileProviderLogMessage.swift @@ -7,6 +7,17 @@ import Foundation /// A data model for the rich JSON object to be written into the JSON lines log files. /// public struct FileProviderLogMessage: Encodable { + enum CodingKeys: CodingKey { + case category + case date + case details + case file + case function + case level + case line + case message + } + /// /// As used with `Logger` of the `os` framework. /// @@ -25,11 +36,26 @@ public struct FileProviderLogMessage: Encodable { /// public let details: [String: FileProviderLogDetail?] + /// + /// The source code file which generates the log message. + /// + public let file: String? + + /// + /// The calling function generating this message. + /// + public let function: String? + /// /// Textual representation of the associated `OSLogType`. /// public let level: String + /// + /// The line in the source code file which generates this message. + /// + public let line: UInt? + /// /// The actual text for the entry. /// @@ -38,7 +64,7 @@ public struct FileProviderLogMessage: Encodable { /// /// Custom initializer to support arbitrary types as detail values. /// - init(category: String, date: String, details: [FileProviderLogDetailKey: Any?], level: String, message: String) { + init(category: String, date: String, details: [FileProviderLogDetailKey: Any?], level: String, message: String, file: StaticString, function: StaticString, line: UInt) { self.category = category self.date = date @@ -51,5 +77,30 @@ public struct FileProviderLogMessage: Encodable { self.details = transformedDetails self.level = level self.message = message + + #if DEBUG + self.file = String("\(file)") + self.function = String("\(function)") + self.line = line + #else + self.file = nil + self.function = nil + self.line = nil + #endif + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(category, forKey: .category) + try container.encode(date, forKey: .date) + try container.encode(details, forKey: .details) + try container.encode(level, forKey: .level) + try container.encode(message, forKey: .message) + + #if DEBUG + try container.encode(file, forKey: .file) + try container.encode(function, forKey: .function) + try container.encode(line, forKey: .line) + #endif } } diff --git a/shell_integration/MacOSX/NextcloudFileProviderKit/Sources/NextcloudFileProviderKit/Log/FileProviderLogger.swift b/shell_integration/MacOSX/NextcloudFileProviderKit/Sources/NextcloudFileProviderKit/Log/FileProviderLogger.swift index 72c939fde9ccb..a2c15fca99ed0 100644 --- a/shell_integration/MacOSX/NextcloudFileProviderKit/Sources/NextcloudFileProviderKit/Log/FileProviderLogger.swift +++ b/shell_integration/MacOSX/NextcloudFileProviderKit/Sources/NextcloudFileProviderKit/Log/FileProviderLogger.swift @@ -37,12 +37,15 @@ public struct FileProviderLogger: Sendable { /// Dispatch a task to write a message with the level `OSLogType.debug`. /// /// - Parameters: - /// - message: The main text message of the entry in the logs. - /// - details: Additional contextual data. + /// - message: A human-readable message, preferably generic and without interpolations. The `details` argument is for arguments. + /// - details: Structured and contextual details about a message. + /// - file: Implementations should have `#filePath` as the default value for this. + /// - function: Implementations should have `#function` as the default value for this. + /// - line: Implementations should have `#line` as the default value for this. /// - public func debug(_ message: String, _ details: [FileProviderLogDetailKey: (any Sendable)?] = [:]) { + public func debug(_ message: String, _ details: [FileProviderLogDetailKey: (any Sendable)?] = [:], file: StaticString = #filePath, function: StaticString = #function, line: UInt = #line) { Task { - await log.write(category: category, level: .debug, message: message, details: details) + await log.write(category: category, level: .debug, message: message, details: details, file: file, function: function, line: line) } } @@ -50,12 +53,15 @@ public struct FileProviderLogger: Sendable { /// Dispatch a task to write a message with the level `OSLogType.info`. /// /// - Parameters: - /// - message: The main text message of the entry in the logs. - /// - details: Additional contextual data. + /// - message: A human-readable message, preferably generic and without interpolations. The `details` argument is for arguments. + /// - details: Structured and contextual details about a message. + /// - file: Implementations should have `#filePath` as the default value for this. + /// - function: Implementations should have `#function` as the default value for this. + /// - line: Implementations should have `#line` as the default value for this. /// - public func info(_ message: String, _ details: [FileProviderLogDetailKey: (any Sendable)?] = [:]) { + public func info(_ message: String, _ details: [FileProviderLogDetailKey: (any Sendable)?] = [:], file: StaticString = #filePath, function: StaticString = #function, line: UInt = #line) { Task { - await log.write(category: category, level: .info, message: message, details: details) + await log.write(category: category, level: .info, message: message, details: details, file: file, function: function, line: line) } } @@ -63,12 +69,15 @@ public struct FileProviderLogger: Sendable { /// Dispatch a task to write a message with the level `OSLogType.error`. /// /// - Parameters: - /// - message: The main text message of the entry in the logs. - /// - details: Additional contextual data. + /// - message: A human-readable message, preferably generic and without interpolations. The `details` argument is for arguments. + /// - details: Structured and contextual details about a message. + /// - file: Implementations should have `#filePath` as the default value for this. + /// - function: Implementations should have `#function` as the default value for this. + /// - line: Implementations should have `#line` as the default value for this. /// - public func error(_ message: String, _ details: [FileProviderLogDetailKey: (any Sendable)?] = [:]) { + public func error(_ message: String, _ details: [FileProviderLogDetailKey: (any Sendable)?] = [:], file: StaticString = #filePath, function: StaticString = #function, line: UInt = #line) { Task { - await log.write(category: category, level: .error, message: message, details: details) + await log.write(category: category, level: .error, message: message, details: details, file: file, function: function, line: line) } } @@ -76,12 +85,15 @@ public struct FileProviderLogger: Sendable { /// Dispatch a task to write a message with the level `OSLogType.fault`. /// /// - Parameters: - /// - message: The main text message of the entry in the logs. - /// - details: Additional contextual data. + /// - message: A human-readable message, preferably generic and without interpolations. The `details` argument is for arguments. + /// - details: Structured and contextual details about a message. + /// - file: Implementations should have `#filePath` as the default value for this. + /// - function: Implementations should have `#function` as the default value for this. + /// - line: Implementations should have `#line` as the default value for this. /// - public func fault(_ message: String, _ details: [FileProviderLogDetailKey: (any Sendable)?] = [:]) { + public func fault(_ message: String, _ details: [FileProviderLogDetailKey: (any Sendable)?] = [:], file: StaticString = #filePath, function: StaticString = #function, line: UInt = #line) { Task { - await log.write(category: category, level: .fault, message: message, details: details) + await log.write(category: category, level: .fault, message: message, details: details, file: file, function: function, line: line) } } } diff --git a/shell_integration/MacOSX/NextcloudFileProviderKit/Sources/NextcloudFileProviderKit/Log/FileProviderLogging.swift b/shell_integration/MacOSX/NextcloudFileProviderKit/Sources/NextcloudFileProviderKit/Log/FileProviderLogging.swift index 7870750ab2ec3..1a78e148febe8 100644 --- a/shell_integration/MacOSX/NextcloudFileProviderKit/Sources/NextcloudFileProviderKit/Log/FileProviderLogging.swift +++ b/shell_integration/MacOSX/NextcloudFileProviderKit/Sources/NextcloudFileProviderKit/Log/FileProviderLogging.swift @@ -12,5 +12,14 @@ public protocol FileProviderLogging: Actor { /// /// Usually, you do not need or want to use this but the methods provided by ``FileProviderLogger`` instead. /// - func write(category: String, level: OSLogType, message: String, details: [FileProviderLogDetailKey: (any Sendable)?]) + /// - Parameters: + /// - category: The unified logging category to use. Usually, this is the logging type. + /// - level: The severity of the message. + /// - message: A human-readable message, preferably generic and without interpolations. The `details` argument is for arguments. + /// - details: Structured and contextual details about a message. + /// - file: Implementations should have `#filePath` as the default value for this. + /// - function: Implementations should have `#function` as the default value for this. + /// - line: Implementations should have `#line` as the default value for this. + /// + func write(category: String, level: OSLogType, message: String, details: [FileProviderLogDetailKey: (any Sendable)?], file: StaticString, function: StaticString, line: UInt) } diff --git a/shell_integration/MacOSX/NextcloudFileProviderKit/Sources/NextcloudFileProviderKitMocks/FileProviderLogMock.swift b/shell_integration/MacOSX/NextcloudFileProviderKit/Sources/NextcloudFileProviderKitMocks/FileProviderLogMock.swift index 65124516fba02..f04a2e7d31d23 100644 --- a/shell_integration/MacOSX/NextcloudFileProviderKit/Sources/NextcloudFileProviderKitMocks/FileProviderLogMock.swift +++ b/shell_integration/MacOSX/NextcloudFileProviderKit/Sources/NextcloudFileProviderKitMocks/FileProviderLogMock.swift @@ -12,7 +12,7 @@ public actor FileProviderLogMock: FileProviderLogging { logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "FileProviderLogMock") } - public func write(category _: String, level _: OSLogType, message: String, details _: [FileProviderLogDetailKey: (any Sendable)?]) { + public func write(category _: String, level _: OSLogType, message: String, details _: [FileProviderLogDetailKey: (any Sendable)?], file _: StaticString, function _: StaticString, line _: UInt) { logger.debug("\(message, privacy: .public)") } }