Initial commit
This commit is contained in:
Generated
+257
@@ -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
@@ -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
@@ -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(×tamp)
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user