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()
|