Home

Awesome

Build Status codecov Carthage compatible Cocoapods Platform Platform License

Spy is a flexible, lightweight, multiplatform logging utility written in pure Swift. It allows to log on different levels and channels which you can define on your own depending on your needs.

Requirements

Development

Project uses following tools for development

  1. XCodeGen
  2. Cocoapods
  3. SwiftLint
  4. Sourcery

Installation

To get started with the Spy you first have to decide how you will integrate it with your project. Spy supports following tools:

Cocoapods

To install Spy using Cocoapods go through following steps:

  1. Add the following entry in your Podfile:
pod 'Spy'
  1. Then run pod install.

Carthage

To install Spy using Carthage go through following steps:

  1. Add the following entry to your Cartfile
github "appunite/Spy"
  1. Then run carthage update

Swift Package Manager

To install Spy using Swift Package Manager go through following steps:

  1. Add following package dependency in you Package.swift .package(url: "https://github.com/appunite/Spy.git", from: "0.5.0")
  2. Add following target dependency in your Package.swift dependencies: ["Spy"])

For instance this is how it might look like:

import PackageDescription

let package = Package(
    name: "YourLibrary",
    products: [
        .library(
            name: "YourLibrary",
            targets: ["YourLibrary"])
    ],
    dependencies: [
        .package(url: "https://github.com/appunite/Spy.git", from: "0.5.0")
    ],
    targets: [
        .target(
            name: "YourLibrary",
            dependencies: ["Spy"])
    ]
)

Overview

Here is a quick overview of functionalities and concepts used in Spy.

SpyChannel

SpyChannel is anything that implements PSpyChannel protocol. Channels can be used to categorize logs. Typically they are implemented with an enum. You can define your own channels as follows:

public enum SpyChannel: String, PSpyChannel {
    case foo
    case bar
    
    public var channelName: String {
        return self.rawValue
    }
}

SpyLevel

SpyLevel is anything that implements PSpyLevel protocol. You can define your own levels, but Spy commes with one set defined for you so you can use it if you want. This set is called SpyLevel and contains following alert levels: finest, finer, fine, config, info, warning, severe sorted by the increasing alert priority.

SpyConfiguration

Contains levels and channels that the Spy will spy on.

SpyConfigurationBuilder

Builds your spy configuration by providing add and remove functions for both levels and channels. Example usage:

SpyConfigurationBuilder()
    .add(level: .severe)
    .add(channels: [.foo, .bar])
    .build()

Spyable

Spyable is a entity that can be logged. It has to implement PSpyable protocol. You can define your own spyables or use string as a basic one.

Spied

Spied is a property wrapper that allows to log all changes and accesses to a property. Example usage:

class Foo {
    @Spied(spy: Environment.spy, onLevel: .info, onChannel: .foo) var foo = "foo"
}

Spy

Spy is anything that implements PSpy protocol. There are a few spies already defined for you:

Logging is performed with log method as follows:

spy.log(level: .severe, channel: .foo, message: "Something bad happened")

ConsoleSpy

ConsoleSpy comes with two available output formatters RawSpyFormatter and DecoratedSpyFormatter with the later being extendable with decorators. You can always define your own output formatter. Example output for RawSpyFormatter will look like:

info::foo::Hello Spy

And example output for DecoratedSpyFormatter may look like:

ℹ️ info::foo::Hello Spy
<p align="center"> <img src="resources/log.png" alt="Log example"/> </p>

Example

This is an example definition of the spies. It utilizes CompositeSpy to allow you to log onto multiple destinations (Console and File).

public static var spy: AnySpy<SpyLevel, SpyChannel> = {
    return CompositeSpy()
        .add(spy: ConsoleSpy<SpyLevel, SpyChannel, DecoratedSpyFormatter>(
            spyFormatter: DecoratedSpyFormatter(
                levelNameBuilder: DecoratedLevelNameBuilder<SpyLevel>()
                    .add(decorator: EmojiPrefixedSpyLevelNameDecorator().any())
                    ),
            timestampProvider: CurrentTimestampProvider(),
            configuration: SpyConfigurationBuilder()
                .add(levels: SpyLevel.levelsFrom(loggingLevel))
                .add(channel: .foo)
            .build()).any())
        .add(spy: FileSpy<SpyLevel, SpyChannel, DecoratedSpyFormatter>(
            logFile: LogFile(
                type: .chunked(maxLogsPerFile: 3),
                directoryURL: logDirectoryURL),
            spyFormatter: DecoratedSpyFormatter(
                levelNameBuilder: DecoratedLevelNameBuilder<SpyLevel>()
                    .add(decorator: EmojiPrefixedSpyLevelNameDecorator().any())
            ),
            timestampProvider: CurrentTimestampProvider(),
            configuration: SpyConfigurationBuilder()
                .add(level: .severe)
                .add(channels: [.foo, .bar])
                .build()).safe().any()
        ).any()
}()

By using preprocessor we can define different logging levels for debug and release. That way we won't forget about switching off unimportant logs before release.

public extension Environment {
	static var loggingLevel: SpyLevel {
        #if DEBUG
        return .info
        #else
        return .warning
        #endif
    }
}

And here is how you could use Spy:

Environment.spy.log(level: .info, channel: .foo, message: "Hello Spy")

For more detailed example please see the source code.

Contribution

Project is created and maintained by Tomasz Lewandowski.

If you created some new feature or fixed a bug you can create a pull request. Please feel free to submit your feature requests if you have any.

License

Spy is released under an MIT license. See License.md for more information.