-
Notifications
You must be signed in to change notification settings - Fork 10.5k
/
Copy pathLogger.swift
176 lines (150 loc) · 5.05 KB
/
Logger.swift
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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
//===--- Logger.swift -----------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2024 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
import Foundation
public final class Logger: @unchecked Sendable {
private let stateLock = Lock()
private let outputLock = Lock()
private var _hadError = false
public var hadError: Bool {
get { stateLock.withLock { _hadError } }
set { stateLock.withLock { _hadError = newValue } }
}
private var _logLevel: LogLevel = .debug
public var logLevel: LogLevel {
get { stateLock.withLock { _logLevel } }
set { stateLock.withLock { _logLevel = newValue } }
}
private var _useColor: Bool = true
public var useColor: Bool {
get { stateLock.withLock { _useColor } }
set { stateLock.withLock { _useColor = newValue } }
}
private var _output: LoggableStream?
public var output: LoggableStream? {
get { stateLock.withLock { _output } }
set { stateLock.withLock { _output = newValue } }
}
public init() {}
}
extension Logger {
public enum LogLevel: Comparable {
/// A message with information that isn't useful to the user, but is
/// useful when debugging issues.
case debug
/// A message with mundane information that may be useful to know if
/// you're interested in verbose output, but is otherwise unimportant.
case info
/// A message with information that does not require any intervention from
/// the user, but is nonetheless something they may want to be aware of.
case note
/// A message that describes an issue that ought to be resolved by the
/// user, but still allows the program to exit successfully.
case warning
/// A message that describes an issue where the program cannot exit
/// successfully.
case error
}
private func log(_ message: @autoclosure () -> String, level: LogLevel) {
guard level >= logLevel else { return }
let output = self.output ?? FileHandleStream.stderr
let useColor = self.useColor && output.supportsColor
outputLock.withLock {
level.write(to: output, useColor: useColor)
output.write(": \(message())\n")
}
}
public func debug(_ message: @autoclosure () -> String) {
log(message(), level: .debug)
}
public func info(_ message: @autoclosure () -> String) {
log(message(), level: .info)
}
public func note(_ message: @autoclosure () -> String) {
log(message(), level: .note)
}
public func warning(_ message: @autoclosure () -> String) {
log(message(), level: .warning)
}
public func error(_ message: @autoclosure () -> String) {
hadError = true
log(message(), level: .error)
}
}
public protocol Loggable {
func write(to stream: LoggableStream, useColor: Bool)
}
extension Logger.LogLevel: Loggable, CustomStringConvertible {
public var description: String {
switch self {
case .debug: "debug"
case .info: "info"
case .note: "note"
case .warning: "warning"
case .error: "error"
}
}
private var ansiColor: AnsiColor {
switch self {
case .debug: .magenta
case .info: .blue
case .note: .brightCyan
case .warning: .brightYellow
case .error: .brightRed
}
}
public func write(to stream: LoggableStream, useColor: Bool) {
let str = useColor
? "\(fg: ansiColor)\(weight: .bold)\(self)\(fg: .normal)\(weight: .normal)"
: "\(self)"
stream.write(str)
}
}
public protocol LoggableStream: Sendable {
var supportsColor: Bool { get }
func write(_: String)
}
/// Check whether $TERM supports color. Ideally we'd consult terminfo, but
/// there aren't any particularly nice APIs for that in the SDK AFAIK. We could
/// shell out to tput, but that adds ~100ms of overhead which I don't think is
/// worth it. This simple check (taken from LLVM) is good enough for now.
fileprivate let termSupportsColor: Bool = {
guard let termEnv = getenv("TERM") else { return false }
switch String(cString: termEnv) {
case "ansi", "cygwin", "linux":
return true
case let term where
term.hasPrefix("screen") ||
term.hasPrefix("xterm") ||
term.hasPrefix("vt100") ||
term.hasPrefix("rxvt") ||
term.hasSuffix("color"):
return true
default:
return false
}
}()
public struct FileHandleStream: LoggableStream, @unchecked Sendable {
public let handle: UnsafeMutablePointer<FILE>
public let supportsColor: Bool
public init(_ handle: UnsafeMutablePointer<FILE>) {
self.handle = handle
self.supportsColor = isatty(fileno(handle)) != 0 && termSupportsColor
}
public func write(_ string: String) {
fputs(string, handle)
}
}
extension FileHandleStream {
static let stdout = Self(Darwin.stdout)
static let stderr = Self(Darwin.stderr)
}
public let log = Logger()