Initial commit

This commit is contained in:
oscarz
2024-08-12 10:49:20 +08:00
parent 3002510aaf
commit 00fd0adf89
331 changed files with 53210 additions and 130 deletions
+257
View File
@@ -0,0 +1,257 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift Logging API open source project
//
// Copyright (c) 2018-2019 Apple Inc. and the Swift Logging API project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of Swift Logging API project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
import Darwin
#elseif os(Windows)
import WinSDK
#else
import Glibc
#endif
/// A threading lock based on `libpthread` instead of `libdispatch`.
///
/// This object provides a lock on top of a single `pthread_mutex_t`. This kind
/// of lock is safe to use with `libpthread`-based threading models, such as the
/// one used by NIO. On Windows, the lock is based on the substantially similar
/// `SRWLOCK` type.
internal final class Lock {
#if os(Windows)
fileprivate let mutex: UnsafeMutablePointer<SRWLOCK> =
UnsafeMutablePointer.allocate(capacity: 1)
#else
fileprivate let mutex: UnsafeMutablePointer<pthread_mutex_t> =
UnsafeMutablePointer.allocate(capacity: 1)
#endif
/// Create a new lock.
public init() {
#if os(Windows)
InitializeSRWLock(self.mutex)
#else
var attr = pthread_mutexattr_t()
pthread_mutexattr_init(&attr)
pthread_mutexattr_settype(&attr, .init(PTHREAD_MUTEX_ERRORCHECK))
let err = pthread_mutex_init(self.mutex, &attr)
precondition(err == 0, "\(#function) failed in pthread_mutex with error \(err)")
#endif
}
deinit {
#if os(Windows)
// SRWLOCK does not need to be free'd
#else
let err = pthread_mutex_destroy(self.mutex)
precondition(err == 0, "\(#function) failed in pthread_mutex with error \(err)")
#endif
self.mutex.deallocate()
}
/// Acquire the lock.
///
/// Whenever possible, consider using `withLock` instead of this method and
/// `unlock`, to simplify lock handling.
public func lock() {
#if os(Windows)
AcquireSRWLockExclusive(self.mutex)
#else
let err = pthread_mutex_lock(self.mutex)
precondition(err == 0, "\(#function) failed in pthread_mutex with error \(err)")
#endif
}
/// Release the lock.
///
/// Whenever possible, consider using `withLock` instead of this method and
/// `lock`, to simplify lock handling.
public func unlock() {
#if os(Windows)
ReleaseSRWLockExclusive(self.mutex)
#else
let err = pthread_mutex_unlock(self.mutex)
precondition(err == 0, "\(#function) failed in pthread_mutex with error \(err)")
#endif
}
}
extension Lock {
/// Acquire the lock for the duration of the given block.
///
/// This convenience method should be preferred to `lock` and `unlock` in
/// most situations, as it ensures that the lock will be released regardless
/// of how `body` exits.
///
/// - Parameter body: The block to execute while holding the lock.
/// - Returns: The value returned by the block.
@inlinable
internal func withLock<T>(_ body: () throws -> T) rethrows -> T {
self.lock()
defer {
self.unlock()
}
return try body()
}
// specialise Void return (for performance)
@inlinable
internal func withLockVoid(_ body: () throws -> Void) rethrows {
try self.withLock(body)
}
}
/// A reader/writer threading lock based on `libpthread` instead of `libdispatch`.
///
/// This object provides a lock on top of a single `pthread_rwlock_t`. This kind
/// of lock is safe to use with `libpthread`-based threading models, such as the
/// one used by NIO. On Windows, the lock is based on the substantially similar
/// `SRWLOCK` type.
internal final class ReadWriteLock {
#if os(Windows)
fileprivate let rwlock: UnsafeMutablePointer<SRWLOCK> =
UnsafeMutablePointer.allocate(capacity: 1)
fileprivate var shared: Bool = true
#else
fileprivate let rwlock: UnsafeMutablePointer<pthread_rwlock_t> =
UnsafeMutablePointer.allocate(capacity: 1)
#endif
/// Create a new lock.
public init() {
#if os(Windows)
InitializeSRWLock(self.rwlock)
#else
let err = pthread_rwlock_init(self.rwlock, nil)
precondition(err == 0, "\(#function) failed in pthread_rwlock with error \(err)")
#endif
}
deinit {
#if os(Windows)
// SRWLOCK does not need to be free'd
#else
let err = pthread_rwlock_destroy(self.rwlock)
precondition(err == 0, "\(#function) failed in pthread_rwlock with error \(err)")
#endif
self.rwlock.deallocate()
}
/// Acquire a reader lock.
///
/// Whenever possible, consider using `withReaderLock` instead of this
/// method and `unlock`, to simplify lock handling.
public func lockRead() {
#if os(Windows)
AcquireSRWLockShared(self.rwlock)
self.shared = true
#else
let err = pthread_rwlock_rdlock(self.rwlock)
precondition(err == 0, "\(#function) failed in pthread_rwlock with error \(err)")
#endif
}
/// Acquire a writer lock.
///
/// Whenever possible, consider using `withWriterLock` instead of this
/// method and `unlock`, to simplify lock handling.
public func lockWrite() {
#if os(Windows)
AcquireSRWLockExclusive(self.rwlock)
self.shared = false
#else
let err = pthread_rwlock_wrlock(self.rwlock)
precondition(err == 0, "\(#function) failed in pthread_rwlock with error \(err)")
#endif
}
/// Release the lock.
///
/// Whenever possible, consider using `withReaderLock` and `withWriterLock`
/// instead of this method and `lockRead` and `lockWrite`, to simplify lock
/// handling.
public func unlock() {
#if os(Windows)
if self.shared {
ReleaseSRWLockShared(self.rwlock)
} else {
ReleaseSRWLockExclusive(self.rwlock)
}
#else
let err = pthread_rwlock_unlock(self.rwlock)
precondition(err == 0, "\(#function) failed in pthread_rwlock with error \(err)")
#endif
}
}
extension ReadWriteLock {
/// Acquire the reader lock for the duration of the given block.
///
/// This convenience method should be preferred to `lockRead` and `unlock`
/// in most situations, as it ensures that the lock will be released
/// regardless of how `body` exits.
///
/// - Parameter body: The block to execute while holding the reader lock.
/// - Returns: The value returned by the block.
@inlinable
internal func withReaderLock<T>(_ body: () throws -> T) rethrows -> T {
self.lockRead()
defer {
self.unlock()
}
return try body()
}
/// Acquire the writer lock for the duration of the given block.
///
/// This convenience method should be preferred to `lockWrite` and `unlock`
/// in most situations, as it ensures that the lock will be released
/// regardless of how `body` exits.
///
/// - Parameter body: The block to execute while holding the writer lock.
/// - Returns: The value returned by the block.
@inlinable
internal func withWriterLock<T>(_ body: () throws -> T) rethrows -> T {
self.lockWrite()
defer {
self.unlock()
}
return try body()
}
// specialise Void return (for performance)
@inlinable
internal func withReaderLockVoid(_ body: () throws -> Void) rethrows {
try self.withReaderLock(body)
}
// specialise Void return (for performance)
@inlinable
internal func withWriterLockVoid(_ body: () throws -> Void) rethrows {
try self.withWriterLock(body)
}
}
+188
View File
@@ -0,0 +1,188 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift Logging API open source project
//
// Copyright (c) 2018-2019 Apple Inc. and the Swift Logging API project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of Swift Logging API project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
/// A `LogHandler` is an implementation of a logging backend.
///
/// This type is an implementation detail and should not normally be used, unless implementing your own logging backend.
/// To use the SwiftLog API, please refer to the documentation of `Logger`.
///
/// # Implementation requirements
///
/// To implement your own `LogHandler` you should respect a few requirements that are necessary so applications work
/// as expected regardless of the selected `LogHandler` implementation.
///
/// - The `LogHandler` must be a `struct`.
/// - The metadata and `logLevel` properties must be implemented so that setting them on a `Logger` does not affect
/// other `Logger`s.
///
/// ### Treat log level & metadata as values
///
/// When developing your `LogHandler`, please make sure the following test works.
///
/// ```swift
/// LoggingSystem.bootstrap(MyLogHandler.init) // your LogHandler might have a different bootstrapping step
/// var logger1 = Logger(label: "first logger")
/// logger1.logLevel = .debug
/// logger1[metadataKey: "only-on"] = "first"
///
/// var logger2 = logger1
/// logger2.logLevel = .error // this must not override `logger1`'s log level
/// logger2[metadataKey: "only-on"] = "second" // this must not override `logger1`'s metadata
///
/// XCTAssertEqual(.debug, logger1.logLevel)
/// XCTAssertEqual(.error, logger2.logLevel)
/// XCTAssertEqual("first", logger1[metadataKey: "only-on"])
/// XCTAssertEqual("second", logger2[metadataKey: "only-on"])
/// ```
///
/// ### Special cases
///
/// In certain special cases, the log level behaving like a value on `Logger` might not be what you want. For example,
/// you might want to set the log level across _all_ `Logger`s to `.debug` when say a signal (eg. `SIGUSR1`) is received
/// to be able to debug special failures in production. This special case is acceptable but we urge you to create a
/// solution specific to your `LogHandler` implementation to achieve that. Please find an example implementation of this
/// behavior below, on reception of the signal you would call
/// `LogHandlerWithGlobalLogLevelOverride.overrideGlobalLogLevel = .debug`, for example.
///
/// ```swift
/// import class Foundation.NSLock
///
/// public struct LogHandlerWithGlobalLogLevelOverride: LogHandler {
/// // the static properties hold the globally overridden log level (if overridden)
/// private static let overrideLock = NSLock()
/// private static var overrideLogLevel: Logger.Level? = nil
///
/// // this holds the log level if not overridden
/// private var _logLevel: Logger.Level = .info
///
/// // metadata storage
/// public var metadata: Logger.Metadata = [:]
///
/// public init(label: String) {
/// // [...]
/// }
///
/// public var logLevel: Logger.Level {
/// // when we get asked for the log level, we check if it was globally overridden or not
/// get {
/// LogHandlerWithGlobalLogLevelOverride.overrideLock.lock()
/// defer { LogHandlerWithGlobalLogLevelOverride.overrideLock.unlock() }
/// return LogHandlerWithGlobalLogLevelOverride.overrideLogLevel ?? self._logLevel
/// }
/// // we set the log level whenever we're asked (note: this might not have an effect if globally
/// // overridden)
/// set {
/// self._logLevel = newValue
/// }
/// }
///
/// public func log(level: Logger.Level, message: Logger.Message, metadata: Logger.Metadata?,
/// source: String, file: String, function: String, line: UInt) {
/// // [...]
/// }
///
/// public subscript(metadataKey metadataKey: String) -> Logger.Metadata.Value? {
/// get {
/// return self.metadata[metadataKey]
/// }
/// set(newValue) {
/// self.metadata[metadataKey] = newValue
/// }
/// }
///
/// // this is the function to globally override the log level, it is not part of the `LogHandler` protocol
/// public static func overrideGlobalLogLevel(_ logLevel: Logger.Level) {
/// LogHandlerWithGlobalLogLevelOverride.overrideLock.lock()
/// defer { LogHandlerWithGlobalLogLevelOverride.overrideLock.unlock() }
/// LogHandlerWithGlobalLogLevelOverride.overrideLogLevel = logLevel
/// }
/// }
/// ```
///
/// Please note that the above `LogHandler` will still pass the 'log level is a value' test above it iff the global log
/// level has not been overridden. And most importantly it passes the requirement listed above: A change to the log
/// level on one `Logger` should not affect the log level of another `Logger` variable.
public protocol LogHandler {
/// This method is called when a `LogHandler` must emit a log message. There is no need for the `LogHandler` to
/// check if the `level` is above or below the configured `logLevel` as `Logger` already performed this check and
/// determined that a message should be logged.
///
/// - parameters:
/// - level: The log level the message was logged at.
/// - message: The message to log. To obtain a `String` representation call `message.description`.
/// - metadata: The metadata associated to this log message.
/// - source: The source where the log message originated, for example the logging module.
/// - file: The file the log message was emitted from.
/// - function: The function the log line was emitted from.
/// - line: The line the log message was emitted from.
func log(level: Logger.Level,
message: Logger.Message,
metadata: Logger.Metadata?,
source: String,
file: String,
function: String,
line: UInt)
/// SwiftLog 1.0 compatibility method. Please do _not_ implement, implement
/// `log(level:message:metadata:source:file:function:line:)` instead.
@available(*, deprecated, renamed: "log(level:message:metadata:source:file:function:line:)")
func log(level: Logging.Logger.Level, message: Logging.Logger.Message, metadata: Logging.Logger.Metadata?, file: String, function: String, line: UInt)
/// Add, remove, or change the logging metadata.
///
/// - note: `LogHandler`s must treat logging metadata as a value type. This means that the change in metadata must
/// only affect this very `LogHandler`.
///
/// - parameters:
/// - metadataKey: The key for the metadata item
subscript(metadataKey _: String) -> Logger.Metadata.Value? { get set }
/// Get or set the entire metadata storage as a dictionary.
///
/// - note: `LogHandler`s must treat logging metadata as a value type. This means that the change in metadata must
/// only affect this very `LogHandler`.
var metadata: Logger.Metadata { get set }
/// Get or set the configured log level.
///
/// - note: `LogHandler`s must treat the log level as a value type. This means that the change in metadata must
/// only affect this very `LogHandler`. It is acceptable to provide some form of global log level override
/// that means a change in log level on a particular `LogHandler` might not be reflected in any
/// `LogHandler`.
var logLevel: Logger.Level { get set }
}
extension LogHandler {
@available(*, deprecated, message: "You should implement this method instead of using the default implementation")
public func log(level: Logger.Level,
message: Logger.Message,
metadata: Logger.Metadata?,
source: String,
file: String,
function: String,
line: UInt) {
self.log(level: level, message: message, metadata: metadata, file: file, function: function, line: line)
}
@available(*, deprecated, renamed: "log(level:message:metadata:source:file:function:line:)")
public func log(level: Logging.Logger.Level, message: Logging.Logger.Message, metadata: Logging.Logger.Metadata?, file: String, function: String, line: UInt) {
self.log(level: level,
message: message,
metadata: metadata,
source: Logger.currentModule(filePath: file),
file: file,
function: function,
line: line)
}
}
+826
View File
@@ -0,0 +1,826 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift Logging API open source project
//
// Copyright (c) 2018-2019 Apple Inc. and the Swift Logging API project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of Swift Logging API project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
import Darwin
#elseif os(Windows)
import MSVCRT
#else
import Glibc
#endif
/// A `Logger` is the central type in `SwiftLog`. Its central function is to emit log messages using one of the methods
/// corresponding to a log level.
///
/// `Logger`s are value types with respect to the `logLevel` and the `metadata` (as well as the immutable `label`
/// and the selected `LogHandler`). Therefore, `Logger`s are suitable to be passed around between libraries if you want
/// to preserve metadata across libraries.
///
/// The most basic usage of a `Logger` is
///
/// logger.info("Hello World!")
///
public struct Logger {
@usableFromInline
var handler: LogHandler
/// An identifier of the creator of this `Logger`.
public let label: String
internal init(label: String, _ handler: LogHandler) {
self.label = label
self.handler = handler
}
}
extension Logger {
/// Log a message passing the log level as a parameter.
///
/// If the `logLevel` passed to this method is more severe than the `Logger`'s `logLevel`, it will be logged,
/// otherwise nothing will happen.
///
/// - parameters:
/// - level: The log level to log `message` at. For the available log levels, see `Logger.Level`.
/// - message: The message to be logged. `message` can be used with any string interpolation literal.
/// - metadata: One-off metadata to attach to this log message.
/// - source: The source this log messages originates to. Currently, it defaults to the folder containing the
/// file that is emitting the log message, which usually is the module.
/// - file: The file this log message originates from (there's usually no need to pass it explicitly as it
/// defaults to `#file`).
/// - function: The function this log message originates from (there's usually no need to pass it explicitly as
/// it defaults to `#function`).
/// - line: The line this log message originates from (there's usually no need to pass it explicitly as it
/// defaults to `#line`).
@inlinable
public func log(level: Logger.Level,
_ message: @autoclosure () -> Logger.Message,
metadata: @autoclosure () -> Logger.Metadata? = nil,
source: @autoclosure () -> String? = nil,
file: String = #file, function: String = #function, line: UInt = #line) {
if self.logLevel <= level {
self.handler.log(level: level,
message: message(),
metadata: metadata(),
source: source() ?? Logger.currentModule(filePath: (file)),
file: file, function: function, line: line)
}
}
/// Add, change, or remove a logging metadata item.
///
/// - note: Logging metadata behaves as a value that means a change to the logging metadata will only affect the
/// very `Logger` it was changed on.
@inlinable
public subscript(metadataKey metadataKey: String) -> Logger.Metadata.Value? {
get {
return self.handler[metadataKey: metadataKey]
}
set {
self.handler[metadataKey: metadataKey] = newValue
}
}
/// Get or set the log level configured for this `Logger`.
///
/// - note: `Logger`s treat `logLevel` as a value. This means that a change in `logLevel` will only affect this
/// very `Logger`. It it acceptable for logging backends to have some form of global log level override
/// that affects multiple or even all loggers. This means a change in `logLevel` to one `Logger` might in
/// certain cases have no effect.
@inlinable
public var logLevel: Logger.Level {
get {
return self.handler.logLevel
}
set {
self.handler.logLevel = newValue
}
}
}
extension Logger {
/// Log a message passing with the `Logger.Level.trace` log level.
///
/// If `.trace` is at least as severe as the `Logger`'s `logLevel`, it will be logged,
/// otherwise nothing will happen.
///
/// - parameters:
/// - message: The message to be logged. `message` can be used with any string interpolation literal.
/// - metadata: One-off metadata to attach to this log message
/// - source: The source this log messages originates to. Currently, it defaults to the folder containing the
/// file that is emitting the log message, which usually is the module.
/// - file: The file this log message originates from (there's usually no need to pass it explicitly as it
/// defaults to `#file`).
/// - function: The function this log message originates from (there's usually no need to pass it explicitly as
/// it defaults to `#function`).
/// - line: The line this log message originates from (there's usually no need to pass it explicitly as it
/// defaults to `#line`).
@inlinable
public func trace(_ message: @autoclosure () -> Logger.Message,
metadata: @autoclosure () -> Logger.Metadata? = nil,
source: @autoclosure () -> String? = nil,
file: String = #file, function: String = #function, line: UInt = #line) {
self.log(level: .trace, message(), metadata: metadata(), source: source(), file: file, function: function, line: line)
}
/// Log a message passing with the `Logger.Level.debug` log level.
///
/// If `.debug` is at least as severe as the `Logger`'s `logLevel`, it will be logged,
/// otherwise nothing will happen.
///
/// - parameters:
/// - message: The message to be logged. `message` can be used with any string interpolation literal.
/// - metadata: One-off metadata to attach to this log message.
/// - source: The source this log messages originates to. Currently, it defaults to the folder containing the
/// file that is emitting the log message, which usually is the module.
/// - file: The file this log message originates from (there's usually no need to pass it explicitly as it
/// defaults to `#file`).
/// - function: The function this log message originates from (there's usually no need to pass it explicitly as
/// it defaults to `#function`).
/// - line: The line this log message originates from (there's usually no need to pass it explicitly as it
/// defaults to `#line`).
@inlinable
public func debug(_ message: @autoclosure () -> Logger.Message,
metadata: @autoclosure () -> Logger.Metadata? = nil,
source: @autoclosure () -> String? = nil,
file: String = #file, function: String = #function, line: UInt = #line) {
self.log(level: .debug, message(), metadata: metadata(), source: source(), file: file, function: function, line: line)
}
/// Log a message passing with the `Logger.Level.info` log level.
///
/// If `.info` is at least as severe as the `Logger`'s `logLevel`, it will be logged,
/// otherwise nothing will happen.
///
/// - parameters:
/// - message: The message to be logged. `message` can be used with any string interpolation literal.
/// - metadata: One-off metadata to attach to this log message.
/// - source: The source this log messages originates to. Currently, it defaults to the folder containing the
/// file that is emitting the log message, which usually is the module.
/// - file: The file this log message originates from (there's usually no need to pass it explicitly as it
/// defaults to `#file`).
/// - function: The function this log message originates from (there's usually no need to pass it explicitly as
/// it defaults to `#function`).
/// - line: The line this log message originates from (there's usually no need to pass it explicitly as it
/// defaults to `#line`).
@inlinable
public func info(_ message: @autoclosure () -> Logger.Message,
metadata: @autoclosure () -> Logger.Metadata? = nil,
source: @autoclosure () -> String? = nil,
file: String = #file, function: String = #function, line: UInt = #line) {
self.log(level: .info, message(), metadata: metadata(), source: source(), file: file, function: function, line: line)
}
/// Log a message passing with the `Logger.Level.notice` log level.
///
/// If `.notice` is at least as severe as the `Logger`'s `logLevel`, it will be logged,
/// otherwise nothing will happen.
///
/// - parameters:
/// - message: The message to be logged. `message` can be used with any string interpolation literal.
/// - metadata: One-off metadata to attach to this log message.
/// - source: The source this log messages originates to. Currently, it defaults to the folder containing the
/// file that is emitting the log message, which usually is the module.
/// - file: The file this log message originates from (there's usually no need to pass it explicitly as it
/// defaults to `#file`).
/// - function: The function this log message originates from (there's usually no need to pass it explicitly as
/// it defaults to `#function`).
/// - line: The line this log message originates from (there's usually no need to pass it explicitly as it
/// defaults to `#line`).
@inlinable
public func notice(_ message: @autoclosure () -> Logger.Message,
metadata: @autoclosure () -> Logger.Metadata? = nil,
source: @autoclosure () -> String? = nil,
file: String = #file, function: String = #function, line: UInt = #line) {
self.log(level: .notice, message(), metadata: metadata(), source: source(), file: file, function: function, line: line)
}
/// Log a message passing with the `Logger.Level.warning` log level.
///
/// If `.warning` is at least as severe as the `Logger`'s `logLevel`, it will be logged,
/// otherwise nothing will happen.
///
/// - parameters:
/// - message: The message to be logged. `message` can be used with any string interpolation literal.
/// - metadata: One-off metadata to attach to this log message.
/// - source: The source this log messages originates to. Currently, it defaults to the folder containing the
/// file that is emitting the log message, which usually is the module.
/// - file: The file this log message originates from (there's usually no need to pass it explicitly as it
/// defaults to `#file`).
/// - function: The function this log message originates from (there's usually no need to pass it explicitly as
/// it defaults to `#function`).
/// - line: The line this log message originates from (there's usually no need to pass it explicitly as it
/// defaults to `#line`).
@inlinable
public func warning(_ message: @autoclosure () -> Logger.Message,
metadata: @autoclosure () -> Logger.Metadata? = nil,
source: @autoclosure () -> String? = nil,
file: String = #file, function: String = #function, line: UInt = #line) {
self.log(level: .warning, message(), metadata: metadata(), source: source(), file: file, function: function, line: line)
}
/// Log a message passing with the `Logger.Level.error` log level.
///
/// If `.error` is at least as severe as the `Logger`'s `logLevel`, it will be logged,
/// otherwise nothing will happen.
///
/// - parameters:
/// - message: The message to be logged. `message` can be used with any string interpolation literal.
/// - metadata: One-off metadata to attach to this log message.
/// - source: The source this log messages originates to. Currently, it defaults to the folder containing the
/// file that is emitting the log message, which usually is the module.
/// - file: The file this log message originates from (there's usually no need to pass it explicitly as it
/// defaults to `#file`).
/// - function: The function this log message originates from (there's usually no need to pass it explicitly as
/// it defaults to `#function`).
/// - line: The line this log message originates from (there's usually no need to pass it explicitly as it
/// defaults to `#line`).
@inlinable
public func error(_ message: @autoclosure () -> Logger.Message,
metadata: @autoclosure () -> Logger.Metadata? = nil,
source: @autoclosure () -> String? = nil,
file: String = #file, function: String = #function, line: UInt = #line) {
self.log(level: .error, message(), metadata: metadata(), source: source(), file: file, function: function, line: line)
}
/// Log a message passing with the `Logger.Level.critical` log level.
///
/// `.critical` messages will always be logged.
///
/// - parameters:
/// - message: The message to be logged. `message` can be used with any string interpolation literal.
/// - metadata: One-off metadata to attach to this log message.
/// - source: The source this log messages originates to. Currently, it defaults to the folder containing the
/// file that is emitting the log message, which usually is the module.
/// - file: The file this log message originates from (there's usually no need to pass it explicitly as it
/// defaults to `#file`).
/// - function: The function this log message originates from (there's usually no need to pass it explicitly as
/// it defaults to `#function`).
/// - line: The line this log message originates from (there's usually no need to pass it explicitly as it
/// defaults to `#line`).
@inlinable
public func critical(_ message: @autoclosure () -> Logger.Message,
metadata: @autoclosure () -> Logger.Metadata? = nil,
source: @autoclosure () -> String? = nil,
file: String = #file, function: String = #function, line: UInt = #line) {
self.log(level: .critical, message(), metadata: metadata(), source: source(), file: file, function: function, line: line)
}
}
/// The `LoggingSystem` is a global facility where the default logging backend implementation (`LogHandler`) can be
/// configured. `LoggingSystem` is set up just once in a given program to set up the desired logging backend
/// implementation.
public enum LoggingSystem {
fileprivate static let lock = ReadWriteLock()
fileprivate static var factory: (String) -> LogHandler = StreamLogHandler.standardOutput
fileprivate static var initialized = false
/// `bootstrap` is a one-time configuration function which globally selects the desired logging backend
/// implementation. `bootstrap` can be called at maximum once in any given program, calling it more than once will
/// lead to undefined behavior, most likely a crash.
///
/// - parameters:
/// - factory: A closure that given a `Logger` identifier, produces an instance of the `LogHandler`.
public static func bootstrap(_ factory: @escaping (String) -> LogHandler) {
self.lock.withWriterLock {
precondition(!self.initialized, "logging system can only be initialized once per process.")
self.factory = factory
self.initialized = true
}
}
// for our testing we want to allow multiple bootstraping
internal static func bootstrapInternal(_ factory: @escaping (String) -> LogHandler) {
self.lock.withWriterLock {
self.factory = factory
}
}
}
extension Logger {
/// `Metadata` is a typealias for `[String: Logger.MetadataValue]` the type of the metadata storage.
public typealias Metadata = [String: MetadataValue]
/// A logging metadata value. `Logger.MetadataValue` is string, array, and dictionary literal convertible.
///
/// `MetadataValue` provides convenient conformances to `ExpressibleByStringInterpolation`,
/// `ExpressibleByStringLiteral`, `ExpressibleByArrayLiteral`, and `ExpressibleByDictionaryLiteral` which means
/// that when constructing `MetadataValue`s you should default to using Swift's usual literals.
///
/// Examples:
/// - prefer `logger.info("user logged in", metadata: ["user-id": "\(user.id)"])` over
/// `..., metadata: ["user-id": .string(user.id.description)])`
/// - prefer `logger.info("user selected colors", metadata: ["colors": ["\(user.topColor)", "\(user.secondColor)"]])`
/// over `..., metadata: ["colors": .array([.string("\(user.topColor)"), .string("\(user.secondColor)")])`
/// - prefer `logger.info("nested info", metadata: ["nested": ["fave-numbers": ["\(1)", "\(2)", "\(3)"], "foo": "bar"]])`
/// over `..., metadata: ["nested": .dictionary(["fave-numbers": ...])])`
public enum MetadataValue {
/// A metadata value which is a `String`.
///
/// Because `MetadataValue` implements `ExpressibleByStringInterpolation`, and `ExpressibleByStringLiteral`,
/// you don't need to type `.string(someType.description)` you can use the string interpolation `"\(someType)"`.
case string(String)
/// A metadata value which is some `CustomStringConvertible`.
case stringConvertible(CustomStringConvertible)
/// A metadata value which is a dictionary from `String` to `Logger.MetadataValue`.
///
/// Because `MetadataValue` implements `ExpressibleByDictionaryLiteral`, you don't need to type
/// `.dictionary(["foo": .string("bar \(buz)")])`, you can just use the more natural `["foo": "bar \(buz)"]`.
case dictionary(Metadata)
/// A metadata value which is an array of `Logger.MetadataValue`s.
///
/// Because `MetadataValue` implements `ExpressibleByArrayLiteral`, you don't need to type
/// `.array([.string("foo"), .string("bar \(buz)")])`, you can just use the more natural `["foo", "bar \(buz)"]`.
case array([Metadata.Value])
}
/// The log level.
///
/// Log levels are ordered by their severity, with `.trace` being the least severe and
/// `.critical` being the most severe.
public enum Level: String, Codable, CaseIterable {
/// Appropriate for messages that contain information normally of use only when
/// tracing the execution of a program.
case trace
/// Appropriate for messages that contain information normally of use only when
/// debugging a program.
case debug
/// Appropriate for informational messages.
case info
/// Appropriate for conditions that are not error conditions, but that may require
/// special handling.
case notice
/// Appropriate for messages that are not error conditions, but more severe than
/// `.notice`.
case warning
/// Appropriate for error conditions.
case error
/// Appropriate for critical error conditions that usually require immediate
/// attention.
///
/// When a `critical` message is logged, the logging backend (`LogHandler`) is free to perform
/// more heavy-weight operations to capture system state (such as capturing stack traces) to facilitate
/// debugging.
case critical
}
/// Construct a `Logger` given a `label` identifying the creator of the `Logger`.
///
/// The `label` should identify the creator of the `Logger`. This can be an application, a sub-system, or even
/// a datatype.
///
/// - parameters:
/// - label: An identifier for the creator of a `Logger`.
public init(label: String) {
self = LoggingSystem.lock.withReaderLock { Logger(label: label, LoggingSystem.factory(label)) }
}
/// Construct a `Logger` given a `label` identifying the creator of the `Logger` or a non-standard `LogHandler`.
///
/// The `label` should identify the creator of the `Logger`. This can be an application, a sub-system, or even
/// a datatype.
///
/// This initializer provides an escape hatch in case the global default logging backend implementation (set up
/// using `LoggingSystem.bootstrap` is not appropriate for this particular logger.
///
/// - parameters:
/// - label: An identifier for the creator of a `Logger`.
/// - factory: A closure creating non-standard `LogHandler`s.
public init(label: String, factory: (String) -> LogHandler) {
self = Logger(label: label, factory(label))
}
}
extension Logger.Level {
internal var naturalIntegralValue: Int {
switch self {
case .trace:
return 0
case .debug:
return 1
case .info:
return 2
case .notice:
return 3
case .warning:
return 4
case .error:
return 5
case .critical:
return 6
}
}
}
extension Logger.Level: Comparable {
public static func < (lhs: Logger.Level, rhs: Logger.Level) -> Bool {
return lhs.naturalIntegralValue < rhs.naturalIntegralValue
}
}
// Extension has to be done on explicit type rather than Logger.Metadata.Value as workaround for
// https://bugs.swift.org/browse/SR-9687
// Then we could write it as follows and it would work under Swift 5 and not only 4 as it does currently:
// extension Logger.Metadata.Value: Equatable {
extension Logger.MetadataValue: Equatable {
public static func == (lhs: Logger.Metadata.Value, rhs: Logger.Metadata.Value) -> Bool {
switch (lhs, rhs) {
case (.string(let lhs), .string(let rhs)):
return lhs == rhs
case (.stringConvertible(let lhs), .stringConvertible(let rhs)):
return lhs.description == rhs.description
case (.array(let lhs), .array(let rhs)):
return lhs == rhs
case (.dictionary(let lhs), .dictionary(let rhs)):
return lhs == rhs
default:
return false
}
}
}
extension Logger {
/// `Logger.Message` represents a log message's text. It is usually created using string literals.
///
/// Example creating a `Logger.Message`:
///
/// let world: String = "world"
/// let myLogMessage: Logger.Message = "Hello \(world)"
///
/// Most commonly, `Logger.Message`s appear simply as the parameter to a logging method such as:
///
/// logger.info("Hello \(world)")
///
public struct Message: ExpressibleByStringLiteral, Equatable, CustomStringConvertible, ExpressibleByStringInterpolation {
public typealias StringLiteralType = String
private var value: String
public init(stringLiteral value: String) {
self.value = value
}
public var description: String {
return self.value
}
}
}
/// A pseudo-`LogHandler` that can be used to send messages to multiple other `LogHandler`s.
///
/// ### Effective Logger.Level
///
/// When first initialized the multiplex log handlers' log level is automatically set to the minimum of all the
/// passed in log handlers. This ensures that each of the handlers will be able to log at their appropriate level
/// any log events they might be interested in.
///
/// Example:
/// If log handler `A` is logging at `.debug` level, and log handler `B` is logging at `.info` level, the constructed
/// `MultiplexLogHandler([A, B])`'s effective log level will be set to `.debug`, meaning that debug messages will be
/// handled by this handler, while only logged by the underlying `A` log handler (since `B`'s log level is `.info`
/// and thus it would not actually log that log message).
///
/// If the log level is _set_ on a `Logger` backed by an `MultiplexLogHandler` the log level will apply to *all*
/// underlying log handlers, allowing a logger to still select at what level it wants to log regardless of if the underlying
/// handler is a multiplex or a normal one. If for some reason one might want to not allow changing a log level of a specific
/// handler passed into the multiplex log handler, this is possible by wrapping it in a handler which ignores any log level changes.
///
/// ### Effective Logger.Metadata
///
/// Since a `MultiplexLogHandler` is a combination of multiple log handlers, the handling of metadata can be non-obvious.
/// For example, the underlying log handlers may have metadata of their own set before they are used to initialize the multiplex log handler.
///
/// The multiplex log handler acts purely as proxy and does not make any changes to underlying handler metadata other than
/// proxying writes that users made on a `Logger` instance backed by this handler.
///
/// Setting metadata is always proxied through to _all_ underlying handlers, meaning that if a modification like
/// `logger[metadataKey: "x"] = "y"` is made, all underlying log handlers that this multiplex handler was initiated with
/// will observe this change.
///
/// Reading metadata from the multiplex log handler MAY need to pick one of conflicting values if the underlying log handlers
/// were already initiated with some metadata before passing them into the multiplex handler. The multiplex handler uses
/// the order in which the handlers were passed in during its initialization as a priority indicator - the first handler's
/// values are more important than the next handlers values, etc.
///
/// Example:
/// If the multiplex log handler was initiated with two handlers like this: `MultiplexLogHandler([handler1, handler2])`.
/// The handlers each have some already set metadata: `handler1` has metadata values for keys `one` and `all`, and `handler2`
/// has values for keys `two` and `all`.
///
/// A query through the multiplex log handler the key `one` naturally returns `handler1`'s value, and a query for `two`
/// naturally returns `handler2`'s value. Querying for the key `all` will return `handler1`'s value, as that handler was indicated
/// "more important" than the second handler. The same rule applies when querying for the `metadata` property of the
/// multiplex log handler - it constructs `Metadata` uniquing values.
public struct MultiplexLogHandler: LogHandler {
private var handlers: [LogHandler]
private var effectiveLogLevel: Logger.Level
/// Create a `MultiplexLogHandler`.
///
/// - parameters:
/// - handlers: An array of `LogHandler`s, each of which will receive the log messages sent to this `Logger`.
/// The array must not be empty.
public init(_ handlers: [LogHandler]) {
assert(!handlers.isEmpty, "MultiplexLogHandler.handlers MUST NOT be empty")
self.handlers = handlers
self.effectiveLogLevel = handlers.map { $0.logLevel }.min() ?? .trace
}
public var logLevel: Logger.Level {
get {
return self.effectiveLogLevel
}
set {
self.mutatingForEachHandler { $0.logLevel = newValue }
self.effectiveLogLevel = newValue
}
}
public func log(level: Logger.Level,
message: Logger.Message,
metadata: Logger.Metadata?,
source: String,
file: String,
function: String,
line: UInt) {
for handler in self.handlers where handler.logLevel <= level {
handler.log(level: level, message: message, metadata: metadata, source: source, file: file, function: function, line: line)
}
}
public var metadata: Logger.Metadata {
get {
var effectiveMetadata: Logger.Metadata = [:]
// as a rough estimate we assume that the underlying handlers have a similar metadata count,
// and we use the first one's current count to estimate how big of a dictionary we need to allocate:
effectiveMetadata.reserveCapacity(self.handlers.first!.metadata.count) // !-safe, we always have at least one handler
return self.handlers.reduce(into: effectiveMetadata) { effectiveMetadata, handler in
effectiveMetadata.merge(handler.metadata, uniquingKeysWith: { l, _ in l })
}
}
set {
self.mutatingForEachHandler { $0.metadata = newValue }
}
}
public subscript(metadataKey metadataKey: Logger.Metadata.Key) -> Logger.Metadata.Value? {
get {
for handler in self.handlers {
if let value = handler[metadataKey: metadataKey] {
return value
}
}
return nil
}
set {
self.mutatingForEachHandler { $0[metadataKey: metadataKey] = newValue }
}
}
private mutating func mutatingForEachHandler(_ mutator: (inout LogHandler) -> Void) {
for index in self.handlers.indices {
mutator(&self.handlers[index])
}
}
}
/// A wrapper to facilitate `print`-ing to stderr and stdio that
/// ensures access to the underlying `FILE` is locked to prevent
/// cross-thread interleaving of output.
internal struct StdioOutputStream: TextOutputStream {
internal let file: UnsafeMutablePointer<FILE>
internal let flushMode: FlushMode
internal func write(_ string: String) {
string.withCString { ptr in
#if os(Windows)
_lock_file(self.file)
#else
flockfile(self.file)
#endif
defer {
#if os(Windows)
_unlock_file(self.file)
#else
funlockfile(self.file)
#endif
}
_ = fputs(ptr, self.file)
if case .always = self.flushMode {
self.flush()
}
}
}
/// Flush the underlying stream.
/// This has no effect when using the `.always` flush mode, which is the default
internal func flush() {
_ = fflush(self.file)
}
internal static let stderr = StdioOutputStream(file: systemStderr, flushMode: .always)
internal static let stdout = StdioOutputStream(file: systemStdout, flushMode: .always)
/// Defines the flushing strategy for the underlying stream.
internal enum FlushMode {
case undefined
case always
}
}
// Prevent name clashes
#if os(macOS) || os(tvOS) || os(iOS) || os(watchOS)
let systemStderr = Darwin.stderr
let systemStdout = Darwin.stdout
#elseif os(Windows)
let systemStderr = MSVCRT.stderr
let systemStdout = MSVCRT.stdout
#else
let systemStderr = Glibc.stderr!
let systemStdout = Glibc.stdout!
#endif
/// `StreamLogHandler` is a simple implementation of `LogHandler` for directing
/// `Logger` output to either `stderr` or `stdout` via the factory methods.
public struct StreamLogHandler: LogHandler {
/// Factory that makes a `StreamLogHandler` to directs its output to `stdout`
public static func standardOutput(label: String) -> StreamLogHandler {
return StreamLogHandler(label: label, stream: StdioOutputStream.stdout)
}
/// Factory that makes a `StreamLogHandler` to directs its output to `stderr`
public static func standardError(label: String) -> StreamLogHandler {
return StreamLogHandler(label: label, stream: StdioOutputStream.stderr)
}
private let stream: TextOutputStream
private let label: String
public var logLevel: Logger.Level = .info
private var prettyMetadata: String?
public var metadata = Logger.Metadata() {
didSet {
self.prettyMetadata = self.prettify(self.metadata)
}
}
public subscript(metadataKey metadataKey: String) -> Logger.Metadata.Value? {
get {
return self.metadata[metadataKey]
}
set {
self.metadata[metadataKey] = newValue
}
}
// internal for testing only
internal init(label: String, stream: TextOutputStream) {
self.label = label
self.stream = stream
}
public func log(level: Logger.Level,
message: Logger.Message,
metadata: Logger.Metadata?,
source: String,
file: String,
function: String,
line: UInt) {
let prettyMetadata = metadata?.isEmpty ?? true
? self.prettyMetadata
: self.prettify(self.metadata.merging(metadata!, uniquingKeysWith: { _, new in new }))
var stream = self.stream
stream.write("\(self.timestamp()) \(level) \(self.label) :\(prettyMetadata.map { " \($0)" } ?? "") \(message)\n")
}
private func prettify(_ metadata: Logger.Metadata) -> String? {
return !metadata.isEmpty ? metadata.map { "\($0)=\($1)" }.joined(separator: " ") : nil
}
private func timestamp() -> String {
var buffer = [Int8](repeating: 0, count: 255)
var timestamp = time(nil)
let localTime = localtime(&timestamp)
strftime(&buffer, buffer.count, "%Y-%m-%dT%H:%M:%S%z", localTime)
return buffer.withUnsafeBufferPointer {
$0.withMemoryRebound(to: CChar.self) {
String(cString: $0.baseAddress!)
}
}
}
}
/// No operation LogHandler, used when no logging is required
public struct SwiftLogNoOpLogHandler: LogHandler {
public init() {}
@inlinable public func log(level: Logger.Level, message: Logger.Message, metadata: Logger.Metadata?, file: String, function: String, line: UInt) {}
@inlinable public subscript(metadataKey _: String) -> Logger.Metadata.Value? {
get {
return nil
}
set {}
}
@inlinable public var metadata: Logger.Metadata {
get {
return [:]
}
set {}
}
@inlinable public var logLevel: Logger.Level {
get {
return .critical
}
set {}
}
}
extension Logger {
@inlinable
internal static func currentModule(filePath: String = #file) -> String {
let utf8All = filePath.utf8
return filePath.utf8.lastIndex(of: UInt8(ascii: "/")).flatMap { lastSlash -> Substring? in
utf8All[..<lastSlash].lastIndex(of: UInt8(ascii: "/")).map { secondLastSlash -> Substring in
filePath[utf8All.index(after: secondLastSlash) ..< lastSlash]
}
}.map {
String($0)
} ?? "n/a"
}
}
// Extension has to be done on explicit type rather than Logger.Metadata.Value as workaround for
// https://bugs.swift.org/browse/SR-9686
extension Logger.MetadataValue: ExpressibleByStringLiteral {
public typealias StringLiteralType = String
public init(stringLiteral value: String) {
self = .string(value)
}
}
// Extension has to be done on explicit type rather than Logger.Metadata.Value as workaround for
// https://bugs.swift.org/browse/SR-9686
extension Logger.MetadataValue: CustomStringConvertible {
public var description: String {
switch self {
case .dictionary(let dict):
return dict.mapValues { $0.description }.description
case .array(let list):
return list.map { $0.description }.description
case .string(let str):
return str
case .stringConvertible(let repr):
return repr.description
}
}
}
// Extension has to be done on explicit type rather than Logger.Metadata.Value as workaround for
// https://bugs.swift.org/browse/SR-9687
extension Logger.MetadataValue: ExpressibleByStringInterpolation {}
// Extension has to be done on explicit type rather than Logger.Metadata.Value as workaround for
// https://bugs.swift.org/browse/SR-9686
extension Logger.MetadataValue: ExpressibleByDictionaryLiteral {
public typealias Key = String
public typealias Value = Logger.Metadata.Value
public init(dictionaryLiteral elements: (String, Logger.Metadata.Value)...) {
self = .dictionary(.init(uniqueKeysWithValues: elements))
}
}
// Extension has to be done on explicit type rather than Logger.Metadata.Value as workaround for
// https://bugs.swift.org/browse/SR-9686
extension Logger.MetadataValue: ExpressibleByArrayLiteral {
public typealias ArrayLiteralElement = Logger.Metadata.Value
public init(arrayLiteral elements: Logger.Metadata.Value...) {
self = .array(elements)
}
}