123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624 |
- //===----------------------------------------------------------------------===//
- //
- // 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
- #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.
- ///
- /// The most basic usage of a `Logger` is
- ///
- /// logger.info("Hello World!")
- ///
- public struct Logger {
- @usableFromInline
- var handler: LogHandler
- 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
- /// - 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,
- file: String = #file, function: String = #function, line: UInt = #line) {
- if self.logLevel <= level {
- self.handler.log(level: level,
- message: message(),
- metadata: metadata(),
- 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.trace` log level.
- ///
- /// If `.trace` is at least as severe as 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
- /// - 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,
- file: String = #file, function: String = #function, line: UInt = #line) {
- self.log(level: .trace, message(), metadata: metadata(), file: file, function: function, line: line)
- }
- /// Log a message passing with the `Logger.info` log level.
- ///
- /// If `.debug` is at least as severe as 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
- /// - 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,
- file: String = #file, function: String = #function, line: UInt = #line) {
- self.log(level: .debug, message(), metadata: metadata(), 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:
- /// - 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
- /// - 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,
- file: String = #file, function: String = #function, line: UInt = #line) {
- self.log(level: .info, message(), metadata: metadata(), 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:
- /// - 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
- /// - 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,
- file: String = #file, function: String = #function, line: UInt = #line) {
- self.log(level: .notice, message(), metadata: metadata(), 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:
- /// - 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
- /// - 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,
- file: String = #file, function: String = #function, line: UInt = #line) {
- self.log(level: .warning, message(), metadata: metadata(), 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:
- /// - 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
- /// - 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,
- file: String = #file, function: String = #function, line: UInt = #line) {
- self.log(level: .error, message(), metadata: metadata(), file: file, function: function, line: line)
- }
- /// Log a message passing with the `Logger.Level.critical` log level.
- ///
- /// `.critical` messages will always be logged.
- ///
- /// - 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
- /// - 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,
- file: String = #file, function: String = #function, line: UInt = #line) {
- self.log(level: .critical, message(), metadata: metadata(), 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 = StdoutLogHandler.init
- 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 behaviour, 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.
- public enum MetadataValue {
- /// A metadata value which is a `String`.
- 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`.
- case dictionary(Metadata)
- /// A metadata value which is an array of `Logger.MetadataValue`s.
- case array([Metadata.Value])
- }
- /// The log level.
- ///
- /// Raw values of log levels correspond to their severity, and are ordered by lowest numeric value (0) being
- /// the most severe. The raw values match the syslog values.
- public enum Level: CaseIterable {
- /// Appropriate for messages that contain information only when debugging 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.
- ///
- /// The first `LogHandler` passed to the initialisation function of `MultiplexLogHandler` control the `logLevel` as
- /// well as the `metadata` for this `LogHandler`. Any subsequent `LogHandler`s used to initialise a
- /// `MultiplexLogHandler` are merely to emit the log message to another place.
- public struct MultiplexLogHandler: LogHandler {
- private var handlers: [LogHandler]
- public init(_ handlers: [LogHandler]) {
- assert(handlers.count > 0)
- self.handlers = handlers
- }
- public var logLevel: Logger.Level {
- get {
- return self.handlers[0].logLevel
- }
- set {
- self.mutatingForEachHandler {
- $0.logLevel = newValue
- }
- }
- }
- public func log(level: Logger.Level,
- message: Logger.Message,
- metadata: Logger.Metadata?,
- file: String, function: String, line: UInt) {
- self.handlers.forEach { handler in
- handler.log(level: level, message: message, metadata: metadata, file: file, function: function, line: line)
- }
- }
- public var metadata: Logger.Metadata {
- get {
- return self.handlers[0].metadata
- }
- set {
- self.mutatingForEachHandler { $0.metadata = newValue }
- }
- }
- public subscript(metadataKey metadataKey: String) -> Logger.Metadata.Value? {
- get {
- return self.handlers[0].metadata[metadataKey]
- }
- 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])
- }
- }
- }
- /// Ships with the logging module, really boring just prints something using the `print` function
- internal struct StdoutLogHandler: LogHandler {
- private let lock = Lock()
- public init(label: String) {}
- private var _logLevel: Logger.Level = .info
- public var logLevel: Logger.Level {
- get {
- return self.lock.withLock { self._logLevel }
- }
- set {
- self.lock.withLock {
- self._logLevel = newValue
- }
- }
- }
- private var prettyMetadata: String?
- private var _metadata = Logger.Metadata() {
- didSet {
- self.prettyMetadata = self.prettify(self._metadata)
- }
- }
- public func log(level: Logger.Level,
- message: Logger.Message,
- metadata: Logger.Metadata?,
- file: String, function: String, line: UInt) {
- let prettyMetadata = metadata?.isEmpty ?? true
- ? self.prettyMetadata
- : self.prettify(self.metadata.merging(metadata!, uniquingKeysWith: { _, new in new }))
- print("\(self.timestamp()) \(level):\(prettyMetadata.map { " \($0)" } ?? "") \(message)")
- }
- public var metadata: Logger.Metadata {
- get {
- return self.lock.withLock { self._metadata }
- }
- set {
- self.lock.withLock { self._metadata = newValue }
- }
- }
- public subscript(metadataKey metadataKey: String) -> Logger.Metadata.Value? {
- get {
- return self.lock.withLock { self._metadata[metadataKey] }
- }
- set {
- self.lock.withLock {
- self._metadata[metadataKey] = newValue
- }
- }
- }
- 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!)
- }
- }
- }
- }
- // 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)
- }
- }
|