1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
|
//
// OSLogHandler.swift
// OSLogHandler
//
// Created by pronebird on 16/08/2021.
// Copyright © 2025 Mullvad VPN AB. All rights reserved.
//
import Foundation
import Logging
import os
public struct OSLogHandler: LogHandler {
public var metadata: Logging.Logger.Metadata = [:]
public var logLevel: Logging.Logger.Level = .debug
private let label: String
private let osLog: OSLog
private struct RegistryKey: Hashable {
let subsystem: String
let category: String
}
nonisolated(unsafe) private static var osLogRegistry: [RegistryKey: OSLog] = [:]
private static let registryLock = NSLock()
private static func getOSLog(subsystem: String, category: String) -> OSLog {
registryLock.lock()
defer { registryLock.unlock() }
let key = RegistryKey(subsystem: subsystem, category: category)
if let log = osLogRegistry[key] {
return log
} else {
let newLog = OSLog(subsystem: subsystem, category: category)
osLogRegistry[key] = newLog
return newLog
}
}
public init(subsystem: String, category: String) {
label = category
osLog = OSLogHandler.getOSLog(subsystem: subsystem, category: category)
}
public subscript(metadataKey metadataKey: String) -> Logging.Logger.Metadata.Value? {
get {
metadata[metadataKey]
}
set(newValue) {
metadata[metadataKey] = newValue
}
}
public func log(
level: Logging.Logger.Level,
message: Logging.Logger.Message,
metadata: Logging.Logger.Metadata?,
source: String,
file: String,
function: String,
line: UInt
) {
let mergedMetadata = self.metadata
.merging(metadata ?? [:]) { _, rhs -> Logging.Logger.MetadataValue in
rhs
}
let prettyMetadata = Self.formatMetadata(mergedMetadata)
let logMessage = prettyMetadata.isEmpty ? message : "\(prettyMetadata) \(message)"
os_log("%{public}s", log: osLog, type: level.osLogType, "\(logMessage)")
}
private static func formatMetadata(_ metadata: Logging.Logger.Metadata) -> String {
metadata.map { "\($0)=\($1)" }.joined(separator: " ")
}
}
private extension Logging.Logger.Level {
var osLogType: OSLogType {
switch self {
case .trace, .debug:
// Console app does not output .debug logs, use .info instead.
return .info
case .info, .notice, .warning:
return .info
case .error, .critical:
return .error
}
}
}
|