Most natural Swift logging

Overview

Evergreen

Most natural Swift logging

Build Status CocoaPods Compatible Platform Gitter

Evergreen is a logging framework written in Swift. It is designed to work just as you would expect, yet so versatile you can make it work however you wish.

Integrate Evergreen logging into your Swift project to replace those plain print() statements with calls to Evergreen's versatile logging functions that make it easy to adjust the verbosity of the output, log to multiple destinations (e.g. a file) with custom formatting and even measure time.

import Evergreen

log("Hello World!", forLevel: .info)
[AppDelegate.swift|INFO] Hello World!

Evergreen logging is great to use in any Swift project, but particularly useful when developing a framework. Give the users of your framework the opportunity to easily adjust the verbosity of the output your framework generates.

For Swift 4 development use the swift4 branch.

About Logging

Logging is a means of tracking events that happen when some software runs. The software’s developer adds logging calls to their code to indicate that certain events have occurred. An event is described by a descriptive message which can optionally contain variable data (i.e. data that is potentially different for each occurrence of the event). Events also have an importance which the developer ascribes to the event; the importance can also be called the level or severity.

— From the Python documentation.

That's not what logging is for everyone, though. There are people who would say:

Logging is the cutting, skidding, on-site processing, and loading of trees or logs onto trucks or skeleton cars.

— From Wikipedia.

For now, let's focus on what the Python people tell us. They seem to know what they are talking about.

Installation

CocoaPods

The easiest way to integrate Evergreen into your project is via CocoaPods:

  1. Install CocoaPods:

    $ gem install cocoapods
  2. Create a Podfile in you project's directory or add Evergreen to your existing Podfile:

    source 'https://github.com/CocoaPods/Specs.git'
    platform :ios, '8.0'
    use_frameworks!
    
    pod 'Evergreen'
    
  3. Let CocoaPods do its magic:

    $ pod install

As usual with CocoaPods, make sure to use the *.xcworkspace instead of the *.xcodeproj.

Swift Package Manager

You can also use the Swift Package Manager included in the Swift developer releases. Just add Evergreen as a dependency to your package description, like this:

import PackageDescription

let package = Package(
    name: "HelloWorld",
    dependencies: [
        .Package(url: "https://github.com/knly/Evergreen.git", majorVersion: 0),
        // ...
    ]
)

Manually

You can also integrate Evergreen into you project manually:

  1. Add Evergreen as a submodule:

    $ git submodule add https://github.com/knly/Evergreen.git
  2. Drag Evergreen.xcodeproj into the file navigator of your project.

  3. In the target configuration under General, add Evergreen.framework to the Embedded Binaries


Usage

Note: Open the Evergreen.xcworkspace and have a look at the Evergreen.playground for a more interactive tour through Evergreen's functionality. You can also create a Playground in your project's workspace and import Evergreen to try for yourself.

Logging without configuration

import Evergreen

log("Hello World!")

You can log events without any configuration and see a nicely formatted message show up in the console. This is for very quick and basic use only. Read on to learn about Evergreen's more sophisticated logging options.

Using Log Levels

You can assign an importance or severity to an event corresponding to one of the following log levels:

  • Critical: Events that are unexpected and can cause serious problems. You would want to be called in the middle of the night to deal with these.
  • Error: Events that are unexpected and not handled by your software. Someone should tell you about these ASAP.
  • Warning: Events that are unexpected, but will probably not affect the runtime of your software. You would want to investigate these eventually.
  • Info: General events that document the software's lifecycle.
  • Debug: Events to give you an understanding about the flow through your software, mainly for debugging purposes.
  • Verbose: Detailed information about the environment to provide additional context when needed.

The logger that handles the event has a log level as well. If the event's log level is lower than the logger's, it will not be logged.

In addition to the log levels above, a logger can have one of the following log levels. Assigning these to events only makes sense in specific use cases.

  • All: All events will be logged.
  • Off: No events will be logged.

With log levels, you can control the verbosity of your software. A common use case is to use a low log level during development and increase it in a release environment, or to adjust it for specific parts of your software and different logging destinations, such as the console or a file.

For a basic configuration, you can adjust Evergreen's default log level. Read about the logger hierarchy below to learn how to control the log level more granually.

Evergreen.logLevel = .debug

// These events will be logged, because their log level is >= .debug
log("Debug", forLevel: .debug)
log("Info", forLevel: .info)
log("Warning", forLevel: .warning)
log("Error", forLevel: .error)
log("Critical", forLevel: .critical)

