LogHandler
public protocol LogHandler
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 astruct
. - The metadata and
logLevel
properties must be implemented so that setting them on aLogger
does not affect otherLogger
s.
Treat log level & metadata as values
When developing your LogHandler
, please make sure the following test works.
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.
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.
-
log(level:
Default implementationmessage: metadata: source: file: function: line: ) This method is called when a
LogHandler
must emit a log message. There is no need for theLogHandler
to check if thelevel
is above or below the configuredlogLevel
asLogger
already performed this check and determined that a message should be logged.Default Implementation
Declaration
Parameters
level
The log level the message was logged at.
message
The message to log. To obtain a
String
representation callmessage.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.
-
log(level:
Default implementationmessage: metadata: file: function: line: ) SwiftLog 1.0 compatibility method. Please do not implement, implement
log(level:message:metadata:source:file:function:line:)
instead.Default Implementation
Declaration
-
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 veryLogHandler
.Declaration
Swift
subscript(metadataKey _: String) -> Logger.Metadata.Value? { get set }
Parameters
metadataKey
The key for the metadata item
-
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 veryLogHandler
.Declaration
Swift
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 veryLogHandler
. It is acceptable to provide some form of global log level override that means a change in log level on a particularLogHandler
might not be reflected in anyLogHandler
.Declaration
Swift
var logLevel: Logger.Level { get set }