{"id":1346,"url":"https://github.com/Nike-Inc/Willow","last_synced_at":"2025-08-06T14:31:07.721Z","repository":{"id":41548618,"uuid":"57251684","full_name":"Nike-Inc/Willow","owner":"Nike-Inc","description":"Willow is a powerful, yet lightweight logging library written in Swift.","archived":false,"fork":false,"pushed_at":"2023-08-23T21:48:27.000Z","size":522,"stargazers_count":1351,"open_issues_count":11,"forks_count":80,"subscribers_count":40,"default_branch":"main","last_synced_at":"2024-10-29T17:58:24.397Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Nike-Inc.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2016-04-27T21:58:39.000Z","updated_at":"2024-10-25T15:58:31.000Z","dependencies_parsed_at":"2022-07-07T16:30:33.321Z","dependency_job_id":"08a056d6-afcf-4bda-ba53-51c87f6b5e75","html_url":"https://github.com/Nike-Inc/Willow","commit_stats":{"total_commits":313,"total_committers":15,"mean_commits":"20.866666666666667","dds":"0.25559105431309903","last_synced_commit":"e97c7935472d8ee183ce4c2f3676043a57c46aaf"},"previous_names":[],"tags_count":16,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Nike-Inc%2FWillow","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Nike-Inc%2FWillow/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Nike-Inc%2FWillow/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Nike-Inc%2FWillow/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Nike-Inc","download_url":"https://codeload.github.com/Nike-Inc/Willow/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":228539729,"owners_count":17933915,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-01-05T20:15:44.330Z","updated_at":"2025-08-06T14:31:07.694Z","avatar_url":"https://github.com/Nike-Inc.png","language":"Swift","funding_links":[],"categories":["Logging","Libs","Swift","Logger","Logging [🔝](#readme)"],"sub_categories":["Other Hardware","Logging","Other free courses"],"readme":"# Willow\n\n[![Build Status](https://travis-ci.org/Nike-Inc/Willow.svg?branch=master)](https://travis-ci.org/Nike-Inc/Willow)\n[![CocoaPods Compatible](https://img.shields.io/cocoapods/v/Willow.svg)](https://img.shields.io/cocoapods/v/Willow.svg)\n[![Carthage Compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)\n[![Platform](https://img.shields.io/cocoapods/p/Willow.svg?style=flat)](http://cocoadocs.org/docsets/Willow)\n\nWillow is a powerful, yet lightweight logging library written in Swift.\n\n- [Features](#features)\n- [Requirements](#requirements)\n- [Migration Guides](#migration-guides)\n- [Communication](#communication)\n- [Installation](#installation)\n    - [CocoaPods](#cocoapods)\n    - [Carthage](#carthage)\n- [Usage](#usage)\n    - [Creating a Logger](#creating-a-logger)\n    - [Logging Messages with Closures](#logging-messages-with-closures)\n    - [Disabling a Logger](#disabling-a-logger)\n    - [Synchronous and Asynchronous Logging](#synchronous-and-asynchronous-logging)\n    - [Log Modifiers](#log-modifiers)\n    - [Log Writers](#log-writers)\n- [Advanced Usage](#advanced-usage)\n    - [Creating Custom Log Levels](#creating-custom-log-levels)\n    - [Shared Loggers between Frameworks](#shared-loggers-between-frameworks)\n    - [Multiple Loggers, One Queue](#multiple-loggers-one-queue)\n    - [Adding Message Filters](#adding-message-filters)\n    - [Changing the log level at runtime](#changing-log-levels-at-runtime)\n- [FAQ](#faq)\n- [License](#license)\n- [Creators](#creators)\n\n## Features\n\n- [X] Default Log Levels\n- [X] Custom Log Levels\n- [X] Simple Logging Functions using Closures\n- [X] Configurable Synchronous or Asynchronous Execution\n- [X] Thread-Safe Logging Output (No Log Mangling)\n- [X] Custom Writers through Dependency Injection\n- [X] Custom Modifiers through Dependency Injection per Writer\n- [X] Supports Multiple Simultaneous Writers\n- [X] Shared Loggers Between Frameworks\n- [X] Shared Locks or Queues Between Multiple Loggers\n- [X] Comprehensive Unit Test Coverage\n- [X] Complete Documentation\n\n## Requirements\n\n- iOS 9.0+ / Mac OS X 10.11+ / tvOS 9.0+ / watchOS 2.0+\n- Xcode 9.3+\n- Swift 4.1+\n\n## Migration Guides\n\n- [Willow 2.0 Migration Guide](https://github.com/Nike-Inc/Willow/blob/master/Documentation/Willow%202.0%20Migration%20Guide.md)\n- [Willow 3.0 Migration Guide](https://github.com/Nike-Inc/Willow/blob/master/Documentation/Willow%203.0%20Migration%20Guide.md)\n- [Willow 4.0 Migration Guide](https://github.com/Nike-Inc/Willow/blob/master/Documentation/Willow%204.0%20Migration%20Guide.md)\n- [Willow 5.0 Migration Guide](https://github.com/Nike-Inc/Willow/blob/master/Documentation/Willow%205.0%20Migration%20Guide.md)\n\n## Communication\n\n- Need help? Open an issue.\n- Have a feature request? Open an issue.\n- Find a bug? Open an issue.\n- Want to contribute? Fork the repo and submit a pull request.\n\n## Installation\n\n### CocoaPods\n\n[CocoaPods](http://cocoapods.org) is a dependency manager for Cocoa projects.\nYou can install it with the following command:\n\n```bash\n[sudo] gem install cocoapods\n```\n\n\u003e CocoaPods 1.3+ is required.\n\nTo integrate Willow into your project, specify it in your [Podfile](http://guides.cocoapods.org/using/the-podfile.html):\n\n```ruby\nsource 'https://github.com/CocoaPods/Specs.git'\nplatform :ios, '11.0'\nuse_frameworks!\n\npod 'Willow', '~\u003e 5.0'\n```\n\nThen, run the following command:\n\n```bash\n$ pod install\n```\n\n### Carthage\n\n[Carthage](https://github.com/Carthage/Carthage) is a decentralized dependency manager that builds your dependencies and provides you with binary frameworks.\n\nYou can install Carthage with [Homebrew](http://brew.sh/) using the following command:\n\n```bash\n$ brew update\n$ brew install carthage\n```\n\nTo integrate Willow into your Xcode project using Carthage, specify it in your [Cartfile](https://github.com/Carthage/Carthage/blob/master/Documentation/Artifacts.md#cartfile):\n\n```\ngithub \"Nike-Inc/Willow\" ~\u003e 5.0\n```\n\nRun `carthage update` to build the framework and drag the built `Willow.framework` into your Xcode project.\n\n### Swift Package Manager\n\nThe [Swift Package Manager](https://swift.org/package-manager/) is a tool for automating the distribution of Swift code and is integrated into the `swift` compiler.\nIt is in early development, but Willow does support its use on supported platforms.\n\nOnce 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`.\n\n```swift\ndependencies: [\n    .package(url: \"https://github.com/Nike-Inc/Willow.git\", majorVersion: 5)\n]\n```\n\n---\n\n## Usage\n\n### Creating a Logger\n\n```swift\nimport Willow\n\nlet defaultLogger = Logger(logLevels: [.all], writers: [ConsoleWriter()])\n```\n\nThe `Logger` initializer takes three parameters to customize the behavior of the logger instance.\n\n- `logLevels: [LogLevel]` - The log message levels that should be processed.\nMessages that don't match the current log level are not processed.\n\n- `writers: [LogWriter]` - The array of writers to write to.\nWriters can be used to log output to a specific destination such as the console, a file, or an external service.\n\n- `executionMethod: ExecutionMethod = .synchronous(lock: NSRecursiveLock())` - The execution method used when writing messages.\n\n`Logger` objects can only be customized during initialization.\nIf you need to change a `Logger` at runtime, it is advised to create an additional logger with a custom configuration to fit your needs.\nIt is perfectly acceptable to have many different `Logger` instances running simultaneously.\n\n#### Thread Safety\n\nThe `print` function does not guarantee that the `String` parameter will be fully logged to the console.\nIf two `print` calls are happening simultaneously from two different queues (threads), the messages can get mangled, or intertwined.\n`Willow` guarantees that messages are completely finished writing before starting on the next one.\n\n\u003e It is important to note that by creating multiple `Logger` instances, you can potentially lose the guarantee of thread-safe logging.\n\u003e If you want to use multiple `Logger` instances, you should create a `NSRecursiveLock` or `DispatchQueue` that is shared between both configurations.\n\u003e For more info, see the [Advanced Usage](#advanced-usage) section.\n\n### Logging Messages and String Messages\n\nWillow can log two different types of objects: Messages and Strings.\n\n#### Log Messages\n\nMessages are structured data with a name and a dictionary of attributes.\nWillow declares the `LogMessage` protocol which frameworks and applications can use as the basis for concrete implementations.\nMessages 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).\n\n```swift\nenum Message: LogMessage {\n    case requestStarted(url: URL)\n    case requestCompleted(url: URL, response: HTTPURLResponse)\n\n    var name: String {\n        switch self {\n        case .requestStarted:   return \"Request started\"\n        case .requestCompleted: return \"Request completed\"\n        }\n    }\n\n    var attributes: [String: Any] {\n        switch self {\n        case let .requestStarted(url):\n            return [\"url\": url]\n\n        case let .requestCompleted(url, response):\n            return [\"url\": url, \"response_code\": response.statusCode]\n        }\n    }\n}\n\nlet url = URL(string: \"https://httpbin.org/get\")!\n\nlog.debug(Message.requestStarted(url: url))\nlog.info(Message.requestStarted(url: url))\nlog.event(Message.requestStarted(url: url))\nlog.warn(Message.requestStarted(url: url))\nlog.error(Message.requestStarted(url: url))\n```\n\n#### Log Message Strings\n\nLog message strings are just `String` instances with no additional data.\n\n```swift\nlet url = URL(string: \"https://httpbin.org/get\")!\n\nlog.debugMessage(\"Request Started: \\(url)\")\nlog.infoMessage(\"Request Started: \\(url)\")\nlog.eventMessage(\"Request Started: \\(url)\")\nlog.warnMessage(\"Request Started: \\(url)\")\nlog.errorMessage(\"Request Started: \\(url)\")\n```\n\n\u003e The log message string APIs have the `Message` suffix on the end to avoid ambiguity with the log message APIs.\n\u003e The multi-line escaping closure APIs collide without the suffix.\n\n### Logging Messages with Closures\n\nThe logging syntax of Willow was optimized to make logging as lightweight and easy to remember as possible.\nDevelopers should be able to focus on the task at hand and not remembering how to write a log message.\n\n#### Single Line Closures\n\n```swift\nlet log = Logger()\n\n// Option 1\nlog.debugMessage(\"Debug Message\")    // Debug Message\nlog.infoMessage(\"Info Message\")      // Info Message\nlog.eventMessage(\"Event Message\")    // Event Message\nlog.warnMessage(\"Warn Message\")      // Warn Message\nlog.errorMessage(\"Error Message\")    // Error Message\n\n// or\n\n// Option 2\nlog.debugMessage { \"Debug Message\" } // Debug Message\nlog.infoMessage { \"Info Message\" }   // Info Message\nlog.eventMessage { \"Event Message\" } // Event Message\nlog.warnMessage { \"Warn Message\" }   // Warn Message\nlog.errorMessage { \"Error Message\" } // Error Message\n```\n\nBoth of these approaches are equivalent.\nThe first set of APIs accept autoclosures and the second set accept closures.\n\n\u003e Feel free to use whichever syntax you prefer for your project.\nAlso, by default, only the `String` returned by the closure will be logged.\nSee the [Log Modifiers](#log-modifiers) section for more information about customizing log message formats.\n\nThe reason both sets of APIs use closures to extract the log message is performance.\n\n\u003e There are some VERY important performance considerations when designing a logging solution that are described in more detail in the [Closure Performance](#closure-performance) section.\n\n#### Multi-Line Closures\n\nLogging 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.\nWe want to make sure logic is encapsulated and very performant.\n`Willow` log level closures allow you to cleanly wrap all the logic to build up the message.\n\n```swift\nlog.debugMessage {\n    // First let's run a giant for loop to collect some info\n    // Now let's scan through the results to get some aggregate values\n    // Now I need to format the data\n    return \"Computed Data Value: \\(dataValue)\"\n}\n\nlog.infoMessage {\n    let countriesString = \",\".join(countriesArray)\n    return \"Countries: \\(countriesString)\"\n}\n```\n\n\u003e Unlike the Single Line Closures, the Multi-Line Closures require a `return` declaration.\n\n#### Closure Performance\n\nWillow works exclusively with logging closures to ensure the maximum performance in all situations.\nClosures defer the execution of all the logic inside the closure until absolutely necessary, including the string evaluation itself.\nIn cases where the Logger instance is disabled, log execution time was reduced by 97% over the traditional log message methods taking a `String` parameter.\nAdditionally, the overhead for creating a closure was measured at 1% over the traditional method making it negligible.\nIn summary, closures allow Willow to be extremely performant in all situations.\n\n### Disabling a Logger\n\nThe `Logger` class has an `enabled` property to allow you to completely disable logging.\nThis can be helpful for turning off specific `Logger` objects at the app level, or more commonly to disable logging in a third-party library.\n\n```swift\nlet log = Logger()\nlog.enabled = false\n\n// No log messages will get sent to the registered Writers\n\nlog.enabled = true\n\n// We're back in business...\n```\n\n### Synchronous and Asynchronous Logging\n\nLogging can greatly affect the runtime performance of your application or library.\nWillow makes it very easy to log messages synchronously or asynchronously.\nYou can define this behavior when creating the `LoggerConfiguration` for your `Logger` instance.\n\n```swift\nlet queue = DispatchQueue(label: \"serial.queue\", qos: .utility)\nlet log = Logger(logLevels: [.all], writers: [ConsoleWriter()], executionMethod: .asynchronous(queue: queue))\n```\n\n#### Synchronous Logging\n\nSynchronous logging is very helpful when you are developing your application or library.\nThe log operation will be completed before executing the next line of code.\nThis can be very useful when stepping through the debugger.\nThe downside is that this can seriously affect performance if logging on the main thread.\n\n#### Asynchronous Logging\n\nAsynchronous logging should be used for deployment builds of your application or library.\nThis will offload the logging operations to a separate dispatch queue that will not affect the performance of the main thread.\nThis allows you to still capture logs in the manner that the `Logger` is configured, yet not affect the performance of the main thread operations.\n\n\u003e These are large generalizations about the typical use cases for one approach versus the other.\n\u003e Before making a final decision about which approach to use when, you should really break down your use case in detail.\n\n### Log Writers\n\nWriting log messages to various locations is an essential feature of any robust logging library.\nThis is made possible in `Willow` through the `LogWriter` protocol.\n\n```swift\npublic protocol LogWriter {\n    func writeMessage(_ message: String, logLevel: LogLevel)\n    func writeMessage(_ message: Message, logLevel: LogLevel)\n}\n```\n\nAgain, this is an extremely lightweight design to allow for ultimate flexibility.\nAs long as your `LogWriter` classes conform, you can do anything with those log messages that you want.\nYou could write the message to the console, append it to a file, send it to a server, etc.\nHere's a quick look at a simple write that writes to the console.\n\n```swift\nopen class ConsoleWriter: LogWriter {\n    open func writeMessage(_ message: String, logLevel: LogLevel) {\n        print(message)\n    }\n\n    open func writeMessage(_ message: LogMessage, logLevel: LogLevel) {\n        let message = \"\\(message.name): \\(message.attributes)\"\n        print(message)\n    }\n}\n```\n\n### Log Modifiers\n\nLog message customization is something that `Willow` specializes in.\nSome devs want to add a prefix to their library output, some want different timestamp formats, some even want emoji!\nThere's no way to predict all the types of custom formatting teams are going to want to use.\nThis is where `LogModifier` objects come in.\n\n```swift\npublic protocol LogModifier {\n    func modifyMessage(_ message: String, with logLevel: LogLevel) -\u003e String\n}\n```\n\nThe `LogModifier` protocol has only a single API.\nIt receives the `message` and `logLevel` and returns a newly formatted `String`.\nThis is about as flexible as you can get.\n\nAs an added layer of convenience, writers intending to output strings (e.g. writing to the console, files, etc.) can conform to the `LogModifierWritier` protocol.\nThe `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.\n\nLet's walk through a simple example for adding a prefix to a logger for the `debug` and `info` log levels.\n\n```swift\nclass PrefixModifier: LogModifier {\n    func modifyMessage(_ message: String, with logLevel: Logger.LogLevel) -\u003e String {\n        return \"[Willow] \\(message)\"\n    }\n}\n\nlet prefixModifiers = [PrefixModifier()]\nlet writers = [ConsoleWriter(modifiers: prefixModifiers)]\nlet log = Logger(logLevels: [.debug, .info], writers: writers)\n```\n\nTo 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.\n\n```swift\nopen func writeMessage(_ message: String, logLevel: LogLevel) {\n    let message = modifyMessage(message, logLevel: logLevel)\n    print(message)\n}\n```\n\n#### Multiple Modifiers\n\nMultiple `LogModifier` objects can be stacked together onto a single log level to perform multiple actions.\nLet's walk through using the `TimestampModifier` (prefixes the message with a timestamp) in combination with an `EmojiModifier`.\n\n```swift\nclass EmojiModifier: LogModifier {\n    func modifyMessage(_ message: String, with logLevel: LogLevel) -\u003e String {\n        return \"🚀🚀🚀 \\(message)\"\n    }\n}\n\nlet writers: = [ConsoleWriter(modifiers: [EmojiModifier(), TimestampModifier()])]\nlet log = Logger(logLevels: [.all], writers: writers)\n```\n\n`Willow` doesn't have any hard limits on the total number of `LogModifier` objects that can be applied to a single log level.\nJust keep in mind that performance is key.\n\n\u003e The default `ConsoleWriter` will execute the modifiers in the same order they were added into the `Array`.\nIn the previous example, Willow would log a much different message if the `TimestampModifier` was inserted before the `EmojiModifier`.\n\n#### OSLog\n\nThe `OSLogWriter` class allows you to use the `os_log` APIs within the Willow system.\nIn order to use it, all you need to do is to create the `LogModifier` instance and add it to the `Logger`.\n\n```swift\nlet writers = [OSLogWriter(subsystem: \"com.nike.willow.example\", category: \"testing\")]\nlet log = Logger(logLevels: [.all], writers: writers)\n\nlog.debugMessage(\"Hello world...coming to your from the os_log APIs!\")\n```\n\n#### Multiple Writers\n\nSo what about logging to both a file and the console at the same time? No problem.\nYou can pass multiple `LogWriter` objects into the `Logger` initializer.\nThe `Logger` will execute each `LogWriter` in the order it was passed in.\nFor example, let's create a `FileWriter` and combine that with our `ConsoleWriter`.\n\n```swift\npublic class FileWriter: LogWriter {\n    public func writeMessage(_ message: String, logLevel: Logger.LogLevel, modifiers: [LogMessageModifier]?) {\n\t    var message = message\n        modifiers?.map { message = $0.modifyMessage(message, with: logLevel) }\n        // Write the formatted message to a file (We'll leave this to you!)\n    }\n\n    public func writeMessage(_ message: LogMessage, logLevel: LogLevel) {\n        let message = \"\\(message.name): \\(message.attributes)\"\n        // Write the formatted message to a file (We'll leave this to you!)\n    }\n}\n\nlet writers: [LogMessageWriter] = [FileWriter(), ConsoleWriter()]\nlet log = Logger(logLevels: [.all], writers: writers)\n```\n\n\u003e `LogWriter` objects can also be selective about which modifiers they want to run for a particular log level.\n\u003e All the examples run all the modifiers, but you can be selective if you want to be.\n\n---\n\n## Advanced Usage\n\n### Creating Custom Log Levels\n\nDepending upon the situation, the need to support additional log levels may arise.\nWillow can easily support additional log levels through the art of [bitmasking](http://en.wikipedia.org/wiki/Mask_(computing)).\nSince the internal `RawValue` of a `LogLevel` is a `UInt`, Willow can support up to 32 log levels simultaneously for a single `Logger`.\nSince there are 7 default log levels, Willow can support up to 27 custom log levels for a single logger.\nThat should be more than enough to handle even the most complex of logging solutions.\n\nCreating custom log levels is very simple. Here's a quick example of how to do so.\nFirst, you must create a `LogLevel` extension and add your custom values.\n\n```swift\nextension LogLevel {\n    private static var verbose = LogLevel(rawValue: 0b00000000_00000000_00000001_00000000)\n}\n```\n\n\u003e It's a good idea to make the values for custom log levels `var` instead of `let`.\nIn the event of two frameworks using the same custom log level bit mask, the application can re-assign one of the frameworks to a new value.\n\nNow that we have a custom log level called `verbose`, we need to extend the `Logger` class to be able to easily call it.\n\n```swift\nextension Logger {\n    public func verboseMessage(_ message: @autoclosure @escaping () -\u003e String) {\n    \tlogMessage(message, with: .verbose)\n    }\n\n    public func verboseMessage(_ message: @escaping () -\u003e String) {\n    \tlogMessage(message, with: .verbose)\n    }\n}\n```\n\nFinally, using the new log level is a simple as...\n\n```swift\nlet log = Logger(logLevels: [.all], writers: [ConsoleWriter()])\nlog.verboseMessage(\"My first verbose log message!\")\n```\n\n\u003e The `all` log level contains a bitmask where all bits are set to 1.\n\u003e This means that the `all` log level will contain all custom log levels automatically.\n\n### Shared Loggers between Frameworks\n\nDefining a single `Logger` and sharing that instance several frameworks can be very advantageous, especially with the addition of Frameworks in iOS 8.\nNow that we're going to be creating more frameworks inside our own apps to be shared between apps, extensions and third party libraries, wouldn't it be nice if we could share `Logger` instances?\n\nLet's walk through a quick example of a `Math` framework sharing a `Logger` with it's parent `Calculator` app.\n\n```swift\n//=========== Inside Math.swift ===========\npublic var log: Logger?\n\n//=========== Calculator.swift ===========\nimport Math\n\nlet writers: [LogMessageWriter] = [FileWriter(), ConsoleWriter()]\nvar log = Logger(logLevels: [.all], writers: writers)\n\n// Set the Math.log instance to the Calculator.log to share the same Logger instance\nMath.log = log\n```\n\nIt's very simple to swap out a pre-existing `Logger` with a new one.\n\n### Multiple Loggers, One Queue\n\nThe previous example showed how to share `Logger` instances between multiple frameworks.\nSomething more likely though is that you would want to have each third party library or internal framework to have their own `Logger` with their own configuration.\nThe one thing that you really want to share is the `NSRecursiveLock` or `DispatchQueue` that they run on.\nThis will ensure all your logging is thread-safe.\nHere's the previous example demonstrating how to create multiple `Logger` instances and still share the queue.\n\n```swift\n//=========== Inside Math.swift ===========\npublic var log: Logger?\n\n//=========== Calculator.swift ===========\nimport Math\n\n// Create a single queue to share\nlet sharedQueue = DispatchQueue(label: \"com.math.logger\", qos: .utility)\n\n// Create the Calculator.log with multiple writers and a .Debug log level\nlet writers: [LogMessageWriter] = [FileWriter(), ConsoleWriter()]\n\nvar log = Logger(\n    logLevels: [.all],\n    writers: writers,\n    executionMethod: .asynchronous(queue: sharedQueue)\n)\n\n// Replace the Math.log with a new instance with all the same configuration values except a shared queue\nMath.log = Logger(\n    logLevels: log.logLevels,\n    writers: [ConsoleWriter()],\n    executionMethod: .asynchronous(queue: sharedQueue)\n)\n```\n\n`Willow` is a very lightweight library, but its flexibility allows it to become very powerful if you so wish.\n\n---\n\n### Adding Message Filters\n\nSometimes you may wish to have finer-grained control over when some log messages are included. For instance,\nif you wanted to ignore logs that have a given attribute, based on whatever dynamic logic you have.\n\nThis is useful if you have a way to toggle log subsystems on/off within the app in a DEBUG/ADHOC scenario.\n\nTo define a filter, create a type that implements the ``LogFilter`` protocol.\n\nHere is an example of a filter that can conditionally exclude noisy logs for an analytics subsystem:\n\n```swift\nstruct AnalyticsLogFilter: LogFilter {\n    let name = \"analytics\"\n    \n    func shouldInclude(_ message: LogMessage, logLevel: LogLevel) -\u003e Bool {\n        // only consider those with a given attribute\n        guard message.attributes[\"subsystem\"] == \"analytics\" else { return true }\n        \n        return logLevel != .debug\n    }\n    \n    func shouldInclude(_ message: String, logLevel: LogLevel) -\u003e Bool {\n        // we don't have any additional context for string messages, so always include\n        return true\n    }\n}\n```\n\nWith this filter you can now conditionally add this to the logger:\n\n```swift\nlogger.addFilter(AnalyticsLogFilter())\n```\n\nOr later if you want to remove it:\n\n```swift\nlogger.removeFilter(named: \"analytics\")\n// or \nlogger.removeFilters()\n```\n\nMessages that return `false` from the filter will not be emitted.\n\n### Changing log levels at runtime\n\nIt can be advantageous in DEBUG and ADHOC builds to allow testers to change the log level to include messages\nthat would otherwise be too noisy to include by default.\n\nYou can change the log level at runtime by calling `logger.setLogLevels(...)`. Note that this is passed an OptionSet,\nso you need to include all of the log levels you want to include.\n\nIf you are using the default set of options, you can use the `.minimum` helper method to include all levels above\na given log level. For instance, to include `.info` and above:\n\n```swift\nlogger.setLogLevels(.minimum(.info))\n```\n\nThis method is not supported if you are using custom log levels.\n\n## FAQ\n\n### Why 5 default log levels? And why are they so named?\n\nSimple...simplicity and elegance.\nContextually it gets difficult to understand which log level you need if you have too many.\nHowever, that doesn't mean that this is always the perfect solution for everyone or every use case.\nThis is why there are 5 default log levels, with support for easily adding additional ones.\n\nAs for the naming, here's our mental breakdown of each log level for an iOS app (obviously it depends on your use case).\n\n* `debug` - Highly detailed information of a context\n* `info` - Summary information of a context\n* `event` - User driven interactions such as button taps, view transitions, selecting a cell\n* `warn` - An error occurred but it is recoverable\n* `error` - A non-recoverable error occurred\n\n### When should I use Willow?\n\nIf you are starting a new iOS project in Swift and want to take advantage of many new conventions and features of the language, Willow would be a great choice.\nIf you are still working in Objective-C, a pure Objective-C library such as [CocoaLumberjack](https://github.com/CocoaLumberjack/CocoaLumberjack) would probably be more appropriate.\n\n### Where did the name Willow come from?\n\nWillow is named after the one, the only, Willow tree.\n\n---\n\n## License\n\nWillow is released under the MIT license.\nSee LICENSE for details.\n\n## Creators\n\n- [Christian Noon](https://github.com/cnoon) ([@Christian_Noon](https://twitter.com/Christian_Noon))\n- [Eric Appel](https://github.com/ericappel) ([@EricAppel](https://twitter.com/EricAppel))\n- [Dave Camp](https://github.com/atomiccat) ([@thinbits](https://twitter.com/thinbits))\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FNike-Inc%2FWillow","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FNike-Inc%2FWillow","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FNike-Inc%2FWillow/lists"}