Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Passing FileName and LineNumber (Issue #57) #60

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 36 additions & 8 deletions Example/Frameworks/Database/Logger.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}

Expand Down
9 changes: 5 additions & 4 deletions Example/iOS Example/WillowConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)"
Expand All @@ -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
Expand Down Expand Up @@ -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)
}
Expand Down
28 changes: 14 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
```

Expand All @@ -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)
}
Expand All @@ -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)"
}
}
Expand All @@ -395,28 +395,28 @@ 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)
}
```

#### 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)
```

Expand Down
32 changes: 28 additions & 4 deletions Source/LogModifier.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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: -
Expand All @@ -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)"
}
}
72 changes: 72 additions & 0 deletions Source/LogSource.swift
Original file line number Diff line number Diff line change
@@ -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)"
}
}
33 changes: 19 additions & 14 deletions Source/LogWriter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
}
}
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down
Loading