Skip to content

Commit

Permalink
change log levels to be more natural (#35)
Browse files Browse the repository at this point in the history
Motivation:

The syslog log level was unnatural in order (`.error` < `.info`) but also
didn't have `.trace` and instead the odd `.emergency` and `.alert`.

Modification:

As per https://forums.swift.org/t/logging-levels-for-swifts-server-side-logging-apis-and-new-os-log-apis/20365/56 ,
we decided to go for

    trace < debug < info < notice < warning < error < critical

and also remove the integrals completely. The levels remain comparable
though.

Result:

log levels more natural
  • Loading branch information
weissi authored Apr 5, 2019
1 parent 55c927d commit 09c2837
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 92 deletions.
134 changes: 70 additions & 64 deletions Sources/Logging/Logging.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ extension Logger {
_ message: @autoclosure () -> Logger.Message,
metadata: @autoclosure () -> Logger.Metadata? = nil,
file: String = #file, function: String = #function, line: UInt = #line) {
if self.logLevel >= level {
if self.logLevel <= level {
self.handler.log(level: level,
message: message(),
metadata: metadata(),
Expand Down Expand Up @@ -111,9 +111,32 @@ extension Logger {
}

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 `#file`.
/// - 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 more severe than the `Logger`'s `logLevel`, it will be logged, otherwise nothing will happen.
/// 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`.
Expand All @@ -134,7 +157,8 @@ extension Logger {

/// Log a message passing with the `Logger.Level.info` log level.
///
/// If `.info` is more severe than the `Logger`'s `logLevel`, it will be logged, otherwise nothing will happen.
/// 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`.
Expand All @@ -155,7 +179,8 @@ extension Logger {

/// Log a message passing with the `Logger.Level.notice` log level.
///
/// If `.notice` is more severe than the `Logger`'s `logLevel`, it will be logged, otherwise nothing will happen.
/// 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`.
Expand All @@ -176,7 +201,8 @@ extension Logger {

/// Log a message passing with the `Logger.Level.warning` log level.
///
/// If `.warning` is more severe than the `Logger`'s `logLevel`, it will be logged, otherwise nothing will happen.
/// 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`.
Expand All @@ -197,7 +223,8 @@ extension Logger {

/// Log a message passing with the `Logger.Level.error` log level.
///
/// If `.error` is more severe than the `Logger`'s `logLevel`, it will be logged, otherwise nothing will happen.
/// 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`.
Expand All @@ -218,7 +245,7 @@ extension Logger {

/// Log a message passing with the `Logger.Level.critical` log level.
///
/// If `.critical` is more severe than the `Logger`'s `logLevel`, it will be logged, otherwise nothing will happen.
/// `.critical` messages will always be logged.
///
/// - parameters:
/// - level: The log level to log `message` at. For the available log levels, see `Logger.Level`.
Expand All @@ -236,48 +263,6 @@ extension Logger {
file: String = #file, function: String = #function, line: UInt = #line) {
self.log(level: .critical, message(), metadata: metadata(), file: file, function: function, line: line)
}

/// Log a message passing with the `Logger.Level.alert` log level.
///
/// If `.alert` 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 `#file`.
/// - 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 alert(_ message: @autoclosure () -> Logger.Message,
metadata: @autoclosure () -> Logger.Metadata? = nil,
file: String = #file, function: String = #function, line: UInt = #line) {
self.log(level: .alert, message(), metadata: metadata(), file: file, function: function, line: line)
}

/// Log a message passing with the `Logger.Level.emergency` log level.
///
/// If `.emergency` 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 `#file`.
/// - 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 emergency(_ message: @autoclosure () -> Logger.Message,
metadata: @autoclosure () -> Logger.Metadata? = nil,
file: String = #file, function: String = #function, line: UInt = #line) {
self.log(level: .emergency, message(), metadata: metadata(), file: file, function: function, line: line)
}
}

/// The `LoggingSystem` is a global facility where the default logging backend implementation (`LogHandler`) can be
Expand Down Expand Up @@ -333,35 +318,35 @@ extension Logger {
///
/// 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: Int {
public enum Level {
/// 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 = 7
case debug

/// Appropriate for informational messages.
case info = 6
case info

/// Appropriate for conditions that are not error conditions, but that may require
/// special handling.
case notice = 5
case notice

/// Appropriate for messages that are not error conditions, but more severe than
/// `.notice`.
case warning = 4
case warning

/// Appropriate for error conditions.
case error = 3
case error

/// Appropriate for criticial error conditions that usually require immediate
/// attention.
case critical = 2

/// Appropriate for conditions that should be corrected immediately, such as a corrupted
/// system database.
case alert = 1

/// Appropriate for panic conditions.
case emergency = 0
///
/// 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`.
Expand Down Expand Up @@ -391,9 +376,30 @@ extension Logger {
}
}

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.rawValue < rhs.rawValue
return lhs.naturalIntegralValue < rhs.naturalIntegralValue
}
}

Expand Down
3 changes: 2 additions & 1 deletion Tests/LoggingTests/LoggingTest+XCTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,13 @@ extension LoggingTest {
("testAutoClosuresAreNotForcedUnlessNeeded", testAutoClosuresAreNotForcedUnlessNeeded),
("testLocalMetadata", testLocalMetadata),
("testCustomFactory", testCustomFactory),
("testAllLogLevelsExceptEmergencyCanBeBlocked", testAllLogLevelsExceptEmergencyCanBeBlocked),
("testAllLogLevelsExceptCriticalCanBeBlocked", testAllLogLevelsExceptCriticalCanBeBlocked),
("testAllLogLevelsWork", testAllLogLevelsWork),
("testLogMessageWithStringInterpolation", testLogMessageWithStringInterpolation),
("testLoggingAString", testLoggingAString),
("testMultiplexerIsValue", testMultiplexerIsValue),
("testLoggerWithGlobalOverride", testLoggerWithGlobalOverride),
("testLogLevelOrdering", testLogLevelOrdering),
]
}
}
Expand Down
74 changes: 47 additions & 27 deletions Tests/LoggingTests/LoggingTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -206,56 +206,52 @@ class LoggingTest: XCTestCase {
XCTAssertTrue(logger2.handler is CustomHandler, "expected custom log handler")
}

func testAllLogLevelsExceptEmergencyCanBeBlocked() {
func testAllLogLevelsExceptCriticalCanBeBlocked() {
let testLogging = TestLogging()
LoggingSystem.bootstrapInternal(testLogging.make)

var logger = Logger(label: "\(#function)")
logger.logLevel = .emergency
logger.logLevel = .critical

logger.trace("no")
logger.debug("no")
logger.info("no")
logger.notice("no")
logger.warning("no")
logger.error("no")
logger.critical("no")
logger.alert("no")
logger.emergency("yes")
logger.critical("yes: critical")

testLogging.history.assertNotExist(level: .trace, message: "no")
testLogging.history.assertNotExist(level: .debug, message: "no")
testLogging.history.assertNotExist(level: .info, message: "no")
testLogging.history.assertNotExist(level: .notice, message: "no")
testLogging.history.assertNotExist(level: .warning, message: "no")
testLogging.history.assertNotExist(level: .error, message: "no")
testLogging.history.assertNotExist(level: .critical, message: "no")
testLogging.history.assertNotExist(level: .alert, message: "no")
testLogging.history.assertExist(level: .emergency, message: "yes")
testLogging.history.assertExist(level: .critical, message: "yes: critical")
}

func testAllLogLevelsWork() {
let testLogging = TestLogging()
LoggingSystem.bootstrapInternal(testLogging.make)

var logger = Logger(label: "\(#function)")
logger.logLevel = .debug

logger.debug("yes")
logger.info("yes")
logger.notice("yes")
logger.warning("yes")
logger.error("yes")
logger.critical("yes")
logger.alert("yes")
logger.emergency("yes")

testLogging.history.assertExist(level: .debug, message: "yes")
testLogging.history.assertExist(level: .info, message: "yes")
testLogging.history.assertExist(level: .notice, message: "yes")
testLogging.history.assertExist(level: .warning, message: "yes")
testLogging.history.assertExist(level: .error, message: "yes")
testLogging.history.assertExist(level: .critical, message: "yes")
testLogging.history.assertExist(level: .alert, message: "yes")
testLogging.history.assertExist(level: .emergency, message: "yes")
logger.logLevel = .trace

logger.trace("yes: trace")
logger.debug("yes: debug")
logger.info("yes: info")
logger.notice("yes: notice")
logger.warning("yes: warning")
logger.error("yes: error")
logger.critical("yes: critical")

testLogging.history.assertExist(level: .trace, message: "yes: trace")
testLogging.history.assertExist(level: .debug, message: "yes: debug")
testLogging.history.assertExist(level: .info, message: "yes: info")
testLogging.history.assertExist(level: .notice, message: "yes: notice")
testLogging.history.assertExist(level: .warning, message: "yes: warning")
testLogging.history.assertExist(level: .error, message: "yes: error")
testLogging.history.assertExist(level: .critical, message: "yes: critical")
}

func testLogMessageWithStringInterpolation() {
Expand Down Expand Up @@ -392,4 +388,28 @@ class LoggingTest: XCTestCase {
logRecorder.assertExist(level: .notice, message: "logger1, after")
logRecorder.assertExist(level: .notice, message: "logger2, after")
}

func testLogLevelOrdering() {
XCTAssertLessThan(Logger.Level.trace, Logger.Level.debug)
XCTAssertLessThan(Logger.Level.trace, Logger.Level.info)
XCTAssertLessThan(Logger.Level.trace, Logger.Level.notice)
XCTAssertLessThan(Logger.Level.trace, Logger.Level.warning)
XCTAssertLessThan(Logger.Level.trace, Logger.Level.error)
XCTAssertLessThan(Logger.Level.trace, Logger.Level.critical)
XCTAssertLessThan(Logger.Level.debug, Logger.Level.info)
XCTAssertLessThan(Logger.Level.debug, Logger.Level.notice)
XCTAssertLessThan(Logger.Level.debug, Logger.Level.warning)
XCTAssertLessThan(Logger.Level.debug, Logger.Level.error)
XCTAssertLessThan(Logger.Level.debug, Logger.Level.critical)
XCTAssertLessThan(Logger.Level.info, Logger.Level.notice)
XCTAssertLessThan(Logger.Level.info, Logger.Level.warning)
XCTAssertLessThan(Logger.Level.info, Logger.Level.error)
XCTAssertLessThan(Logger.Level.info, Logger.Level.critical)
XCTAssertLessThan(Logger.Level.notice, Logger.Level.warning)
XCTAssertLessThan(Logger.Level.notice, Logger.Level.error)
XCTAssertLessThan(Logger.Level.notice, Logger.Level.critical)
XCTAssertLessThan(Logger.Level.warning, Logger.Level.error)
XCTAssertLessThan(Logger.Level.warning, Logger.Level.critical)
XCTAssertLessThan(Logger.Level.error, Logger.Level.critical)
}
}

0 comments on commit 09c2837

Please sign in to comment.