Willow
Willow is a powerful, yet lightweight logging library written in Swift.
Install / Use
/learn @Nike-Inc/WillowREADME
Willow
Willow is a powerful, yet lightweight logging library written in Swift.
- Features
- Requirements
- Migration Guides
- Communication
- Installation
- Usage
- Advanced Usage
- FAQ
- License
- Creators
Features
- [X] Default Log Levels
- [X] Custom Log Levels
- [X] Simple Logging Functions using Closures
- [X] Configurable Synchronous or Asynchronous Execution
- [X] Thread-Safe Logging Output (No Log Mangling)
- [X] Custom Writers through Dependency Injection
- [X] Custom Modifiers through Dependency Injection per Writer
- [X] Supports Multiple Simultaneous Writers
- [X] Shared Loggers Between Frameworks
- [X] Shared Locks or Queues Between Multiple Loggers
- [X] Comprehensive Unit Test Coverage
- [X] Complete Documentation
Requirements
- iOS 9.0+ / Mac OS X 10.11+ / tvOS 9.0+ / watchOS 2.0+
- Xcode 9.3+
- Swift 4.1+
Migration Guides
- Willow 2.0 Migration Guide
- Willow 3.0 Migration Guide
- Willow 4.0 Migration Guide
- Willow 5.0 Migration Guide
Communication
- Need help? Open an issue.
- Have a feature request? Open an issue.
- Find a bug? Open an issue.
- Want to contribute? Fork the repo and submit a pull request.
Installation
CocoaPods
CocoaPods is a dependency manager for Cocoa projects. You can install it with the following command:
[sudo] gem install cocoapods
CocoaPods 1.3+ is required.
To integrate Willow into your project, specify it in your Podfile:
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '11.0'
use_frameworks!
pod 'Willow', '~> 5.0'
Then, run the following command:
$ pod install
Carthage
Carthage is a decentralized dependency manager that builds your dependencies and provides you with binary frameworks.
You can install Carthage with Homebrew using the following command:
$ brew update
$ brew install carthage
To integrate Willow into your Xcode project using Carthage, specify it in your Cartfile:
github "Nike-Inc/Willow" ~> 5.0
Run carthage update to build the framework and drag the built Willow.framework into your Xcode project.
Swift Package Manager
The Swift Package Manager is a tool for automating the distribution of Swift code and is integrated into the swift compiler.
It is in early development, but Willow does support its use on supported platforms.
Once you have your Swift package set up, adding Willow as a dependency is as easy as adding it to the dependencies value of your Package.swift.
dependencies: [
.package(url: "https://github.com/Nike-Inc/Willow.git", majorVersion: 5)
]
Usage
Creating a Logger
import Willow
let defaultLogger = Logger(logLevels: [.all], writers: [ConsoleWriter()])
The Logger initializer takes three parameters to customize the behavior of the logger instance.
-
logLevels: [LogLevel]- The log message levels that should be processed. Messages that don't match the current log level are not processed. -
writers: [LogWriter]- The array of writers to write to. Writers can be used to log output to a specific destination such as the console, a file, or an external service. -
executionMethod: ExecutionMethod = .synchronous(lock: NSRecursiveLock())- The execution method used when writing messages.
Logger objects can only be customized during initialization.
If you need to change a Logger at runtime, it is advised to create an additional logger with a custom configuration to fit your needs.
It is perfectly acceptable to have many different Logger instances running simultaneously.
Thread Safety
The print function does not guarantee that the String parameter will be fully logged to the console.
If two print calls are happening simultaneously from two different queues (threads), the messages can get mangled, or intertwined.
Willow guarantees that messages are completely finished writing before starting on the next one.
It is important to note that by creating multiple
Loggerinstances, you can potentially lose the guarantee of thread-safe logging. If you want to use multipleLoggerinstances, you should create aNSRecursiveLockorDispatchQueuethat is shared between both configurations. For more info, see the Advanced Usage section.
Logging Messages and String Messages
Willow can log two different types of objects: Messages and Strings.
Log Messages
Messages are structured data with a name and a dictionary of attributes.
Willow declares the LogMessage protocol which frameworks and applications can use as the basis for concrete implementations.
Messages are a good choice if you want to provide context information along with the log text (e.g. routing log information to an external system like New Relic).
enum Message: LogMessage {
case requestStarted(url: URL)
case requestCompleted(url: URL, response: HTTPURLResponse)
var name: String {
switch self {
case .requestStarted: return "Request started"
case .requestCompleted: return "Request completed"
}
}
var attributes: [String: Any] {
switch self {
case let .requestStarted(url):
return ["url": url]
case let .requestCompleted(url, response):
return ["url": url, "response_code": response.statusCode]
}
}
}
let url = URL(string: "https://httpbin.org/get")!
log.debug(Message.requestStarted(url: url))
log.info(Message.requestStarted(url: url))
log.event(Message.requestStarted(url: url))
log.warn(Message.requestStarted(url: url))
log.error(Message.requestStarted(url: url))
Log Message Strings
Log message strings are just String instances with no additional data.
let url = URL(string: "https://httpbin.org/get")!
log.debugMessage("Request Started: \(url)")
log.infoMessage("Request Started: \(url)")
log.eventMessage("Request Started: \(url)")
log.warnMessage("Request Started: \(url)")
log.errorMessage("Request Started: \(url)")
The log message string APIs have the
Messagesuffix on the end to avoid ambiguity with the log message APIs. The multi-line escaping closure APIs collide without the suffix.
Logging Messages with Closures
The logging syntax of Willow was optimized to make logging as lightweight and easy to remember as possible. Developers should be able to focus on the task at hand and not remembering how to write a log message.
Single Line Closures
let log = Logger()
// Option 1
log.debugMessage("Debug Message") // Debug Message
log.infoMessage("Info Message") // Info Message
log.eventMessage("Event Message") // Event Message
log.warnMessage("Warn Message") // Warn Message
log.errorMessage("Error Message") // Error Message
// or
// Option 2
log.debugMessage { "Debug Message" } // Debug Message
log.infoMessage { "Info Message" } // Info Message
log.eventMessage { "Event Message" } // Event Message
log.warnMessage { "Warn Message" } // Warn Message
log.errorMessage { "Error Message" } // Error Message
Both of these approaches are equivalent. The first set of APIs accept autoclosures and the second set accept closures.
Feel free to use whichever syntax you prefer for your project. Also, by default, only the
Stringreturned by the closure will be logged. See the Log Modifiers section for more information about customizing log message formats.
The reason both sets of APIs use closures to extract the log message is performance.
There are some VERY important performance considerations when designing a logging solution that are described in more detail in the Closure Performance section.
Multi-Line Closures
Logging a message is easy, but knowing when to add the logic necessary to build a log message and tune it for performance can be a bit tricky.
We want to make sure logic is encapsulated and very performant.
Willow lo
