CommandLineToolkit
121 строка · 4.5 Кб
1/*
2* Copyright (c) Avito Tech LLC
3*/
4
5import DateProvider
6import CLTLoggingModels
7import Foundation
8import ProcessController
9
10/// # Philosophy behind a contextual logging system
11/// Each layer of a software might want to log different information on top of other information added by layers on top.
12/// Example:
13///
14/// * `main` entrypoint might put some metadata to a logger: pid, process name
15/// * `command` being executed might add a command name, its arguments, probably user input
16/// * `process executor` which is being used by a `command` may add `subprocessId` and `subprocessName` to loggable messages.
17///
18/// In order to achieve this, `main` may create its instance of `ContextualLogger` and pass it down to the objects
19/// `command` may get the instance from `main`, and obtain its own instance by adding metadata. `ContextualLogger` here works like a factory.
20/// When executing subprocess, `command` will pass its `ContextualLogger` to `process executor`.
21/// `process executor` will again append its own metadata and use new instance to log its stuff.
22/// This way metadata can be derived between layers of software, extending it where needed, and still allowing layers to log data with its set of metadata without being affected by other layers.
23public final class ContextualLogger {
24private let dateProvider: DateProvider
25private let loggerHandler: LoggerHandler
26private let metadata: [String: String?]
27
28public enum ContextKeys: String, CaseIterable {
29/// Id of a subprocess started by current process
30case subprocessId
31
32/// Name of a subprocess started by current process
33case subprocessName
34
35/// process id
36case processId
37
38/// process name
39case processName
40
41/// Hostname where process is being executed
42case hostname
43
44/// If subprocess is started via `xcrun`, this key contains a name of a launched tool, e.g. `simctl`
45case xcrunToolName
46
47public static func stringSetForAllRawValues() -> Set<String> {
48Set(allCases.map { $0.rawValue })
49}
50}
51
52public static let noOp: ContextualLogger = ContextualLogger(
53dateProvider: SystemDateProvider(),
54loggerHandler: AggregatedLoggerHandler(handlers: []),
55metadata: [:]
56)
57
58public init(
59dateProvider: DateProvider,
60loggerHandler: LoggerHandler,
61metadata: [String: String?]
62) {
63self.dateProvider = dateProvider
64self.loggerHandler = loggerHandler
65self.metadata = metadata
66}
67
68public func withMetadata(_ keyValues: [String: String?]) -> ContextualLogger {
69var metadata = self.metadata
70metadata.merge(keyValues) { _, new -> String? in new }
71return ContextualLogger(dateProvider: dateProvider, loggerHandler: loggerHandler, metadata: metadata)
72}
73
74public func withMetadata(key: String, value: String?) -> ContextualLogger {
75var metadata = self.metadata
76metadata[key] = value
77return ContextualLogger(dateProvider: dateProvider, loggerHandler: loggerHandler, metadata: metadata)
78}
79
80public func withMetadata(_ coordinate: LogEntryCoordinate) -> ContextualLogger {
81var metadata = self.metadata
82metadata[coordinate.name] = coordinate.value
83return ContextualLogger(dateProvider: dateProvider, loggerHandler: loggerHandler, metadata: metadata)
84}
85
86public func withMetadata(key: ContextKeys, value: String?) -> ContextualLogger {
87withMetadata(key: key.rawValue, value: value)
88}
89
90public func log(
91_ verbosity: Verbosity,
92_ message: String,
93subprocessPidInfo: PidInfo?,
94source: String?,
95file: String,
96function: String,
97line: UInt
98) {
99var flattenedMetadata = self.metadata
100
101if let subprocessPidInfo = subprocessPidInfo {
102flattenedMetadata[ContextKeys.subprocessId.rawValue] = "\(subprocessPidInfo.pid)"
103flattenedMetadata[ContextKeys.subprocessName.rawValue] = "\(subprocessPidInfo.name)"
104}
105
106let coordinates = flattenedMetadata.map { (key: String, value: String?) in
107LogEntryCoordinate(name: key, value: value)
108}
109
110let logEntry = LogEntry(
111file: file,
112line: line,
113coordinates: coordinates,
114message: message,
115timestamp: dateProvider.currentDate(),
116verbosity: verbosity
117)
118
119loggerHandler.handle(logEntry: logEntry)
120}
121}
122