diff --git a/Example/Frameworks/Database/Logger.swift b/Example/Frameworks/Database/Logger.swift index 8f93650..ef27e9f 100644 --- a/Example/Frameworks/Database/Logger.swift +++ b/Example/Frameworks/Database/Logger.swift @@ -37,25 +37,53 @@ extension LogLevel { // Extend Logger to have `sql` log level functions. extension Logger { - func sql(_ message: @autoclosure @escaping () -> LogMessage) { - logMessage(message, with: LogLevel.Database.sql) + func sql( + file: StaticString = #file, + function: StaticString = #function, + line: UInt = #line, + column: UInt = #column, + _ message: @autoclosure @escaping () -> LogMessage + ) { + let logSource = LogSource(file: file, function: function, line: line, column: column) + logMessage(message, with: LogLevel.Database.sql, at: logSource) } - func sql(_ message: @escaping () -> LogMessage) { - logMessage(message, with: LogLevel.Database.sql) + func sql( + file: StaticString = #file, + function: StaticString = #function, + line: UInt = #line, + column: UInt = #column, + _ message: @escaping () -> LogMessage + ) { + let logSource = LogSource(file: file, function: function, line: line, column: column) + logMessage(message, with: LogLevel.Database.sql, at: logSource) } } // Extend Logger optional support to have `sql` log level functions. extension Optional where Wrapped == Logger { - func sql(_ message: @autoclosure @escaping () -> LogMessage) { + func sql( + file: StaticString = #file, + function: StaticString = #function, + line: UInt = #line, + column: UInt = #column, + _ message: @autoclosure @escaping () -> LogMessage + ) { guard case let .some(log) = self else { return } - log.logMessage(message, with: LogLevel.Database.sql) + let logSource = LogSource(file: file, function: function, line: line, column: column) + log.logMessage(message, with: LogLevel.Database.sql, at: logSource) } - func sql(_ message: @escaping () -> LogMessage) { + func sql( + file: StaticString = #file, + function: StaticString = #function, + line: UInt = #line, + column: UInt = #column, + _ message: @escaping () -> LogMessage + ) { guard case let .some(log) = self else { return } - log.logMessage(message, with: LogLevel.Database.sql) + let logSource = LogSource(file: file, function: function, line: line, column: column) + log.logMessage(message, with: LogLevel.Database.sql, at: logSource) } } diff --git a/Example/iOS Example/WillowConfiguration.swift b/Example/iOS Example/WillowConfiguration.swift index 71a2e89..390a2ba 100644 --- a/Example/iOS Example/WillowConfiguration.swift +++ b/Example/iOS Example/WillowConfiguration.swift @@ -43,7 +43,7 @@ struct WillowConfiguration { self.name = name } - func modifyMessage(_ message: String, with logLevel: LogLevel) -> String { + func modifyMessage(_ message: String, with logLevel: LogLevel, at logSource: LogSource) -> String { switch logLevel { case .warn: return "🚨🚨🚨 [\(name)] => \(message)" case .error: return "💣💥💣💥 [\(name)] => \(message)" @@ -55,14 +55,14 @@ struct WillowConfiguration { // MARK: Writers private class ServiceWriter: LogWriter { - func writeMessage(_ message: String, logLevel: LogLevel) { + func writeMessage(_ message: String, logLevel: LogLevel, logSource: LogSource) { // Send the message as-is to our external logging service let attributes: [String: Any] = ["LogLevel": logLevel.description] ServiceSDK.recordBreadcrumb(message, attributes: attributes) } - func writeMessage(_ message: LogMessage, logLevel: LogLevel) { + func writeMessage(_ message: LogMessage, logLevel: LogLevel, logSource: LogSource) { // Send the message as-is to our external logging service var attributes = message.attributes attributes["LogLevel"] = logLevel.description @@ -125,7 +125,8 @@ struct WillowConfiguration { { let prefixModifier = PrefixModifier(prefix: prefix, name: name) let timestampModifier = TimestampModifier() - let writers: [LogWriter] = [ConsoleWriter(modifiers: [prefixModifier, timestampModifier]), ServiceWriter()] + let sourceModifier = SourceModifier() + let writers: [LogWriter] = [ConsoleWriter(modifiers: [prefixModifier, timestampModifier, sourceModifier]), ServiceWriter()] return Logger(logLevels: logLevels, writers: writers, executionMethod: executionMethod) } diff --git a/README.md b/README.md index 393155c..633dda3 100644 --- a/README.md +++ b/README.md @@ -338,8 +338,8 @@ This is made possible in `Willow` through the `LogWriter` protocol. ```swift public protocol LogWriter { - func writeMessage(_ message: String, logLevel: LogLevel) - func writeMessage(_ message: Message, logLevel: LogLevel) + func writeMessage(_ message: String, logLevel: LogLevel, logSource: LogSource) + func writeMessage(_ message: Message, logLevel: LogLevel, logSource: LogSource) } ``` @@ -350,11 +350,11 @@ Here's a quick look at a simple write that writes to the console. ```swift open class ConsoleWriter: LogMessageWriter { - open func writeMessage(_ message: String, logLevel: LogLevel) { + open func writeMessage(_ message: String, logLevel: LogLevel, logSource: LogSource) { print(message) } - open func writeMessage(_ message: LogMessage, logLevel: LogLevel) { + open func writeMessage(_ message: LogMessage, logLevel: LogLevel, logSource: LogSource) { let message = "\(message.name): \(message.attributes)" print(message) } @@ -370,22 +370,22 @@ This is where `LogModifier` objects come in. ```swift public protocol LogModifier { - func modifyMessage(_ message: String, with logLevel: LogLevel) -> String + func modifyMessage(_ message: String, with logLevel: LogLevel, at logSource: LogSource) -> String } ``` The `LogModifier` protocol has only a single API. -It receives the `message` and `logLevel` and returns a newly formatted `String`. +It receives the `message` , `logLevel` and `logSource` and returns a newly formatted `String`. This is about as flexible as you can get. As an added layer of convenience, writers intending to output strings (e.g. writing to the console, files, etc.) can conform to the `LogModifierWritier` protocol. -The `LogModifierWriter` protocol adds an array of `LogModifier` objects to the `LogWriter` that can be applied to the message before it is output using the `modifyMessage(_:logLevel)` API in the extension. +The `LogModifierWriter` protocol adds an array of `LogModifier` objects to the `LogWriter` that can be applied to the message before it is output using the `modifyMessage(_:logLevel:logSource:)` API in the extension. Let's walk through a simple example for adding a prefix to a logger for the `debug` and `info` log levels. ```swift class PrefixModifier: LogModifier { - func modifyMessage(_ message: String, with logLevel: Logger.LogLevel) -> String { + func modifyMessage(_ message: String, with logLevel: Logger.LogLevel, at logSource: LogSource) -> String { return "[Willow] \(message)" } } @@ -395,11 +395,11 @@ let writers = [ConsoleWriter(modifiers: prefixModifiers)] let log = Logger(logLevels: [.debug, .info], writers: writers) ``` -To apply modifiers consistently to strings, `LogModifierWriter` objects should call `modifyMessage(_:logLevel)` to create a new string based on the original string with all the modifiers applied in order. +To apply modifiers consistently to strings, `LogModifierWriter` objects should call `modifyMessage(_:logLevel:logSource:)` to create a new string based on the original string with all the modifiers applied in order. ```swift -open func writeMessage(_ message: String, logLevel: LogLevel) { - let message = modifyMessage(message, logLevel: logLevel) +open func writeMessage(_ message: String, logLevel: LogLevel, logSource: LogSource) { + let message = modifyMessage(message, logLevel: logLevel, logSource: LogSource) print(message) } ``` @@ -407,16 +407,16 @@ open func writeMessage(_ message: String, logLevel: LogLevel) { #### Multiple Modifiers Multiple `LogModifier` objects can be stacked together onto a single log level to perform multiple actions. -Let's walk through using the `TimestampModifier` (prefixes the message with a timestamp) in combination with an `EmojiModifier`. +Let's walk through using the `TimestampModifier` (prefixes the message with a timestamp) in combination with `SourceModifier` (prefixes the message with the filename and line number where the message was logged) and an `EmojiModifier`. ```swift class EmojiModifier: LogModifier { - func modifyMessage(_ message: String, with logLevel: LogLevel) -> String { + func modifyMessage(_ message: String, with logLevel: LogLevel, at logSource: LogSource) -> String { return "🚀🚀🚀 \(message)" } } -let writers: = [ConsoleWriter(modifiers: [EmojiModifier(), TimestampModifier()])] +let writers: = [ConsoleWriter(modifiers: [EmojiModifier(), TimestampModifier(), SourceModifier()])] let log = Logger(logLevels: [.all], writers: writers) ``` diff --git a/Source/LogModifier.swift b/Source/LogModifier.swift index ecf74b2..0b9da23 100644 --- a/Source/LogModifier.swift +++ b/Source/LogModifier.swift @@ -27,7 +27,7 @@ import Foundation /// The LogModifier protocol defines a single method for modifying a log message after it has been constructed. /// This is very flexible allowing any object that conforms to modify messages in any way it wants. public protocol LogModifier { - func modifyMessage(_ message: String, with logLevel: LogLevel) -> String + func modifyMessage(_ message: String, with logLevel: LogLevel, at logSource: LogSource) -> String } // MARK: - @@ -48,12 +48,36 @@ open class TimestampModifier: LogModifier { /// Applies a timestamp to the beginning of the message. /// /// - Parameters: - /// - message: The original message to format. - /// - logLevel: The log level set for the message. + /// - message: The original message to format. + /// - logLevel: The log level set for the message. + /// - logSource: The souce of the log message. /// /// - Returns: A newly formatted message. - open func modifyMessage(_ message: String, with logLevel: LogLevel) -> String { + open func modifyMessage(_ message: String, with logLevel: LogLevel, at logSource: LogSource) -> String { let timestampString = timestampFormatter.string(from: Date()) return "\(timestampString) \(message)" } } + +// MARK: - + +/// The SourceModifier class adds the source of a message to the beginning of the message in a readable format. +open class SourceModifier: LogModifier { + /// Initializes a `SourceModifier` instance. + /// + /// - Returns: A new `SourceModifier` instance. + public init() {} + + /// Adds the source of the message to the beginning of the message. + /// + /// - Parameters: + /// - message: The original message to format. + /// - logLevel: The log level set for the message. + /// - logSource: The souce of the log message. + /// + /// - Returns: A newly formatted message. + open func modifyMessage(_ message: String, with logLevel: LogLevel, at logSource: LogSource) -> String { + let fileUrl = URL(fileURLWithPath: String(describing: logSource.file)) + return "\(fileUrl.lastPathComponent):\(logSource.line) \(message)" + } +} diff --git a/Source/LogSource.swift b/Source/LogSource.swift new file mode 100644 index 0000000..c87cc4c --- /dev/null +++ b/Source/LogSource.swift @@ -0,0 +1,72 @@ +// +// LogMessage.swift +// +// Copyright (c) 2015-present Nike, Inc. (https://www.nike.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + +/// A LogSource represents the position in the source code where a message is logged. +public struct LogSource { + /// The name of the file. + public var file: StaticString + + /// The name of the function. + public var function: StaticString + + /// The line number. + public var line: UInt + + /// The column number. + public var column: UInt + + /// Initializes a `LogSource` instance. + /// + /// - Parameters: + /// - file: The name of the file. + /// - function: The name of the function. + /// - line: The line number. + /// - column: The column number. + /// + /// - Returns: A new `LogSource` instance. + public init(file: StaticString, function: StaticString, line: UInt, column: UInt) { + self.file = file + self.function = function + self.line = line + self.column = column + } +} + +extension LogSource: Equatable { + public static func == (lhs: LogSource, rhs: LogSource) -> Bool { + guard lhs.column == rhs.column else { return false } + guard lhs.line == rhs.line else { return false } + guard String(describing: lhs.function) == String(describing: rhs.function) else { return false } + guard String(describing: lhs.file) == String(describing: rhs.file) else { return false } + return true + } +} + +extension LogSource: CustomStringConvertible { + public var description: String { + return "\(file):\(line).\(column) \(function)" + } +} diff --git a/Source/LogWriter.swift b/Source/LogWriter.swift index 5b3016a..4a10084 100644 --- a/Source/LogWriter.swift +++ b/Source/LogWriter.swift @@ -29,8 +29,8 @@ import os /// the conforming object sees fit. For example, it could write to the console, write to a file, remote log to a third /// party service, etc. public protocol LogWriter { - func writeMessage(_ message: String, logLevel: LogLevel) - func writeMessage(_ message: LogMessage, logLevel: LogLevel) + func writeMessage(_ message: String, logLevel: LogLevel, logSource: LogSource) + func writeMessage(_ message: LogMessage, logLevel: LogLevel, logSource: LogSource) } /// LogModifierWriter extends LogWriter to allow for standard writers that utilize MessageModifiers @@ -45,13 +45,14 @@ extension LogModifierWriter { /// The modifiers are run in the order they are stored in `modifiers`. /// /// - Parameters: - /// - message: Original message. - /// - logLevel: Log level of message. + /// - message: Original message. + /// - logLevel: Log level of message. + /// - logSource: The souce of the log message. /// /// - Returns: The result of executing all the modifiers on the original message. - public func modifyMessage(_ message: String, logLevel: LogLevel) -> String { + public func modifyMessage(_ message: String, logLevel: LogLevel, logSource: LogSource) -> String { var message = message - modifiers.forEach { message = $0.modifyMessage(message, with: logLevel) } + modifiers.forEach { message = $0.modifyMessage(message, with: logLevel, at: logSource) } return message } } @@ -96,8 +97,9 @@ open class ConsoleWriter: LogModifierWriter { /// - Parameters: /// - message: The original message to write to the console. /// - logLevel: The log level associated with the message. - open func writeMessage(_ message: String, logLevel: LogLevel) { - let message = modifyMessage(message, logLevel: logLevel) + /// - logSource: The souce of the log message. + open func writeMessage(_ message: String, logLevel: LogLevel, logSource: LogSource) { + let message = modifyMessage(message, logLevel: logLevel, logSource: logSource) switch method { case .print: print(message) @@ -113,8 +115,9 @@ open class ConsoleWriter: LogModifierWriter { /// - Parameters: /// - message: The original message to write to the console. /// - logLevel: The log level associated with the message. - open func writeMessage(_ message: LogMessage, logLevel: LogLevel) { - let message = modifyMessage("\(message.name): \(message.attributes)", logLevel: logLevel) + /// - logSource: The souce of the log message. + open func writeMessage(_ message: LogMessage, logLevel: LogLevel, logSource: LogSource) { + let message = modifyMessage("\(message.name): \(message.attributes)", logLevel: logLevel, logSource: logSource) switch method { case .print: print(message) @@ -156,8 +159,9 @@ open class OSLogWriter: LogModifierWriter { /// - Parameters: /// - message: The original message to write to the console. /// - logLevel: The log level associated with the message. - open func writeMessage(_ message: String, logLevel: LogLevel) { - let message = modifyMessage(message, logLevel: logLevel) + /// - logSource: The souce of the log message. + open func writeMessage(_ message: String, logLevel: LogLevel, logSource: LogSource) { + let message = modifyMessage(message, logLevel: logLevel, logSource: logSource) let type = logType(forLogLevel: logLevel) os_log("%@", log: log, type: type, message) @@ -171,8 +175,9 @@ open class OSLogWriter: LogModifierWriter { /// - Parameters: /// - message: The original breadrumb to write to the console /// - logLevel: The log level associated with the message. - open func writeMessage(_ message: LogMessage, logLevel: LogLevel) { - let message = modifyMessage("\(message.name): \(message.attributes)", logLevel: logLevel) + /// - logSource: The souce of the log message. + open func writeMessage(_ message: LogMessage, logLevel: LogLevel, logSource: LogSource) { + let message = modifyMessage("\(message.name): \(message.attributes)", logLevel: logLevel, logSource: logSource) let type = logType(forLogLevel: logLevel) os_log("%@", log: log, type: type, message) diff --git a/Source/Logger.swift b/Source/Logger.swift index f9608e8..ebe0de0 100644 --- a/Source/Logger.swift +++ b/Source/Logger.swift @@ -48,7 +48,7 @@ open class Logger { case asynchronous(queue: DispatchQueue) } - // MARK: - Properties + // MARK: - Properties /// A logger that does not output any messages to writers. public static let disabled: Logger = NoOpLogger() @@ -87,90 +87,201 @@ open class Logger { /// Writes out the given message using the logger if the debug log level is set. /// + /// - Parameter file: The name of the file where the message is logged. Do not provide a value; keep the default instead. + /// - Parameter function: The name of the function in which the message is logged. Do not provide a value; keep the default instead. + /// - Parameter line: The line number on which the message is logged. Do not provide a value; keep the default instead. + /// - Parameter column: The column number in which the message is logged. Do not provide a value; keep the default instead. /// - Parameter message: An autoclosure returning the message to log. - open func debug(_ message: @autoclosure @escaping () -> LogMessage) { - logMessage(message, with: LogLevel.debug) + open func debug( + file: StaticString = #file, + function: StaticString = #function, + line: UInt = #line, + column: UInt = #column, + _ message: @autoclosure @escaping () -> LogMessage + ) { + let logSource = LogSource(file: file, function: function, line: line, column: column) + logMessage(message, with: LogLevel.debug, at: logSource) } /// Writes out the given message using the logger if the debug log level is set. /// + /// - Parameter file: The name of the file where the message is logged. Do not provide a value; keep the default instead. + /// - Parameter function: The name of the function in which the message is logged. Do not provide a value; keep the default instead. + /// - Parameter line: The line number on which the message is logged. Do not provide a value; keep the default instead. + /// - Parameter column: The column number in which the message is logged. Do not provide a value; keep the default instead. /// - Parameter message: A closure returning the message to log. - open func debug(_ message: @escaping () -> LogMessage) { - logMessage(message, with: LogLevel.debug) + open func debug( + file: StaticString = #file, + function: StaticString = #function, + line: UInt = #line, + column: UInt = #column, + _ message: @escaping () -> LogMessage + ) { + let logSource = LogSource(file: file, function: function, line: line, column: column) + logMessage(message, with: LogLevel.debug, at: logSource) } /// Writes out the given message using the logger if the info log level is set. /// + /// - Parameter file: The name of the file where the message is logged. Do not provide a value; keep the default instead. + /// - Parameter function: The name of the function in which the message is logged. Do not provide a value; keep the default instead. + /// - Parameter line: The line number on which the message is logged. Do not provide a value; keep the default instead. + /// - Parameter column: The column number in which the message is logged. Do not provide a value; keep the default instead. /// - Parameter message: An autoclosure returning the message to log. - open func info(_ message: @autoclosure @escaping () -> LogMessage) { - logMessage(message, with: LogLevel.info) + open func info( + file: StaticString = #file, + function: StaticString = #function, + line: UInt = #line, + column: UInt = #column, + _ message: @autoclosure @escaping () -> LogMessage + ) { + let logSource = LogSource(file: file, function: function, line: line, column: column) + logMessage(message, with: LogLevel.info, at: logSource) } /// Writes out the given message using the logger if the info log level is set. /// + /// - Parameter file: The name of the file where the message is logged. Do not provide a value; keep the default instead. + /// - Parameter function: The name of the function in which the message is logged. Do not provide a value; keep the default instead. + /// - Parameter line: The line number on which the message is logged. Do not provide a value; keep the default instead. + /// - Parameter column: The column number in which the message is logged. Do not provide a value; keep the default instead. /// - Parameter message: A closure returning the message to log. - open func info(_ message: @escaping () -> LogMessage) { - logMessage(message, with: LogLevel.info) + open func info( + file: StaticString = #file, + function: StaticString = #function, + line: UInt = #line, + column: UInt = #column, + _ message: @escaping () -> LogMessage + ) { + let logSource = LogSource(file: file, function: function, line: line, column: column) + logMessage(message, with: LogLevel.info, at: logSource) } /// Writes out the given message using the logger if the event log level is set. /// + /// - Parameter file: The name of the file where the message is logged. Do not provide a value; keep the default instead. + /// - Parameter function: The name of the function in which the message is logged. Do not provide a value; keep the default instead. + /// - Parameter line: The line number on which the message is logged. Do not provide a value; keep the default instead. + /// - Parameter column: The column number in which the message is logged. Do not provide a value; keep the default instead. /// - Parameter message: An autoclosure returning the message to log. - open func event(_ message: @autoclosure @escaping () -> LogMessage) { - logMessage(message, with: LogLevel.event) + open func event( + file: StaticString = #file, + function: StaticString = #function, + line: UInt = #line, + column: UInt = #column, + _ message: @autoclosure @escaping () -> LogMessage + ) { + let logSource = LogSource(file: file, function: function, line: line, column: column) + logMessage(message, with: LogLevel.event, at: logSource) } /// Writes out the given message using the logger if the event log level is set. /// + /// - Parameter file: The name of the file where the message is logged. Do not provide a value; keep the default instead. + /// - Parameter function: The name of the function in which the message is logged. Do not provide a value; keep the default instead. + /// - Parameter line: The line number on which the message is logged. Do not provide a value; keep the default instead. + /// - Parameter column: The column number in which the message is logged. Do not provide a value; keep the default instead. /// - Parameter message: A closure returning the message to log. - open func event(_ message: @escaping () -> LogMessage) { - logMessage(message, with: LogLevel.event) + open func event( + file: StaticString = #file, + function: StaticString = #function, + line: UInt = #line, + column: UInt = #column, + _ message: @escaping () -> LogMessage + ) { + let logSource = LogSource(file: file, function: function, line: line, column: column) + logMessage(message, with: LogLevel.event, at: logSource) } /// Writes out the given message using the logger if the warn log level is set. /// + /// - Parameter file: The name of the file where the message is logged. Do not provide a value; keep the default instead. + /// - Parameter function: The name of the function in which the message is logged. Do not provide a value; keep the default instead. + /// - Parameter line: The line number on which the message is logged. Do not provide a value; keep the default instead. + /// - Parameter column: The column number in which the message is logged. Do not provide a value; keep the default instead. /// - Parameter message: An autoclosure returning the message to log. - open func warn(_ message: @autoclosure @escaping () -> LogMessage) { - logMessage(message, with: LogLevel.warn) + open func warn( + file: StaticString = #file, + function: StaticString = #function, + line: UInt = #line, + column: UInt = #column, + _ message: @autoclosure @escaping () -> LogMessage + ) { + let logSource = LogSource(file: file, function: function, line: line, column: column) + logMessage(message, with: LogLevel.warn, at: logSource) } /// Writes out the given message using the logger if the warn log level is set. /// + /// - Parameter file: The name of the file where the message is logged. Do not provide a value; keep the default instead. + /// - Parameter function: The name of the function in which the message is logged. Do not provide a value; keep the default instead. + /// - Parameter line: The line number on which the message is logged. Do not provide a value; keep the default instead. + /// - Parameter column: The column number in which the message is logged. Do not provide a value; keep the default instead. /// - Parameter message: A closure returning the message to log. - open func warn(_ message: @escaping () -> LogMessage) { - logMessage(message, with: LogLevel.warn) + open func warn( + file: StaticString = #file, + function: StaticString = #function, + line: UInt = #line, + column: UInt = #column, + _ message: @escaping () -> LogMessage + ) { + let logSource = LogSource(file: file, function: function, line: line, column: column) + logMessage(message, with: LogLevel.warn, at: logSource) } /// Writes out the given message using the logger if the error log level is set. /// + /// - Parameter file: The name of the file where the message is logged. Do not provide a value; keep the default instead. + /// - Parameter function: The name of the function in which the message is logged. Do not provide a value; keep the default instead. + /// - Parameter line: The line number on which the message is logged. Do not provide a value; keep the default instead. + /// - Parameter column: The column number in which the message is logged. Do not provide a value; keep the default instead. /// - Parameter message: An autoclosure returning the message to log. - open func error(_ message: @autoclosure @escaping () -> LogMessage) { - logMessage(message, with: LogLevel.error) + open func error( + file: StaticString = #file, + function: StaticString = #function, + line: UInt = #line, + column: UInt = #column, + _ message: @autoclosure @escaping () -> LogMessage + ) { + let logSource = LogSource(file: file, function: function, line: line, column: column) + logMessage(message, with: LogLevel.error, at: logSource) } /// Writes out the given message using the logger if the error log level is set. /// + /// - Parameter file: The name of the file where the message is logged. Do not provide a value; keep the default instead. + /// - Parameter function: The name of the function in which the message is logged. Do not provide a value; keep the default instead. + /// - Parameter line: The line number on which the message is logged. Do not provide a value; keep the default instead. + /// - Parameter column: The column number in which the message is logged. Do not provide a value; keep the default instead. /// - Parameter message: A closure returning the message to log. - open func error(_ message: @escaping () -> LogMessage) { - logMessage(message, with: LogLevel.error) + open func error( + file: StaticString = #file, + function: StaticString = #function, + line: UInt = #line, + column: UInt = #column, + _ message: @escaping () -> LogMessage + ) { + let logSource = LogSource(file: file, function: function, line: line, column: column) + logMessage(message, with: LogLevel.error, at: logSource) } /// Writes out the given message closure string with the logger if the log level is allowed. /// /// - Parameters: - /// - message: A closure returning the message to log. - /// - logLevel: The log level associated with the message closure. - open func logMessage(_ message: @escaping () -> (LogMessage), with logLevel: LogLevel) { + /// - message: A closure returning the message to log. + /// - logLevel: The log level associated with the message closure. + /// - logSource: The souce of the log message. + open func logMessage(_ message: @escaping () -> (LogMessage), with logLevel: LogLevel, at logSource: LogSource) { guard enabled && logLevelAllowed(logLevel) else { return } switch executionMethod { case .synchronous(let lock): let message = message() lock.lock() ; defer { lock.unlock() } - logMessage(message, with: logLevel) + logMessage(message, with: logLevel, at: logSource) case .asynchronous(let queue): - queue.async { self.logMessage(message(), with: logLevel) } + queue.async { self.logMessage(message(), with: logLevel, at: logSource) } } } @@ -178,89 +289,200 @@ open class Logger { /// Writes out the given message using the logger if the debug log level is set. /// + /// - Parameter file: The name of the file where the message is logged. Do not provide a value; keep the default instead. + /// - Parameter function: The name of the function in which the message is logged. Do not provide a value; keep the default instead. + /// - Parameter line: The line number on which the message is logged. Do not provide a value; keep the default instead. + /// - Parameter column: The column number in which the message is logged. Do not provide a value; keep the default instead. /// - Parameter message: An autoclosure returning the message to log. - open func debugMessage(_ message: @autoclosure @escaping () -> String) { - logMessage(message, with: LogLevel.debug) + open func debugMessage( + file: StaticString = #file, + function: StaticString = #function, + line: UInt = #line, + column: UInt = #column, + _ message: @autoclosure @escaping () -> String + ) { + let logSource = LogSource(file: file, function: function, line: line, column: column) + logMessage(message, with: LogLevel.debug, at: logSource) } /// Writes out the given message using the logger if the debug log level is set. /// + /// - Parameter file: The name of the file where the message is logged. Do not provide a value; keep the default instead. + /// - Parameter function: The name of the function in which the message is logged. Do not provide a value; keep the default instead. + /// - Parameter line: The line number on which the message is logged. Do not provide a value; keep the default instead. + /// - Parameter column: The column number in which the message is logged. Do not provide a value; keep the default instead. /// - Parameter message: A closure returning the message to log. - open func debugMessage(_ message: @escaping () -> String) { - logMessage(message, with: LogLevel.debug) + open func debugMessage( + file: StaticString = #file, + function: StaticString = #function, + line: UInt = #line, + column: UInt = #column, + _ message: @escaping () -> String + ) { + let logSource = LogSource(file: file, function: function, line: line, column: column) + logMessage(message, with: LogLevel.debug, at: logSource) } /// Writes out the given message using the logger if the info log level is set. /// + /// - Parameter file: The name of the file where the message is logged. Do not provide a value; keep the default instead. + /// - Parameter function: The name of the function in which the message is logged. Do not provide a value; keep the default instead. + /// - Parameter line: The line number on which the message is logged. Do not provide a value; keep the default instead. + /// - Parameter column: The column number in which the message is logged. Do not provide a value; keep the default instead. /// - Parameter message: An autoclosure returning the message to log. - open func infoMessage(_ message: @autoclosure @escaping () -> String) { - logMessage(message, with: LogLevel.info) + open func infoMessage( + file: StaticString = #file, + function: StaticString = #function, + line: UInt = #line, + column: UInt = #column, + _ message: @autoclosure @escaping () -> String + ) { + let logSource = LogSource(file: file, function: function, line: line, column: column) + logMessage(message, with: LogLevel.info, at: logSource) } /// Writes out the given message using the logger if the info log level is set. /// + /// - Parameter file: The name of the file where the message is logged. Do not provide a value; keep the default instead. + /// - Parameter function: The name of the function in which the message is logged. Do not provide a value; keep the default instead. + /// - Parameter line: The line number on which the message is logged. Do not provide a value; keep the default instead. + /// - Parameter column: The column number in which the message is logged. Do not provide a value; keep the default instead. /// - Parameter message: A closure returning the message to log. - open func infoMessage(_ message: @escaping () -> String) { - logMessage(message, with: LogLevel.info) + open func infoMessage( + file: StaticString = #file, + function: StaticString = #function, + line: UInt = #line, + column: UInt = #column, + _ message: @escaping () -> String + ) { + let logSource = LogSource(file: file, function: function, line: line, column: column) + logMessage(message, with: LogLevel.info, at: logSource) } /// Writes out the given message using the logger if the event log level is set. /// + /// - Parameter file: The name of the file where the message is logged. Do not provide a value; keep the default instead. + /// - Parameter function: The name of the function in which the message is logged. Do not provide a value; keep the default instead. + /// - Parameter line: The line number on which the message is logged. Do not provide a value; keep the default instead. + /// - Parameter column: The column number in which the message is logged. Do not provide a value; keep the default instead. /// - Parameter message: An autoclosure returning the message to log. - open func eventMessage(_ message: @autoclosure @escaping () -> String) { - logMessage(message, with: LogLevel.event) + open func eventMessage( + file: StaticString = #file, + function: StaticString = #function, + line: UInt = #line, + column: UInt = #column, + _ message: @autoclosure @escaping () -> String + ) { + let logSource = LogSource(file: file, function: function, line: line, column: column) + logMessage(message, with: LogLevel.event, at: logSource) } /// Writes out the given message using the logger if the event log level is set. /// + /// - Parameter file: The name of the file where the message is logged. Do not provide a value; keep the default instead. + /// - Parameter function: The name of the function in which the message is logged. Do not provide a value; keep the default instead. + /// - Parameter line: The line number on which the message is logged. Do not provide a value; keep the default instead. + /// - Parameter column: The column number in which the message is logged. Do not provide a value; keep the default instead. /// - Parameter message: A closure returning the message to log. - open func eventMessage(_ message: @escaping () -> String) { - logMessage(message, with: LogLevel.event) + open func eventMessage( + file: StaticString = #file, + function: StaticString = #function, + line: UInt = #line, + column: UInt = #column, + _ message: @escaping () -> String + ) { + let logSource = LogSource(file: file, function: function, line: line, column: column) + logMessage(message, with: LogLevel.event, at: logSource) } /// Writes out the given message using the logger if the warn log level is set. /// + /// - Parameter file: The name of the file where the message is logged. Do not provide a value; keep the default instead. + /// - Parameter function: The name of the function in which the message is logged. Do not provide a value; keep the default instead. + /// - Parameter line: The line number on which the message is logged. Do not provide a value; keep the default instead. + /// - Parameter column: The column number in which the message is logged. Do not provide a value; keep the default instead. /// - Parameter message: An autoclosure returning the message to log. - open func warnMessage(_ message: @autoclosure @escaping () -> String) { - logMessage(message, with: LogLevel.warn) + open func warnMessage( + file: StaticString = #file, + function: StaticString = #function, + line: UInt = #line, + column: UInt = #column, + _ message: @autoclosure @escaping () -> String + ) { + let logSource = LogSource(file: file, function: function, line: line, column: column) + logMessage(message, with: LogLevel.warn, at: logSource) } /// Writes out the given message using the logger if the warn log level is set. /// + /// - Parameter file: The name of the file where the message is logged. Do not provide a value; keep the default instead. + /// - Parameter function: The name of the function in which the message is logged. Do not provide a value; keep the default instead. + /// - Parameter line: The line number on which the message is logged. Do not provide a value; keep the default instead. + /// - Parameter column: The column number in which the message is logged. Do not provide a value; keep the default instead. /// - Parameter message: A closure returning the message to log. - open func warnMessage(_ message: @escaping () -> String) { - logMessage(message, with: LogLevel.warn) + open func warnMessage( + file: StaticString = #file, + function: StaticString = #function, + line: UInt = #line, + column: UInt = #column, + _ message: @escaping () -> String + ) { + let logSource = LogSource(file: file, function: function, line: line, column: column) + logMessage(message, with: LogLevel.warn, at: logSource) } /// Writes out the given message using the logger if the error log level is set. /// + /// - Parameter file: The name of the file where the message is logged. Do not provide a value; keep the default instead. + /// - Parameter function: The name of the function in which the message is logged. Do not provide a value; keep the default instead. + /// - Parameter line: The line number on which the message is logged. Do not provide a value; keep the default instead. + /// - Parameter column: The column number in which the message is logged. Do not provide a value; keep the default instead. /// - Parameter message: An autoclosure returning the message to log. - open func errorMessage(_ message: @autoclosure @escaping () -> String) { - logMessage(message, with: LogLevel.error) + open func errorMessage( + file: StaticString = #file, + function: StaticString = #function, + line: UInt = #line, + column: UInt = #column, + _ message: @autoclosure @escaping () -> String + ) { + let logSource = LogSource(file: file, function: function, line: line, column: column) + logMessage(message, with: LogLevel.error, at: logSource) } /// Writes out the given message using the logger if the error log level is set. /// + /// - Parameter file: The name of the file where the message is logged. Do not provide a value; keep the default instead. + /// - Parameter function: The name of the function in which the message is logged. Do not provide a value; keep the default instead. + /// - Parameter line: The line number on which the message is logged. Do not provide a value; keep the default instead. + /// - Parameter column: The column number in which the message is logged. Do not provide a value; keep the default instead. /// - Parameter message: A closure returning the message to log. - open func errorMessage(_ message: @escaping () -> String) { - logMessage(message, with: LogLevel.error) + open func errorMessage( + file: StaticString = #file, + function: StaticString = #function, + line: UInt = #line, + column: UInt = #column, + _ message: @escaping () -> String + ) { + let logSource = LogSource(file: file, function: function, line: line, column: column) + logMessage(message, with: LogLevel.error, at: logSource) } /// Writes out the given message closure string with the logger if the log level is allowed. /// /// - Parameters: - /// - message: A closure returning the message to log. - /// - withLogLevel: The log level associated with the message closure. - open func logMessage(_ message: @escaping () -> String, with logLevel: LogLevel) { + /// - message: A closure returning the message to log. + /// - logLevel: The log level associated with the message closure. + /// - logSource: The souce of the log message. + open func logMessage(_ message: @escaping () -> String, with logLevel: LogLevel, at logSource: LogSource) { guard enabled && logLevelAllowed(logLevel) else { return } switch executionMethod { case .synchronous(let lock): lock.lock() ; defer { lock.unlock() } - logMessage(message(), with: logLevel) + logMessage(message(), with: logLevel, at: logSource) case .asynchronous(let queue): - queue.async { self.logMessage(message(), with: logLevel) } + queue.async { self.logMessage(message(), with: logLevel, at: logSource) } } } @@ -270,12 +492,12 @@ open class Logger { return logLevels.contains(logLevel) } - private func logMessage(_ message: String, with logLevel: LogLevel) { - writers.forEach { $0.writeMessage(message, logLevel: logLevel) } + private func logMessage(_ message: String, with logLevel: LogLevel, at logSource: LogSource) { + writers.forEach { $0.writeMessage(message, logLevel: logLevel, logSource: logSource) } } - private func logMessage(_ message: LogMessage, with logLevel: LogLevel) { - writers.forEach { $0.writeMessage(message, logLevel: logLevel) } + private func logMessage(_ message: LogMessage, with logLevel: LogLevel, at logSource: LogSource) { + writers.forEach { $0.writeMessage(message, logLevel: logLevel, logSource: logSource) } } // MARK: - Private - No-Op Logger @@ -286,6 +508,6 @@ open class Logger { enabled = false } - override func logMessage(_ message: @escaping () -> String, with logLevel: LogLevel) {} + override func logMessage(_ message: @escaping () -> String, with logLevel: LogLevel, at logSource: LogSource) {} } } diff --git a/Tests/LogLevelTests.swift b/Tests/LogLevelTests.swift index a336a83..529232d 100644 --- a/Tests/LogLevelTests.swift +++ b/Tests/LogLevelTests.swift @@ -35,19 +35,23 @@ extension LogLevel { extension Logger { fileprivate func verboseMessage(_ message: @autoclosure @escaping () -> String) { - logMessage(message, with: LogLevel.verbose) + let logSource = LogSource(file: #file, function: #function, line: #line, column: #column) + logMessage(message, with: LogLevel.verbose, at: logSource) } fileprivate func verboseMessage(_ message: @escaping () -> String) { - logMessage(message, with: LogLevel.verbose) + let logSource = LogSource(file: #file, function: #function, line: #line, column: #column) + logMessage(message, with: LogLevel.verbose, at: logSource) } fileprivate func summaryMessage(_ message: @autoclosure @escaping () -> String) { - logMessage(message, with: LogLevel.summary) + let logSource = LogSource(file: #file, function: #function, line: #line, column: #column) + logMessage(message, with: LogLevel.summary, at: logSource) } fileprivate func summaryMessage(_ message: @escaping () -> String) { - logMessage(message, with: LogLevel.summary) + let logSource = LogSource(file: #file, function: #function, line: #line, column: #column) + logMessage(message, with: LogLevel.summary, at: logSource) } } @@ -57,12 +61,12 @@ class TestWriter: LogWriter { private(set) var actualNumberOfWrites: Int = 0 private(set) var message: String? - func writeMessage(_ message: String, logLevel: LogLevel) { + func writeMessage(_ message: String, logLevel: LogLevel, logSource: LogSource) { self.message = message actualNumberOfWrites += 1 } - func writeMessage(_ message: LogMessage, logLevel: LogLevel) { + func writeMessage(_ message: LogMessage, logLevel: LogLevel, logSource: LogSource) { self.message = "\(message.name): \(message.attributes)" actualNumberOfWrites += 1 } diff --git a/Tests/LogModifierTests.swift b/Tests/LogModifierTests.swift index 174db7d..d53f299 100644 --- a/Tests/LogModifierTests.swift +++ b/Tests/LogModifierTests.swift @@ -32,9 +32,10 @@ class TimestampModifierTestCase: XCTestCase { let modifier = TimestampModifier() let message = "Test Message" let logLevels: [LogLevel] = [.error, .warn, .event, .info, .debug] + let logSource = LogSource(file: #file, function: #function, line: #line, column: #column) // When - var actualMessages = logLevels.map { modifier.modifyMessage(message, with: $0) } + let actualMessages = logLevels.map { modifier.modifyMessage(message, with: $0, at: logSource) } // Then for (index, _) in logLevels.enumerated() { @@ -45,3 +46,37 @@ class TimestampModifierTestCase: XCTestCase { } } } + +class SourceModifierTestCase: XCTestCase { + func testThatItModifiesAMessageLogLevelIndependent() { + // Given + let modifier = SourceModifier() + let message = "A Message" + let logLevels: [LogLevel] = [.error, .warn, .event, .info, .debug] + let logSource = LogSource(file: "File", function: "Function", line: 1, column: 2) + + // When + let actualMessages = logLevels.map { modifier.modifyMessage(message, with: $0, at: logSource) } + + // Then + let messageSet = Set(actualMessages) + XCTAssertEqual(1, messageSet.count, "All actual messages should be equal") + } + + func testThatItModifiesAMessageRespectingFileAndLineOfSource() { + // Given + let modifier = SourceModifier() + let message = "Test Message" + let logLevel = LogLevel.debug + let logFile: StaticString = "LogFile" + let logLine: UInt = 42 + let logSource = LogSource(file: logFile, function: "", line: logLine, column: 0) + + // When + let actualMessage = modifier.modifyMessage(message, with: logLevel, at: logSource) + + // Then + let expectedMessage = "\(logFile):\(logLine) \(message)" + XCTAssertEqual(actualMessage, expectedMessage, "Actual message should equal the expected") + } +} diff --git a/Tests/LogWriterTests.swift b/Tests/LogWriterTests.swift index 8580682..eb0f972 100644 --- a/Tests/LogWriterTests.swift +++ b/Tests/LogWriterTests.swift @@ -32,30 +32,33 @@ class ConsoleWriterTestCase: XCTestCase { // Given let message = "Test Message" let logLevel: LogLevel = .all + let logSource = LogSource(file: #file, function: #function, line: #line, column: #column) let writer = ConsoleWriter() // When, Then - writer.writeMessage(message, logLevel: logLevel) + writer.writeMessage(message, logLevel: logLevel, logSource: logSource) } func testThatConsoleWriterCanWriteMessageToConsoleWithPrint() { // Given let message = "Test Message" let logLevel: LogLevel = .all + let logSource = LogSource(file: #file, function: #function, line: #line, column: #column) let writer = ConsoleWriter(method: .print) // When, Then - writer.writeMessage(message, logLevel: logLevel) + writer.writeMessage(message, logLevel: logLevel, logSource: logSource) } func testThatConsoleWriterCanWriteMessageToConsoleWithNSLog() { // Given let message = "Test Message" let logLevel: LogLevel = .all + let logSource = LogSource(file: #file, function: #function, line: #line, column: #column) let writer = ConsoleWriter(method: .nslog) // When, Then - writer.writeMessage(message, logLevel: logLevel) + writer.writeMessage(message, logLevel: logLevel, logSource: logSource) } } @@ -69,19 +72,21 @@ class OSLogWriterTestCase: XCTestCase { // Given let message = "Test Message" let logLevel: LogLevel = .all + let logSource = LogSource(file: #file, function: #function, line: #line, column: #column) let writer = OSLogWriter(subsystem: subsystem, category: category) // When, Then - writer.writeMessage(message, logLevel: logLevel) + writer.writeMessage(message, logLevel: logLevel, logSource: logSource) } func testThatOSLogWriterCanWriteMessageUsingOSLog() { // Given let message = "Test Message" let logLevel: LogLevel = .all + let logSource = LogSource(file: #file, function: #function, line: #line, column: #column) let writer = OSLogWriter(subsystem: subsystem, category: category) // When, Then - writer.writeMessage(message, logLevel: logLevel) + writer.writeMessage(message, logLevel: logLevel, logSource: logSource) } } diff --git a/Tests/LoggerTests.swift b/Tests/LoggerTests.swift index 9a23bc8..db2aea4 100644 --- a/Tests/LoggerTests.swift +++ b/Tests/LoggerTests.swift @@ -46,10 +46,10 @@ class SynchronousTestWriter: LogModifierWriter { self.modifiers = modifiers } - func writeMessage(_ message: String, logLevel: LogLevel) { + func writeMessage(_ message: String, logLevel: LogLevel, logSource: LogSource) { var mutableMessage = message - modifiers.forEach { mutableMessage = $0.modifyMessage(mutableMessage, with: logLevel) } + modifiers.forEach { mutableMessage = $0.modifyMessage(mutableMessage, with: logLevel, at: logSource) } modifiedMessages.append(mutableMessage) self.message = mutableMessage @@ -57,12 +57,12 @@ class SynchronousTestWriter: LogModifierWriter { actualNumberOfWrites += 1 } - func writeMessage(_ message: LogMessage, logLevel: LogLevel) { + func writeMessage(_ message: LogMessage, logLevel: LogLevel, logSource: LogSource) { var mutableMessage = "\(message.name): \(message.attributes)" lastMessage = message - modifiers.forEach { mutableMessage = $0.modifyMessage(mutableMessage, with: logLevel) } + modifiers.forEach { mutableMessage = $0.modifyMessage(mutableMessage, with: logLevel, at: logSource) } modifiedMessages.append(mutableMessage) self.message = mutableMessage @@ -83,16 +83,16 @@ class AsynchronousTestWriter: SynchronousTestWriter { super.init(modifiers: modifiers) } - override func writeMessage(_ message: String, logLevel: LogLevel) { - super.writeMessage(message, logLevel: logLevel) + override func writeMessage(_ message: String, logLevel: LogLevel, logSource: LogSource) { + super.writeMessage(message, logLevel: logLevel, logSource: logSource) if actualNumberOfWrites == expectedNumberOfWrites { expectation.fulfill() } } - override func writeMessage(_ message: LogMessage, logLevel: LogLevel) { - super.writeMessage(message, logLevel: logLevel) + override func writeMessage(_ message: LogMessage, logLevel: LogLevel, logSource: LogSource) { + super.writeMessage(message, logLevel: logLevel, logSource: logSource) if actualNumberOfWrites == expectedNumberOfWrites { expectation.fulfill() @@ -103,7 +103,7 @@ class AsynchronousTestWriter: SynchronousTestWriter { // MARK: - class PrefixModifier: LogModifier { - func modifyMessage(_ message: String, with: LogLevel) -> String { + func modifyMessage(_ message: String, with: LogLevel, at: LogSource) -> String { return "[Willow] \(message)" } } @@ -149,7 +149,7 @@ class AsynchronousLoggerTestCase: SynchronousLoggerTestCase { class SynchronousLoggerMultiModifierTestCase: SynchronousLoggerTestCase { private struct SymbolModifier: LogModifier { - func modifyMessage(_ message: String, with logLevel: LogLevel) -> String { + func modifyMessage(_ message: String, with logLevel: LogLevel, at logSource: LogSource) -> String { return "+=+-+ \(message)" } } @@ -199,3 +199,145 @@ class SynchronousLoggerMultiWriterTestCase: SynchronousLoggerTestCase { XCTAssertEqual(writer3.actualNumberOfWrites, 5, "writer 3 actual number of writes should be 5") } } + +// MARK: - + +class LoggerSourceTestCase: SynchronousLoggerTestCase { + private class SourceModifier: LogModifier { + private(set) var logSources: [LogSource] = [] + + func modifyMessage(_ message: String, with logLevel: LogLevel, at logSource: LogSource) -> String { + logSources.append(logSource) + return message + } + } + + private class TestMessage: LogMessage { + var name: String = "Message Name" + var attributes: [String : Any] = [:] + } + + func testThatSourcesAreEqual() { + // Given + let source1 = LogSource(file: "File", function: "Function", line: 1, column: 11) + let source2 = LogSource(file: "File", function: "Function", line: 1, column: 11) + + // When + let result = source1 == source2 + + // Then + XCTAssertTrue(result, "Comparision should be true for equal sources") + } + + func testThatSourcesAreNotEqual() { + // Given + let baseSource = LogSource(file: "File", function: "Function", line: 0, column: 10) + let variations = [ + LogSource(file: "Difference", function: "Function", line: 0, column: 10), + LogSource(file: "File", function: "Difference", line: 0, column: 10), + LogSource(file: "File", function: "Function", line: 1, column: 10), + LogSource(file: "File", function: "Function", line: 0, column: 11) + ] + + // When + let results = variations.map { $0 == baseSource } + + // Then + results.enumerated().forEach { XCTAssertFalse($0.element, "Variation at index \($0.offset) should be different to the base source") } + } + + func testThatStringMessageClosuresPassLogSourcesToTheModifier() { + // Given + let sourceModifier = SourceModifier() + let (log, _) = logger(modifiers: [sourceModifier]) + let logSources = [ + LogSource(file: "DebugFile", function: "DebugFunction", line: 1, column: 11), + LogSource(file: "InfoFile", function: "InfoFunction", line: 2, column: 12), + LogSource(file: "EventFile", function: "EventFunction", line: 3, column: 13), + LogSource(file: "WarnFile", function: "WarnFunction", line: 4, column: 14), + LogSource(file: "ErrorFile", function: "ErrorFunction", line: 5, column: 15) + ] + + // When + log.debugMessage(file: logSources[0].file, function: logSources[0].function, line: logSources[0].line, column: logSources[0].column) { self.message } + log.infoMessage(file: logSources[1].file, function: logSources[1].function, line: logSources[1].line, column: logSources[1].column) { self.message } + log.eventMessage(file: logSources[2].file, function: logSources[2].function, line: logSources[2].line, column: logSources[2].column) { self.message } + log.warnMessage(file: logSources[3].file, function: logSources[3].function, line: logSources[3].line, column: logSources[3].column) { self.message } + log.errorMessage(file: logSources[4].file, function: logSources[4].function, line: logSources[4].line, column: logSources[4].column) { self.message } + + // Then + XCTAssertEqual(sourceModifier.logSources, logSources, "Actual sources should be equal to provided log sources") + } + + func testThatStringMessagesPassLogSourcesToTheModifier() { + // Given + let sourceModifier = SourceModifier() + let (log, _) = logger(modifiers: [sourceModifier]) + let logSources = [ + LogSource(file: "Debug", function: "Debug", line: 21, column: 31), + LogSource(file: "Info", function: "Info", line: 22, column: 32), + LogSource(file: "Event", function: "Event", line: 23, column: 33), + LogSource(file: "Warn", function: "Warn", line: 24, column: 34), + LogSource(file: "Error", function: "Error", line: 25, column: 35) + ] + + // When + log.debugMessage(file: logSources[0].file, function: logSources[0].function, line: logSources[0].line, column: logSources[0].column, self.message) + log.infoMessage(file: logSources[1].file, function: logSources[1].function, line: logSources[1].line, column: logSources[1].column, self.message) + log.eventMessage(file: logSources[2].file, function: logSources[2].function, line: logSources[2].line, column: logSources[2].column, self.message) + log.warnMessage(file: logSources[3].file, function: logSources[3].function, line: logSources[3].line, column: logSources[3].column, self.message) + log.errorMessage(file: logSources[4].file, function: logSources[4].function, line: logSources[4].line, column: logSources[4].column, self.message) + + // Then + XCTAssertEqual(sourceModifier.logSources, logSources, "Actual sources should be equal to provided log sources") + } + + func testThatLogMessageClosuresPassLogSourcesToTheModifier() { + // Given + let testMessage = TestMessage() + let sourceModifier = SourceModifier() + let (log, _) = logger(modifiers: [sourceModifier]) + let logSources = [ + LogSource(file: "DebugFile", function: "DebugFunction", line: 1, column: 11), + LogSource(file: "InfoFile", function: "InfoFunction", line: 2, column: 12), + LogSource(file: "EventFile", function: "EventFunction", line: 3, column: 13), + LogSource(file: "WarnFile", function: "WarnFunction", line: 4, column: 14), + LogSource(file: "ErrorFile", function: "ErrorFunction", line: 5, column: 15) + ] + + // When + log.debug(file: logSources[0].file, function: logSources[0].function, line: logSources[0].line, column: logSources[0].column) { testMessage } + log.info(file: logSources[1].file, function: logSources[1].function, line: logSources[1].line, column: logSources[1].column) { testMessage } + log.event(file: logSources[2].file, function: logSources[2].function, line: logSources[2].line, column: logSources[2].column) { testMessage } + log.warn(file: logSources[3].file, function: logSources[3].function, line: logSources[3].line, column: logSources[3].column) { testMessage } + log.error(file: logSources[4].file, function: logSources[4].function, line: logSources[4].line, column: logSources[4].column) { testMessage } + + // Then + XCTAssertEqual(sourceModifier.logSources, logSources, "Actual sources should be equal to provided log sources") + } + + func testThatLogMessagesPassLogSourcesToTheModifier() { + // Given + let testMessage = TestMessage() + let sourceModifier = SourceModifier() + let (log, _) = logger(modifiers: [sourceModifier]) + let logSources = [ + LogSource(file: "Debug", function: "Debug", line: 21, column: 31), + LogSource(file: "Info", function: "Info", line: 22, column: 32), + LogSource(file: "Event", function: "Event", line: 23, column: 33), + LogSource(file: "Warn", function: "Warn", line: 24, column: 34), + LogSource(file: "Error", function: "Error", line: 25, column: 35) + ] + + // When + log.debug(file: logSources[0].file, function: logSources[0].function, line: logSources[0].line, column: logSources[0].column, testMessage) + log.info(file: logSources[1].file, function: logSources[1].function, line: logSources[1].line, column: logSources[1].column, testMessage) + log.event(file: logSources[2].file, function: logSources[2].function, line: logSources[2].line, column: logSources[2].column, testMessage) + log.warn(file: logSources[3].file, function: logSources[3].function, line: logSources[3].line, column: logSources[3].column, testMessage) + log.error(file: logSources[4].file, function: logSources[4].function, line: logSources[4].line, column: logSources[4].column, testMessage) + + // Then + XCTAssertEqual(sourceModifier.logSources, logSources, "Actual sources should be equal to provided log sources") + } +} + diff --git a/Willow.xcodeproj/project.pbxproj b/Willow.xcodeproj/project.pbxproj index ae733c2..c043d4f 100644 --- a/Willow.xcodeproj/project.pbxproj +++ b/Willow.xcodeproj/project.pbxproj @@ -7,6 +7,10 @@ objects = { /* Begin PBXBuildFile section */ + 26FAB8C7236058A80040B628 /* LogSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26FAB8C6236058A80040B628 /* LogSource.swift */; }; + 26FAB8C8236058A80040B628 /* LogSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26FAB8C6236058A80040B628 /* LogSource.swift */; }; + 26FAB8C9236058A80040B628 /* LogSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26FAB8C6236058A80040B628 /* LogSource.swift */; }; + 26FAB8CA236058A80040B628 /* LogSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26FAB8C6236058A80040B628 /* LogSource.swift */; }; 321F2DF335043A818DBEC881 /* (null) in Frameworks */ = {isa = PBXBuildFile; }; 4C0CE38D1CFF3FB600D57B03 /* LogWriterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C0CE38C1CFF3FB600D57B03 /* LogWriterTests.swift */; }; 4C0CE38E1CFF3FB600D57B03 /* LogWriterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C0CE38C1CFF3FB600D57B03 /* LogWriterTests.swift */; }; @@ -80,6 +84,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 26FAB8C6236058A80040B628 /* LogSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogSource.swift; sourceTree = ""; }; 4C0CE38C1CFF3FB600D57B03 /* LogWriterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LogWriterTests.swift; sourceTree = ""; }; 4C1BBA691A6C518800D39EE1 /* LogModifierTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LogModifierTests.swift; sourceTree = ""; }; 4C1BBA6A1A6C518800D39EE1 /* LoggerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoggerTests.swift; sourceTree = ""; }; @@ -178,6 +183,7 @@ 4C83FB2C1AEC1959003FEA49 /* LogLevel.swift */, 55B859C21F44FC9F0012F5F4 /* LogMessage.swift */, 4CA86F641AEC465E005E6475 /* LogModifier.swift */, + 26FAB8C6236058A80040B628 /* LogSource.swift */, 4CA86F671AEC466A005E6475 /* LogWriter.swift */, 4CA75D451A6C493F00275DC5 /* Supporting Files */, ); @@ -575,6 +581,7 @@ 55B859C51F44FC9F0012F5F4 /* LogMessage.swift in Sources */, 4C8835371C82530300F70419 /* Logger.swift in Sources */, 4C8835391C82530300F70419 /* LogLevel.swift in Sources */, + 26FAB8C9236058A80040B628 /* LogSource.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -600,6 +607,7 @@ 55B859C61F44FC9F0012F5F4 /* LogMessage.swift in Sources */, 4CA33F351B7556FC0047C307 /* Logger.swift in Sources */, 4CA33F371B7556FC0047C307 /* LogLevel.swift in Sources */, + 26FAB8CA236058A80040B628 /* LogSource.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -612,6 +620,7 @@ 55B859C41F44FC9F0012F5F4 /* LogMessage.swift in Sources */, 4C83FB2E1AEC1959003FEA49 /* LogLevel.swift in Sources */, 4CA86F631AEC4654005E6475 /* Logger.swift in Sources */, + 26FAB8C8236058A80040B628 /* LogSource.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -637,6 +646,7 @@ 55B859C31F44FC9F0012F5F4 /* LogMessage.swift in Sources */, 4C83FB2D1AEC1959003FEA49 /* LogLevel.swift in Sources */, 4CA86F621AEC4654005E6475 /* Logger.swift in Sources */, + 26FAB8C7236058A80040B628 /* LogSource.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; };