// These events will not be logged, because their log level is < .debug
log("Verbose", forLevel: .verbose)

Every log level has a corresponding log function for convenience:

debug("Debug") // equivalent to log("Debug", forLevel: .debug)
info("Info") // equivalent to log("Info", forLevel: .info)
// ...

Using the Logger Hierarchy

You usually want to use loggers to log events instead of the global functions. A logger is always part of a hierarchy and inherits attributes, such as the log level, from its parent. This way, you can provide a default configuration and adjust it for specific parts of your software.

This is particularly useful during development to lower the log level of the part of your software you are currently working on.

Every logger has a key to identify the source of any given event. In its hierarchy, the key expands to a dot-separated key path, such as "Parent.Child".

You can manually build your own hierarchy, of course, but Evergreen provides a convenient way for you to utilize this powerful feature:

  • The default logger is the root of the logger hierarchy and can be retrieved using the Evergreen.defaultLogger constant. Use it to set a default log level. The global variable Evergreen.logLevel also refers to the default logger.
  • Retrieve an appropriate logger using the global Evergreen.getLogger function or the Logger.child instance method. Provide a key path that describes the part of your software the event is relevant for, such as "MyModule.MyType". These methods will always return the same logger instance for a given key path and establish the logger hierarchy, if it does not yet exist.

It is convenient to use a constant stored attribute to make an appropriate logger available for a given type:

import Evergreen

class MyType {

	let logger = Evergreen.getLogger("MyModule.MyType")

	init() {
		self.logger.debug("Initializing...")
	}

}

Having established a logger hierarchy, you can adjust the logging configuration for parts of it:

Evergreen.logLevel = .warning // Set the `defaultLogger`'s log level to .warning
let logger = Evergreen.getLogger("MyModule") // Retrieve the logger with key "MyModule" directly descending from the default logger
logger.logLevel = .debug // We are working on this part of the software, so set its log level to .debug

Note: A good place to do this configuration for production is in the AppDelegate's application:didFinishLaunchingWithOptions: method. Temporary log level adjustments are best configured as environment variables as described in the following section.

Using Environment Variables for Configuration

The preferred way to temporarily configure the logger hierarchy is using environment variables. This way, you can conveniently enable more verbose logging for the parts of your software you are currently working on. In Xcode, choose your target from the dropdown in the toolbar, select Edit Scheme... > Run > Arguments and add environment variables to the list. Then call:

Evergreen.configureFromEnvironment()

Every environment variable prefixed Evergreen is evaluated as a logger key path and assigned a log level corresponding to the variable's value. Values should match the log level descriptions, e.g. Debug or Warning.

Valid environment variable declarations would be e.g. Evergreen = Debug or Evergreen.MyLogger = Verbose.

Logging Errors alongside your Events

You can pass any error conforming to Swift's Error type (such as NSError) to Evergreen's logging functions, either as the message or in the separate error: argument:

let error: Error // some error
debug("Something unexpected happened here!", error: error)

Measuring Time

Easily measure the time between two events with tic and toc:

tic(andLog: "Starting expensive operation...", forLevel: .debug)
// ...
toc(andLog: "Completed expensive operation!", forLevel: .info)
[Default|DEBUG] Starting expensive operation...
[Default|INFO] Completed expensive operation! [ELAPSED TIME: 0.0435580015182495s]

You can also use the timerKey argument for nested timing:

tic(andLog: "Starting expensive operation...", forLevel: .debug, timerKey: "expensiveOperation")
for var i=0; i<10; i++ {
	tic(andLog: "\(i+1). iteration...", forLevel: .verbose, timerKey: "iteration")
	// ...
	toc(andLog: "Done!", forLevel: .verbose, timerKey: "iteration")
}
toc(andLog: "Completed expensive operation!", forLevel: .info, timerKey: "expensiveOperation")

Logging Events only once

You can keep similar events from being logged in excessive amounts by associating a key with them in any logging call, e.g.:

debug("Announcing this once!", onceForKey: "announcement")

Advanced Usage

Using Handlers

When a logger determines that an event should be handled, it will pass it to its handlers. A handler uses its formatter to retrieve a human-readable record from the event and then emits the record. Subclasses of Handler emit records in different ways:

  • A ConsoleHandler prints the record to the console.
  • A FileHandler writes the records to a file.
  • A StenographyHandler appends the record to an array in memory.

You can override emit in you own subclass to implement any custom behaviour you like, e.g. send it to a server.

