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.
Project uses following tools for development
To get started with the Spy you first have to decide how you will integrate it with your project. Spy supports following tools:
To install Spy using Cocoapods go through following steps:
- Add the following entry in your Podfile:
pod 'Spy'
- Then run
pod install
.
To install Spy using Carthage go through following steps:
- Add the following entry to your Cartfile
github "appunite/Spy"
- Then run
carthage update
To install Spy using Swift Package Manager go through following steps:
- Add following package dependency in you Package.swift
.package(url: "https://github.com/appunite/Spy.git", from: "0.5.0")
- 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"])
]
)
Here is a quick overview of functionalities and concepts used in Spy.
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 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.
Contains levels and channels that the Spy will spy on.
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 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 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 is anything that implements PSpy protocol. There are a few spies already defined for you:
- ConsoleSpy - spy that logs spyables by using print command
- FileSpy - spy that logs spyables into the filesystem (allows to create monolith or chunked logs)
- CompositeSpy - spy that groups multiple spies into one
- AnySpy - type-erased spy - every spy can be converted to AnySpy
Logging is performed with log method as follows:
spy.log(level: .severe, channel: .foo, message: "Something bad happened")
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
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.
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.
Spy is released under an MIT license. See License.md for more information.