| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375 |
- // Software License Agreement (BSD License)
- //
- // Copyright (c) 2010-2025, Deusty, LLC
- // All rights reserved.
- //
- // Redistribution and use of this software in source and binary forms,
- // with or without modification, are permitted provided that the following conditions are met:
- //
- // * Redistributions of source code must retain the above copyright notice,
- // this list of conditions and the following disclaimer.
- //
- // * Neither the name of Deusty nor the names of its contributors may be used
- // to endorse or promote products derived from this software without specific
- // prior written permission of Deusty, LLC.
- import CocoaLumberjack
- import Logging
- extension Logging.Logger.Level {
- @inlinable
- var ddLogLevelAndFlag: (DDLogLevel, DDLogFlag) {
- switch self {
- case .trace: return (.verbose, .verbose)
- case .debug: return (.debug, .debug)
- case .info, .notice: return (.info, .info)
- case .warning: return (.warning, .warning)
- case .error, .critical: return (.error, .error)
- }
- }
- }
- extension DDLogMessage {
- /// Contains the swift-log details of a given log message.
- public struct SwiftLogInformation: Equatable, Sendable {
- /// Contains information about the swift-log logger that logged this message.
- public struct LoggerInformation: Equatable, Sendable {
- /// Contains the metadata from the various sources of on a logger.
- /// Currently this can be the logger itself, as well as its metadata provider
- public struct MetadataSources: Equatable, Sendable {
- /// The metadata of the swift-log logger that logged this message.
- public let logger: Logging.Logger.Metadata
- /// The metadata of the metadata provider on the swift-log logger that logged this message.
- public let provider: Logging.Logger.Metadata?
- }
- /// The label of the swift-log logger that logged this message.
- public let label: String
- /// The metadata of the swift-log logger that logged this message.
- public let metadataSources: MetadataSources
- /// The metadata of the swift-log logger that logged this message.
- @available(*, deprecated, renamed: "metadataSources.logger")
- public var metadata: Logging.Logger.Metadata { metadataSources.logger }
- }
- /// Contains information about the swift-log message thas was logged.
- public struct MessageInformation: Equatable, Sendable {
- /// The original swift-log message.
- public let message: Logging.Logger.Message
- /// The original swift-log level of the message. This could be more fine-grained than ``DDLogMessage/level`` & ``DDLogMessage/flag``.
- public let level: Logging.Logger.Level
- /// The original swift-log metadata of the message.
- public let metadata: Logging.Logger.Metadata?
- /// The original swift-log source of the message.
- public let source: String
- }
- /// The information about the swift-log logger that logged this message.
- public let logger: LoggerInformation
- /// The information about the swift-log message that was logged.
- public let message: MessageInformation
- /// Merges the metadata from all layers together.
- /// The metadata on the logger provides the base.
- /// Metadata from the logger's metadata provider (if any) trumps the base.
- /// Metadata from the logged message again trumps both the base and the metadata from the logger's metadata provider.
- /// Essentially: `logger.metadata < logger.metadataProvider < message.metadata`
- /// - Note: Accessing this property performs the merge! Accessing it multiple times can be a performance issue!
- public var mergedMetadata: Logging.Logger.Metadata {
- var merged = logger.metadataSources.logger
- if let providerMetadata = logger.metadataSources.provider {
- merged.merge(providerMetadata, uniquingKeysWith: { $1 })
- }
- if let messageMetadata = message.metadata {
- merged.merge(messageMetadata, uniquingKeysWith: { $1 })
- }
- return merged
- }
- }
- /// The swift-log information of this log message. This only exists for messages logged via swift-log.
- /// - SeeAlso: ``DDLogMessage/SwiftLogInformation``
- @inlinable
- public var swiftLogInfo: SwiftLogInformation? {
- (self as? SwiftLogMessage)?._swiftLogInfo
- }
- }
- /// This class (intentionally internal) is only an "encapsulation" layer above ``DDLogMessage``.
- /// It's basically an implementation detail of ``DDLogMessage/swiftLogInfo``.
- @usableFromInline
- final class SwiftLogMessage: DDLogMessage, @unchecked Sendable {
- @usableFromInline
- let _swiftLogInfo: SwiftLogInformation
- @usableFromInline
- init(loggerLabel: String,
- loggerMetadata: Logging.Logger.Metadata,
- loggerProvidedMetadata: Logging.Logger.Metadata?,
- message: Logging.Logger.Message,
- level: Logging.Logger.Level,
- metadata: Logging.Logger.Metadata?,
- source: String,
- file: String,
- function: String,
- line: UInt) {
- _swiftLogInfo = .init(logger: .init(label: loggerLabel,
- metadataSources: .init(logger: loggerMetadata,
- provider: loggerProvidedMetadata)),
- message: .init(message: message,
- level: level,
- metadata: metadata,
- source: source))
- let (ddLogLevel, ddLogFlag) = level.ddLogLevelAndFlag
- let msg = String(describing: message)
- super.init(format: msg,
- formatted: msg, // We have no chance in retrieving the original format here.
- level: ddLogLevel,
- flag: ddLogFlag,
- context: 0,
- file: file,
- function: function,
- line: line,
- tag: nil,
- options: .dontCopyMessage, // Swift will bridge to NSString. No need to make an additional copy.
- timestamp: nil) // Passing nil will make DDLogMessage create the timestamp which saves us the bridging between Date and NSDate.
- }
- // Not removed due to `@usableFromInline`.
- @usableFromInline
- @available(*, deprecated, renamed: "init(loggerLabel:loggerMetadata:loggerMetadata:message:level:metadata:source:file:function:line:)")
- convenience init(loggerLabel: String,
- loggerMetadata: Logging.Logger.Metadata,
- message: Logging.Logger.Message,
- level: Logging.Logger.Level,
- metadata: Logging.Logger.Metadata?,
- source: String,
- file: String,
- function: String,
- line: UInt) {
- self.init(loggerLabel: loggerLabel,
- loggerMetadata: loggerMetadata,
- loggerProvidedMetadata: nil,
- message: message,
- level: level,
- metadata: metadata,
- source: source,
- file: file,
- function: function,
- line: line)
- }
- override func isEqual(_ object: Any?) -> Bool {
- super.isEqual(object) && (object as? SwiftLogMessage)?._swiftLogInfo == _swiftLogInfo
- }
- }
- /// A swift-log ``LogHandler`` implementation that forwards messages to a given ``DDLog`` instance.
- public struct DDLogHandler: LogHandler {
- @usableFromInline
- struct Configuration: Sendable {
- @usableFromInline
- struct SyncLogging: Sendable {
- @usableFromInline
- let tresholdLevel: Logging.Logger.Level
- @usableFromInline
- let metadataKey: Logging.Logger.Metadata.Key
- }
- @usableFromInline
- let log: DDLog
- @usableFromInline
- let syncLogging: SyncLogging
- }
- @usableFromInline
- struct LoggerInfo: Sendable {
- @usableFromInline
- struct MetadataSources: Sendable {
- @usableFromInline
- var provider: Logging.Logger.MetadataProvider?
- @usableFromInline
- var logger: Logging.Logger.Metadata = [:]
- }
- @usableFromInline
- let label: String
- @usableFromInline
- var logLevel: Logging.Logger.Level
- @usableFromInline
- var metadataSources: MetadataSources
- // Not removed due to `@usableFromInline`
- @usableFromInline
- @available(*, deprecated, renamed: "metadataSources.logger")
- var metadata: Logging.Logger.Metadata {
- get { metadataSources.logger }
- set { metadataSources.logger = newValue }
- }
- }
- @usableFromInline
- let config: Configuration
- @usableFromInline
- var loggerInfo: LoggerInfo
- @inlinable
- public var logLevel: Logging.Logger.Level {
- get { loggerInfo.logLevel }
- set { loggerInfo.logLevel = newValue }
- }
- @inlinable
- public var metadataProvider: Logging.Logger.MetadataProvider? {
- get { loggerInfo.metadataSources.provider }
- set { loggerInfo.metadataSources.provider = newValue }
- }
- @inlinable
- public var metadata: Logging.Logger.Metadata {
- get { loggerInfo.metadataSources.logger }
- set { loggerInfo.metadataSources.logger = newValue }
- }
- @inlinable
- public subscript(metadataKey metadataKey: String) -> Logging.Logger.Metadata.Value? {
- get { metadata[metadataKey] }
- set { metadata[metadataKey] = newValue }
- }
- private init(config: Configuration, loggerInfo: LoggerInfo) {
- self.config = config
- self.loggerInfo = loggerInfo
- }
- /// Returns whether a message with the given level and the given metadata should be logged asynchronous.
- /// - Parameters:
- /// - level: The level at which the message was logged.
- /// - metadata: The metadata associated with the message.
- /// - Returns: Whether to log the message asynchronous.
- @usableFromInline
- func _logAsync(level: Logging.Logger.Level, metadata: Logging.Logger.Metadata?) -> Bool {
- if level >= config.syncLogging.tresholdLevel {
- // Easiest check -> level is above treshold. Not async.
- return false
- } else if case .stringConvertible(let logSynchronous as Bool) = metadata?[config.syncLogging.metadataKey] {
- // There's a metadata value, return it's value. We need to invert it since it defines whether to log _synchronous_.
- return !logSynchronous
- } else {
- // If we're below the treshold and no metadata value is set -> we're logging async.
- return true
- }
- }
- @inlinable
- public func log(level: Logging.Logger.Level,
- message: Logging.Logger.Message,
- metadata: Logging.Logger.Metadata?,
- source: String,
- file: String,
- function: String,
- line: UInt) {
- let slMessage = SwiftLogMessage(loggerLabel: loggerInfo.label,
- loggerMetadata: loggerInfo.metadataSources.logger,
- loggerProvidedMetadata: loggerInfo.metadataSources.provider?.get(),
- message: message,
- level: level,
- metadata: metadata,
- source: source,
- file: file,
- function: function,
- line: line)
- config.log.log(asynchronous: _logAsync(level: level, metadata: metadata), message: slMessage)
- }
- }
- #if swift(>=5.6)
- /// A typealias for the "old" log handler factory.
- public typealias OldLogHandlerFactory = (String) -> any LogHandler
- /// A typealias for the log handler factory.
- public typealias LogHandlerFactory = (String, Logging.Logger.MetadataProvider?) -> any LogHandler
- #else
- /// A typealias for the "old" log handler factory.
- public typealias OldLogHandlerFactory = (String) -> LogHandler
- /// A typealias for the log handler factory.
- public typealias LogHandlerFactory = (String, Logging.Logger.MetadataProvider?) -> LogHandler
- #endif
- extension DDLogHandler {
- /// The default key to control per message whether to log it synchronous or asynchronous.
- public static var defaultSynchronousLoggingMetadataKey: Logging.Logger.Metadata.Key {
- "log-synchronous"
- }
- /// Creates a new ``LogHandler`` factory using ``DDLogHandler`` with the given parameters.
- /// - Parameters:
- /// - log: The ``DDLog`` instance to use for logging. Defaults to ``DDLog/sharedInstance``.
- /// - defaultLogLevel: The default log level for new loggers. Defaults to ``Logging/Logger/Level/info``.
- /// - syncLoggingTreshold: The level as of which log messages should be logged synchronously instead of asynchronously. Defaults to ``Logging/Logger/Level/error``.
- /// - synchronousLoggingMetadataKey: The metadata key to check on messages to decide whether to log synchronous or asynchronous. Defaults to ``DDLogHandler/defaultSynchronousLoggingMetadataKey``.
- /// - Returns: A new ``LogHandler`` factory using ``DDLogHandler`` that can be passed to ``LoggingSystem/bootstrap``.
- /// - SeeAlso: ``DDLog``, ``LoggingSystem/boostrap``
- public static func handlerFactory(
- for log: DDLog = .sharedInstance,
- defaultLogLevel: Logging.Logger.Level = .info,
- loggingSynchronousAsOf syncLoggingTreshold: Logging.Logger.Level = .error,
- synchronousLoggingMetadataKey: Logging.Logger.Metadata.Key = DDLogHandler.defaultSynchronousLoggingMetadataKey
- ) -> LogHandlerFactory {
- let config = DDLogHandler.Configuration(
- log: log,
- syncLogging: .init(tresholdLevel: syncLoggingTreshold,
- metadataKey: synchronousLoggingMetadataKey)
- )
- return {
- DDLogHandler(config: config, loggerInfo: .init(label: $0, logLevel: defaultLogLevel, metadataSources: .init(provider: $1)))
- }
- }
- /// Creates a new ``LogHandler`` factory using ``DDLogHandler`` with the given parameters.
- /// - Parameters:
- /// - log: The ``DDLog`` instance to use for logging. Defaults to ``DDLog/sharedInstance``.
- /// - defaultLogLevel: The default log level for new loggers. Defaults to ``Logging/Logger/Level/info``.
- /// - syncLoggingTreshold: The level as of which log messages should be logged synchronously instead of asynchronously. Defaults to ``Logging/Logger/Level/error``.
- /// - synchronousLoggingMetadataKey: The metadata key to check on messages to decide whether to log synchronous or asynchronous. Defaults to ``DDLogHandler/defaultSynchronousLoggingMetadataKey``.
- /// - Returns: A new ``LogHandler`` factory using ``DDLogHandler`` that can be passed to ``LoggingSystem/bootstrap``.
- /// - SeeAlso: ``DDLog``, ``LoggingSystem/boostrap``
- @inlinable
- public static func handlerFactory(
- for log: DDLog = .sharedInstance,
- defaultLogLevel: Logging.Logger.Level = .info,
- loggingSynchronousAsOf syncLoggingTreshold: Logging.Logger.Level = .error,
- synchronousLoggingMetadataKey: Logging.Logger.Metadata.Key = DDLogHandler.defaultSynchronousLoggingMetadataKey
- ) -> OldLogHandlerFactory {
- let factory: LogHandlerFactory = handlerFactory(
- for: log,
- defaultLogLevel: defaultLogLevel,
- loggingSynchronousAsOf: syncLoggingTreshold,
- synchronousLoggingMetadataKey: synchronousLoggingMetadataKey
- )
- return { factory($0, nil) }
- }
- }
- extension LoggingSystem {
- /// Bootraps the logging system with a new ``LogHandler`` factory using ``DDLogHandler``.
- /// - Parameters:
- /// - log: The ``DDLog`` instance to use for logging. Defaults to ``DDLog/sharedInstance``.
- /// - defaultLogLevel: The default log level for new loggers. Defaults to ``Logging/Logger/Level/info``.
- /// - syncLoggingTreshold: The level as of which log messages should be logged synchronously instead of asynchronously. Defaults to ``Logging/Logger/Level/error``.
- /// - synchronousLoggingMetadataKey: The metadata key to check on messages to decide whether to log synchronous or asynchronous. Defaults to ``DDLogHandler/defaultSynchronousLoggingMetadataKey``.
- /// - metadataProvider: The (global) metadata provider to use with the setup. Defaults to `nil`.
- /// - SeeAlso: ``DDLogHandler/handlerFactory``, ``LoggingSystem/bootstrap``
- @inlinable
- public static func bootstrapWithCocoaLumberjack(
- for log: DDLog = .sharedInstance,
- defaultLogLevel: Logging.Logger.Level = .info,
- loggingSynchronousAsOf syncLoggingTreshold: Logging.Logger.Level = .error,
- synchronousLoggingMetadataKey: Logging.Logger.Metadata.Key = DDLogHandler.defaultSynchronousLoggingMetadataKey,
- metadataProvider: Logging.Logger.MetadataProvider? = nil
- ) {
- bootstrap(DDLogHandler.handlerFactory(for: log,
- defaultLogLevel: defaultLogLevel,
- loggingSynchronousAsOf: syncLoggingTreshold,
- synchronousLoggingMetadataKey: synchronousLoggingMetadataKey),
- metadataProvider: metadataProvider)
- }
- }
|