Evergreen's defaultLogger has a ConsoleHandler attached by default, so every event in its hierarchy will be logged to the console. You can easily add additional handlers, by appending them to an appropriate logger's handlers array:

let logger = Evergreen.defaultLogger
let stenographyHandler = StenographyHandler()
logger.handlers.append(stenographyHandler)

You can also set a handler's logLevel, to add an additional level of filtering.

Formatting

Evergreen's Formatter class implements a convenient way for you to adjust the format of log records.

The default implementation of the string(from event: Event<M>) method, that you can also override in a subclass, uses a list components: [Formatter.Component] to construct a record from an event using instances of the Formatter.Component enumeration:

let simpleFormatter = Formatter(components: [ .Text("["), .Logger, .Text("|"), .LogLevel, .Text("] "), .Message ])
let consoleHandler = ConsoleHandler(formatter: simpleFormatter)
Evergreen.defaultLogger.handlers = [ consoleHandler ]

Contact

Evergreen was created and is maintained by Nils Leif Fischer.

I would greatly appreciate some professional opinions on the API and architecture. Please let me know any suggestions via Email ([email protected]), on Gitter or by opening an issue.

License

Evergreen is released under the MIT license. See LICENSE.md for details.

Comments
  • Build fails when using Swift Package Manager: Tests/Evergreen has an invalid name

    Build fails when using Swift Package Manager: Tests/Evergreen has an invalid name

    Swift Package Manager build fails with error: the module at Tests/Evergreen has an invalid name ('Evergreen').

    cat Package.swift
    import PackageDescription
    
    let package = Package(
        name: "linuxlog",
        dependencies: [
            .Package(url: "https://github.com/knly/Evergreen.git", majorVersion: 0),
            // ...
        ]
    )
    
    swift build
    Cloning https://github.com/knly/Evergreen.git
    HEAD is now at 2eefe6f removed initial info loggin for now, improved documentation
    Resolved version: 0.8.2
    error: the module at Tests/Evergreen has an invalid name ('Evergreen'): the name of a test module has no ‘Tests’ suffix
    fix: rename the module at ‘Tests/Evergreen’ to have a ‘Tests’ suffix
    
    swift --version
    Apple Swift version 3.1 (swiftlang-802.0.31.3 clang-802.0.30.2)
    Target: x86_64-apple-macosx10.9
    
    opened by maximveksler 1
  • Fix FileHandler file path resolution

    Fix FileHandler file path resolution

    The latest commits break the FileHandler file path resolution.

    Currently, FileHandler initializer always returns nil because file path is created using fileURL.absoluteString instead of fileURL.path.

    In fact, for a local file "mylog.txt" located in /tmp, path will be "file://tmp/mylog.txt" if absoluteString is used. Thus FileManager.createFile() will fail with this path, because the function expects a regular path like "/tmp/mylog.txt"

    opened by frajaona 0
  • Use autoclosures to lazily evaluate messages

    Use autoclosures to lazily evaluate messages

    Message should only be evaluated when they are handled, as they may contain expensive operations that have to be carried out just for logging purposes.

    opened by nilsvu 0
  • Tag new release for Swift 4?

    Tag new release for Swift 4?

    I've been using the swift4 branch for quite a while successfully in some of my Swift 4 projects. Could you tag a new release for SwiftPM to find, so we can reference/pin an exact version? Thank you!

    opened by Bouke 0
  • Use SemVer tags for SwiftPM

    Use SemVer tags for SwiftPM

    SwiftPM uses SemVer (x.y.z) to find versions inside git repos. The latest version it can find for Evergreen is 0.8.2. However the latest tag is v0.10. Can you tag that version as 0.10.0 (or whatever you find applicable)?

    opened by Bouke 0
  • Test and possibly fix unordered output stream when logging from different operation queues

    Test and possibly fix unordered output stream when logging from different operation queues

    Records printed to the console could possibly get mixed up when logging from different operation queues, in particular for asynchronous calls. This should be investigated at first, but can be easily fixed by introducing a dedicated NSOperationQueue for logging purposes.

    opened by nilsvu 0
Owner
Nils Leif Fischer
Theoretical physicist pursuing my PhD in numerical relativity. Also passionate about software development, design, data visualization and teaching.
Nils Leif Fischer
JustLog brings logging on iOS to the next level. It supports console, file and remote Logstash logging via TCP socket with no effort. Support for logz.io available.

