6 Commits
1.0.0 ... 2.0.0

Author SHA1 Message Date
T. R. Bernstein
e18692fd4d Separate metadata by a unicode arrow in log
Use a single unicode right arrow instead of the two char ASCII right
arrow to separate the metadata from the actual log message. This is just
a preference decision and has no functional background.
2026-03-02 19:38:45 +01:00
T. R. Bernstein
b48562c2b0 Describe JSON serialization of metadata in docs 2026-03-02 19:37:55 +01:00
T. R. Bernstein
d22556d04e Output metadata as JSON
Metadata shall be machine readable (see SwiftLog/Structured Logging) and
the previous metadata serialization erased the nesting structure.
The JSON output ensures that the metadata can be quickly analyzed by either a
copy and paste into a JSON editor or a small CLI pipeline.

See https://swiftpackageindex.com/apple/swift-log/1.10.1/documentation/logging/002-structuredlogging
for more information on metadata.
2026-03-02 18:53:17 +01:00
T. R. Bernstein
e3f2ba03ef Adapt internal API to Swift API Design Guidelines 2026-03-02 16:46:05 +01:00
T. R. Bernstein
e957caf031 Fix indention error in SPI config 2026-03-02 16:06:21 +01:00
T. R. Bernstein
48fdc144c1 Fix message reduction error
The unified logging system (aka OSLog) expects a OSLogMessage and does
not allow just passing in a string variable. Instead the variable has to
be interpolated into a string literal, which sets the default privacy
setting to 'private' and hence reducts the whole message.
This commit sets the privacy setting to 'public'.
2026-03-02 16:06:13 +01:00
5 changed files with 75 additions and 12 deletions

View File

@@ -66,6 +66,13 @@ func init() {
"main": Logger(label: "com.example.yourapp.Main"), "main": Logger(label: "com.example.yourapp.Main"),
"mail": Logger(label: "com.example.yourapp.Mail System"), "mail": Logger(label: "com.example.yourapp.Mail System"),
] ]
// And using loggers
logger["main"]!.debug("Starting build",
metadata: [
"input.output_directory": "\(output)",
"input.data": "\(data ?? "-")"
])
} }
``` ```
@@ -78,6 +85,14 @@ library maps a reverse domain style label to the subsystem and category
parameters of the unified logging system. See parameters of the unified logging system. See
``/LoggingOSLog/LoggingOSLog/init(label:)`` for more information. ``/LoggingOSLog/LoggingOSLog/init(label:)`` for more information.
Metadata is appended as JSON to the logged message after a simple unicode right
arrow ("→").
### What this library does not supply
This library does not implement any of the interpolation privacy features of
the unified logging system, i.e. there is no way to redact or align a meta
information, like the `Logging` API would allow.
[subsystem]: https://developer.apple.com/documentation/os/generating-log-messages-from-your-code#:~:text=The%20subsystem%20string,for%20each%20subsystem%20string. [subsystem]: https://developer.apple.com/documentation/os/generating-log-messages-from-your-code#:~:text=The%20subsystem%20string,for%20each%20subsystem%20string.
[category]: https://developer.apple.com/documentation/os/generating-log-messages-from-your-code#:~:text=The%20category%20string,for%20these%20strings. [category]: https://developer.apple.com/documentation/os/generating-log-messages-from-your-code#:~:text=The%20category%20string,for%20these%20strings.

View File

@@ -0,0 +1,41 @@
import Logging
extension Logging.Logger.Metadata {
public func asJSON() -> String {
return Self.asJSON(self)
}
private static func asJSON(_ metadata: Logging.Logger.Metadata) -> String {
var outputParts: [String: String] = [:]
for (key, value) in metadata {
let jsonKey = Self.escapeForJSON(key)
outputParts[jsonKey] = Self.asJSON(value)
}
return "{" + outputParts.map { "\"\($0)\": \($1)" }.joined(separator: ", ") + "}"
}
private static func asJSON(_ metadata: [Logging.Logger.MetadataValue]) -> String {
var outputParts: [String] = []
for item in metadata {
outputParts.append(Self.asJSON(item))
}
return "[" + outputParts.joined(separator: ", ") + "]"
}
private static func asJSON(_ metadata: Logging.Logger.MetadataValue) -> String {
switch metadata {
case .dictionary(let subvalues):
return Self.asJSON(subvalues)
case .array(let subvalues):
return Self.asJSON(subvalues)
case .string(let subvalue):
return "\"" + Self.escapeForJSON(subvalue) + "\""
case .stringConvertible(let subvalue):
return "\"" + Self.escapeForJSON("\(subvalue)") + "\""
}
}
private static func escapeForJSON(_ data: String) -> String {
return data.replacingOccurrences(of: "\"", with: "\\\"")
}
}

View File

@@ -78,11 +78,11 @@ public struct LoggingOSLog: LogHandler {
function: String, function: String,
line: UInt line: UInt
) { ) {
let metadataCSV = Self.joinMetadata(self.metadata, self.metadataProvider?.get(), metadata) let metadataCSV = Self.joinedMetadata(self.metadata, self.metadataProvider?.get(), metadata)
let messageParts = [message.description, metadataCSV] let messageParts = [message.description, metadataCSV]
let message = messageParts.compactMap { $0 }.joined(separator: " -> ") let message = messageParts.compactMap { $0 }.joined(separator: " ")
self.oslogger.log(level: OSLogType.from(loggerLevel: level), "\(message)") self.oslogger.log(level: OSLogType.from(loggerLevel: level), "\(message, privacy: .public)")
} }
/** /**
@@ -97,17 +97,12 @@ public struct LoggingOSLog: LogHandler {
} }
} }
private static func joinMetadata(_ metadataList: Logging.Logger.Metadata?...) -> String? { private static func joinedMetadata(_ metadataList: Logging.Logger.Metadata?...) -> String? {
var metadataAggregator: Logging.Logger.Metadata = [:] var metadataAggregator: Logging.Logger.Metadata = [:]
for metadata in metadataList { for metadata in metadataList {
guard let metadata = metadata else { continue } guard let metadata = metadata else { continue }
metadataAggregator.merge(metadata) { return $1 } metadataAggregator.merge(metadata) { return $1 }
} }
return Self.joinMetadata(metadataAggregator) return metadataAggregator.asJSON()
}
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)
} }
} }

View File

@@ -14,3 +14,15 @@ import LoggingOSLog
let logger = Logging.Logger(label: "de.astzweig.loggingoslog.Test") let logger = Logging.Logger(label: "de.astzweig.loggingoslog.Test")
logger.info("Test message") logger.info("Test message")
} }
@Test func canLogMessageWithMetadata() {
let logger = Logging.Logger(label: "de.astzweig.loggingoslog.Test")
logger.info("Test message", metadata: [
"request.id": "20140801",
"request.dirname": "\"Impossible\"",
"request.authorization": [
"bearer": "empty"
],
"request.values": ["1", "rootID", ["key": "value"]]
])
}