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.
This commit is contained in:
T. R. Bernstein
2026-03-02 18:53:17 +01:00
parent e3f2ba03ef
commit d22556d04e
3 changed files with 55 additions and 7 deletions

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

@@ -103,11 +103,6 @@ public struct LoggingOSLog: LogHandler {
guard let metadata = metadata else { continue }
metadataAggregator.merge(metadata) { return $1 }
}
return Self.joinedMetadata(metadataAggregator)
}
private static func joinedMetadata(_ metadata: Logging.Logger.Metadata, with separator: String = ", ") -> String? {
guard !metadata.isEmpty else { return nil }
return metadata.map { "\"\($0)\": \"\($1)\"" }.joined(separator: separator)
return metadataAggregator.asJSON()
}
}