JustLog JustLog takes logging on iOS to the next level. It supports console, file and remote Logstash logging via TCP socket with no effort. Support f

Just Eat 509 Dec 10, 2022
Twitter Logging Service is a robust and performant logging framework for iOS clients

Twitter Logging Service Background Twitter created a framework for logging in order to fulfill the following requirements: fast (no blocking the main

Twitter 290 Nov 15, 2022
Convenient & secure logging during development & release in Swift 3, 4 & 5

Colorful, flexible, lightweight logging for Swift 3, Swift 4 & Swift 5. Great for development & release with support for Console, File & cloud platfor

SwiftyBeaver 5.6k Jan 4, 2023
CleanroomLogger provides an extensible Swift-based logging API that is simple, lightweight and performant

CleanroomLogger CleanroomLogger provides an extensible Swift-based logging API that is simple, lightweight and performant. The API provided by Cleanro

null 1.3k Dec 8, 2022
Willow is a powerful, yet lightweight logging library written in Swift.

Willow Willow is a powerful, yet lightweight logging library written in Swift. Features Requirements Migration Guides Communication Installation Cocoa

Nike Inc. 1.3k Nov 16, 2022
Swift Logging Utility for Xcode & Google Docs

QorumLogs Swift Logging Utility in Xcode & Google Docs

Goktug Yilmaz 777 Jul 15, 2022
A lightweight logging framework for Swift

HeliumLogger Provides a lightweight logging implementation for Swift which logs to standard output. Features Logs output to stdout by default. You can

Kitura 174 Nov 30, 2022
A lightweight logging framework for Swift

HeliumLogger Provides a lightweight logging implementation for Swift which logs to standard output. Features Logs output to stdout by default. You can

Kitura 174 Nov 30, 2022
TraceLog is a highly configurable, flexible, portable, and simple to use debug logging system for Swift and Objective-C applications running on Linux, macOS, iOS, watchOS, and tvOS.

Please star this github repository to stay up to date. TraceLog Introduction TraceLog is a highly configurable, flexible, portable, and simple to use

Tony Stone 52 Oct 28, 2022
A flexible logging library written in Swift

Puppy Puppy is a flexible logging library written in Swift ?? It supports multiple transports(console, file, syslog, and oslog) as loggers. It not onl

Koichi Yokota 92 Dec 29, 2022
An extensible logging framework for Swift

Log is a powerful logging framework that provides built-in themes and formatters, and a nice API to define your owns. Get the most out of Log by insta

Damien 825 Nov 6, 2022
Logging utility for Swift and Objective C

Swell - Swift Logging A logging utility for Swift and Objective C. ##Features Turn on logging during development, turn them off when building for the

Hubert Rabago 361 Jun 29, 2022
A powerful input-agnostic swift logging framework made to speed up development with maximum readability.

The Swift logging framework. Atlantis is an extremely powerful logging framework that I've created for everyday use, including enterprise development

Andrew Aquino 199 Jan 2, 2023
Simple, lightweight and flexible debug logging framework written in Swift

AELog Simple, lightweight and flexible debug logging minion written in Swift If you find yourself in upcoming statements, then you probably want to us

Marko Tadić 28 Jul 6, 2022
Spy is a flexible, lightweight, multiplatform logging utility written in pure Swift.

Spy is a flexible, lightweight, multiplatform logging utility written in pure Swift. It allows to log with different levels and on different channels. You can define what levels and channels actually are.

AppUnite Sp. z o.o. Spk. 12 Jul 28, 2021
A simple logging package for Swift

OhMyLog OhMyLog is a simple logging package for Swift. It supports the following features: Six logging levels ( ?? , ?? , ?? , ⚠️ , ?? , ?? ) Display

Junhao Wang 1 Jan 10, 2022
Simple logging for simples needs.

Simple logging for simples needs.

native.dev.br 0 May 30, 2022
Class for logging excessive blocking on the main thread

Watchdog Class for logging excessive blocking on the main thread. It watches the main thread and checks if it doesn’t get blocked for more than define

Wojtek Lukaszuk 1.8k Dec 6, 2022
In-App iOS Debugging Tool With Enhanced Logging, Networking Info, Crash reporting And More.

The debugger tool for iOS developer. Display logs, network request, device informations, crash logs while using the app. Easy accessible with its bubble head button ?? . Easy to integrate in any apps, to handle development or testing apps easier. First version, there is plenty of room for improvement.

Remi ROBERT 1.8k Dec 29, 2022