Skip to content
This repository has been archived by the owner on Aug 12, 2022. It is now read-only.

Commit

Permalink
added error logging
Browse files Browse the repository at this point in the history
  • Loading branch information
nilsvu committed Nov 16, 2015
1 parent f936e08 commit 8f6a9fd
Show file tree
Hide file tree
Showing 8 changed files with 94 additions and 44 deletions.
16 changes: 10 additions & 6 deletions Evergreen.playground/Contents.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,21 @@ log("Hello World!") // Look at the console output!
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("Critical", forLevel: .Critical)
Evergreen.log("Debug", forLevel: .Debug)
Evergreen.log("Info", forLevel: .Info)
Evergreen.log("Warning", forLevel: .Warning)
Evergreen.log("Critical", forLevel: .Critical)

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

// Each log level has a corresponding log function alias for convenience
debug("Debug")
Evergreen.debug("Debug")

// Easily log errors
let error = NSError(domain: "error_domain", code: 0, userInfo: [ NSLocalizedDescriptionKey: "This was unexpected."])
Evergreen.critical(error)
Evergreen.debug("Some nasty error occured!", error: error)

// Use the logger hierarchy to adjust the logging configuration for specific parts of your software
let fooLogger = Evergreen.getLogger("MyModule.Foo")
Expand Down
2 changes: 1 addition & 1 deletion Evergreen.playground/contents.xcplayground
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<playground version='5.0' target-platform='ios' requires-full-environment='true' last-migration='0700'>
<playground version='5.0' target-platform='ios' last-migration='0700'>
<timeline fileName='timeline.xctimeline'/>
</playground>
4 changes: 2 additions & 2 deletions Evergreen.podspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = "Evergreen"
s.version = "0.6.0"
s.version = "0.7.0"
s.summary = "A Swift Logging Framework."
s.description = <<-DESC
Evergreen is a logging framework written in Swift.
Expand All @@ -11,6 +11,6 @@ Pod::Spec.new do |s|
s.license = { :type => "MIT", :file => "LICENSE.md" }
s.author = { "Nils Fischer" => "n.fischer@viwid.com" }
s.platform = :ios, "8.0"
s.source = { :git => "https://github.com/viWiD/Evergreen.git", :tag => "v0.6.0" }
s.source = { :git => "https://github.com/viWiD/Evergreen.git", :tag => "v0.7.0" }
s.source_files = "Evergreen"
end
3 changes: 3 additions & 0 deletions Evergreen.xcworkspace/contents.xcworkspacedata

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 17 additions & 1 deletion Evergreen/Formatter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,12 @@ public class Formatter {
case .LogLevel:
return (event.logLevel?.description ?? "Unspecified").uppercaseString
case .Message:
return String(stringInterpolationSegment: event.message())
switch event.message() {
case let error as NSError:
return error.localizedDescription
case let message:
return String(message)
}
case .Function:
return event.function
case .File:
Expand All @@ -74,6 +79,17 @@ public class Formatter {
if let elapsedTime = event.elapsedTime {
string += " [ELAPSED TIME: \(elapsedTime)s]"
}

if let error = event.error {
let errorMessage: String
switch error {
case let error as NSError:
errorMessage = error.localizedDescription
default:
errorMessage = String(error)
}
string += " [ERROR: \(errorMessage)]"
}

return string
}
Expand Down
62 changes: 32 additions & 30 deletions Evergreen/Logger.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,34 +40,34 @@ public func getLoggerForFile(file: String = __FILE__) -> Logger {
}

/// Logs the event using a logger that is appropriate for the caller. See `Logger.log:forLevel:` for further documentation.
public func log<M>(@autoclosure(escaping) message: () -> M, forLevel logLevel: LogLevel? = nil, function: String = __FUNCTION__, file: String = __FILE__, line: Int = __LINE__)
public func log<M>(@autoclosure(escaping) message: () -> M, error: ErrorType? = nil, forLevel logLevel: LogLevel? = nil, function: String = __FUNCTION__, file: String = __FILE__, line: Int = __LINE__)
{
Logger.loggerForFile(file).log(message, forLevel: logLevel, function: function, file: file, line: line)
Logger.loggerForFile(file).log(message, error: error, forLevel: logLevel, function: function, file: file, line: line)
}

/// Logs the event with the Verbose log level using a logger that is appropriate for the caller. See `Logger.log:forLevel:` for further documentation.
public func verbose<M>(@autoclosure(escaping) message: () -> M, function: String = __FUNCTION__, file: String = __FILE__, line: Int = __LINE__) {
Evergreen.log(message, forLevel: .Verbose, function: function, file: file, line: line)
public func verbose<M>(@autoclosure(escaping) message: () -> M, error: ErrorType? = nil, function: String = __FUNCTION__, file: String = __FILE__, line: Int = __LINE__) {
Evergreen.log(message, error: error, forLevel: .Verbose, function: function, file: file, line: line)
}
/// Logs the event with the Debug log level using a logger that is appropriate for the caller. See `Logger.log:forLevel:` for further documentation.
public func debug<M>(@autoclosure(escaping) message: () -> M, function: String = __FUNCTION__, file: String = __FILE__, line: Int = __LINE__) {
Evergreen.log(message, forLevel: .Debug, function: function, file: file, line: line)
public func debug<M>(@autoclosure(escaping) message: () -> M, error: ErrorType? = nil, function: String = __FUNCTION__, file: String = __FILE__, line: Int = __LINE__) {
Evergreen.log(message, error: error, forLevel: .Debug, function: function, file: file, line: line)
}
/// Logs the event with the Info log level using a logger that is appropriate for the caller. See `Logger.log:forLevel:` for further documentation.
public func info<M>(@autoclosure(escaping) message: () -> M, function: String = __FUNCTION__, file: String = __FILE__, line: Int = __LINE__) {
Evergreen.log(message, forLevel: .Info, function: function, file: file, line: line)
public func info<M>(@autoclosure(escaping) message: () -> M, error: ErrorType? = nil, function: String = __FUNCTION__, file: String = __FILE__, line: Int = __LINE__) {
Evergreen.log(message, error: error, forLevel: .Info, function: function, file: file, line: line)
}
/// Logs the event with the Warning log level using a logger that is appropriate for the caller. See `Logger.log:forLevel:` for further documentation.
public func warning<M>(@autoclosure(escaping) message: () -> M, function: String = __FUNCTION__, file: String = __FILE__, line: Int = __LINE__) {
Evergreen.log(message, forLevel: .Warning, function: function, file: file, line: line)
public func warning<M>(@autoclosure(escaping) message: () -> M, error: ErrorType? = nil, function: String = __FUNCTION__, file: String = __FILE__, line: Int = __LINE__) {
Evergreen.log(message, error: error, forLevel: .Warning, function: function, file: file, line: line)
}
/// Logs the event with the Error log level using a logger that is appropriate for the caller. See `Logger.log:forLevel:` for further documentation.
public func error<M>(@autoclosure(escaping) message: () -> M, function: String = __FUNCTION__, file: String = __FILE__, line: Int = __LINE__) {
Evergreen.log(message, forLevel: .Error, function: function, file: file, line: line)
public func error<M>(@autoclosure(escaping) message: () -> M, error: ErrorType? = nil, function: String = __FUNCTION__, file: String = __FILE__, line: Int = __LINE__) {
Evergreen.log(message, error: error, forLevel: .Error, function: function, file: file, line: line)
}
/// Logs the event with the Critical log level using a logger that is appropriate for the caller. See `Logger.log:forLevel:` for further documentation.
public func critical<M>(@autoclosure(escaping) message: () -> M, function: String = __FUNCTION__, file: String = __FILE__, line: Int = __LINE__) {
Evergreen.log(message, forLevel: .Critical, function: function, file: file, line: line)
public func critical<M>(@autoclosure(escaping) message: () -> M, error: ErrorType? = nil, function: String = __FUNCTION__, file: String = __FILE__, line: Int = __LINE__) {
Evergreen.log(message, error: error, forLevel: .Critical, function: function, file: file, line: line)
}

/// Reads the logging configuration from environment variables. Every environment variable with prefix 'Evergreen' is evaluated as a logger key path and assigned a log level corresponding to its value. Values should match the log level descriptions, e.g. 'Debug'. Valid environment variable declarations would be e.g. 'Evergreen = Debug' or 'Evergreen.MyLogger = Verbose'.
Expand Down Expand Up @@ -168,7 +168,7 @@ public final class Logger {
private func logInitialInfo() {
if !hasLoggedInitialInfo {
if handlers.count > 0 {
let event = Event(logger: self, message: { "Logging to \(self.handlers)..." }, logLevel: .Info, date: NSDate(), elapsedTime: nil, function: __FUNCTION__, file: __FILE__, line: __LINE__)
let event = Event(logger: self, message: { "Logging to \(self.handlers)..." }, error: nil, logLevel: .Info, date: NSDate(), elapsedTime: nil, function: __FUNCTION__, file: __FILE__, line: __LINE__)
self.handleEvent(event)
}
hasLoggedInitialInfo = true
Expand All @@ -189,35 +189,35 @@ public final class Logger {
- parameter message: The message to be logged, provided by an autoclosure. The closure will not be evaluated if the event is not going to be emitted, so it can contain expensive operations only needed for logging purposes.
- parameter logLevel: If the event's log level is lower than the receiving logger's `effectiveLogLevel`, the event will not be logged. The event will always be logged, if no log level is provided for either the event or the logger's `effectiveLogLevel`.
*/
public func log<M>(@autoclosure(escaping) message: () -> M, forLevel logLevel: LogLevel? = nil, function: String = __FUNCTION__, file: String = __FILE__, line: Int = __LINE__)
public func log<M>(@autoclosure(escaping) message: () -> M, error: ErrorType? = nil, forLevel logLevel: LogLevel? = nil, function: String = __FUNCTION__, file: String = __FILE__, line: Int = __LINE__)
{
let event = Event(logger: self, message: message, logLevel: logLevel, date: NSDate(), elapsedTime: nil, function: function, file: file, line: line)
let event = Event(logger: self, message: message, error: error, logLevel: logLevel, date: NSDate(), elapsedTime: nil, function: function, file: file, line: line)
self.logEvent(event)
}

/// Logs the event with the Verbose log level. See `log:forLevel:` for further documentation.
public func verbose<M>(@autoclosure(escaping) message: () -> M, function: String = __FUNCTION__, file: String = __FILE__, line: Int = __LINE__) {
self.log(message, forLevel: .Verbose, function: function, file: file, line: line)
public func verbose<M>(@autoclosure(escaping) message: () -> M, error: ErrorType? = nil, function: String = __FUNCTION__, file: String = __FILE__, line: Int = __LINE__) {
self.log(message, error: error, forLevel: .Verbose, function: function, file: file, line: line)
}
/// Logs the event with the Debug log level. See `log:forLevel:` for further documentation.
public func debug<M>(@autoclosure(escaping) message: () -> M, function: String = __FUNCTION__, file: String = __FILE__, line: Int = __LINE__) {
self.log(message, forLevel: .Debug, function: function, file: file, line: line)
public func debug<M>(@autoclosure(escaping) message: () -> M, error: ErrorType? = nil, function: String = __FUNCTION__, file: String = __FILE__, line: Int = __LINE__) {
self.log(message, error: error, forLevel: .Debug, function: function, file: file, line: line)
}
/// Logs the event with the Info log level. See `log:forLevel:` for further documentation.
public func info<M>(@autoclosure(escaping) message: () -> M, function: String = __FUNCTION__, file: String = __FILE__, line: Int = __LINE__) {
self.log(message, forLevel: .Info, function: function, file: file, line: line)
public func info<M>(@autoclosure(escaping) message: () -> M, error: ErrorType? = nil, function: String = __FUNCTION__, file: String = __FILE__, line: Int = __LINE__) {
self.log(message, error: error, forLevel: .Info, function: function, file: file, line: line)
}
/// Logs the event with the Warning log level. See `log:forLevel:` for further documentation.
public func warning<M>(@autoclosure(escaping) message: () -> M, function: String = __FUNCTION__, file: String = __FILE__, line: Int = __LINE__) {
self.log(message, forLevel: .Warning, function: function, file: file, line: line)
public func warning<M>(@autoclosure(escaping) message: () -> M, error: ErrorType? = nil, function: String = __FUNCTION__, file: String = __FILE__, line: Int = __LINE__) {
self.log(message, error: error, forLevel: .Warning, function: function, file: file, line: line)
}
/// Logs the event with the Error log level. See `log:forLevel:` for further documentation.
public func error<M>(@autoclosure(escaping) message: () -> M, function: String = __FUNCTION__, file: String = __FILE__, line: Int = __LINE__) {
self.log(message, forLevel: .Error, function: function, file: file, line: line)
public func error<M>(@autoclosure(escaping) message: () -> M, error: ErrorType? = nil, function: String = __FUNCTION__, file: String = __FILE__, line: Int = __LINE__) {
self.log(message, error: error, forLevel: .Error, function: function, file: file, line: line)
}
/// Logs the event with the Critical log level. See `log:forLevel:` for further documentation.
public func critical<M>(@autoclosure(escaping) message: () -> M, function: String = __FUNCTION__, file: String = __FILE__, line: Int = __LINE__) {
self.log(message, forLevel: .Critical, function: function, file: file, line: line)
public func critical<M>(@autoclosure(escaping) message: () -> M, error: ErrorType? = nil, function: String = __FUNCTION__, file: String = __FILE__, line: Int = __LINE__) {
self.log(message, error: error, forLevel: .Critical, function: function, file: file, line: line)
}

/// Logs the given event. Use `log:forLevel:` for convenience.
Expand Down Expand Up @@ -285,7 +285,7 @@ public final class Logger {
}
if let startDate = startDate {
let elapsedTime = NSDate().timeIntervalSinceDate(startDate)
let event = Event(logger: self, message: message, logLevel: logLevel, date: NSDate(), elapsedTime: elapsedTime, function: function, file: file, line: line)
let event = Event(logger: self, message: message, error: nil, logLevel: logLevel, date: NSDate(), elapsedTime: elapsedTime, function: function, file: file, line: line)
self.logEvent(event)
}
}
Expand Down Expand Up @@ -470,6 +470,8 @@ public struct Event<M> {
let logger: Logger
/// The log message
let message: () -> M
/// An error that occured alongside the event
let error: ErrorType?
/// The log level. A logger will only log events with equal or higher log levels than its own. Events that don't specify a log level will always be logged.
let logLevel: LogLevel?
let date: NSDate
Expand Down
5 changes: 5 additions & 0 deletions EvergreenTests/EvergreenTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,9 @@ class EvergreenTests: XCTestCase {
XCTAssert(logLevels.sort(<) == logLevels, "Log levels initialized by sequencial raw values are not ordered by comparison operator.")
}

func testErrorTypeLogging() {
let error = NSError(domain: "error_domain", code: 0, userInfo: nil)
Evergreen.verbose("Something failed", error: error)
}

}
28 changes: 24 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,9 +131,18 @@ log("Critical", forLevel: .Critical)
log("Verbose", forLevel: .Verbose)
```

Every log level has a corresponding log function for convenience:

```swift
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 `log` function. 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.
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.
Expand All @@ -154,7 +163,7 @@ class MyType {
let logger = Evergreen.getLogger("MyModule.MyType")

init() {
self.logger.log("Initializing...", forLevel: .Debug)
self.logger.debug("Initializing...")
}

}
Expand All @@ -168,17 +177,28 @@ let logger = Evergreen.getLogger("MyModule") // Retrieve the logger with key 'My
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 is in the `AppDelegate`'s `application:didFinishLaunchingWithOptions:` method.
> **Note:** A good place to do this configuration 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 conveniently configure the logger hierarchy is using environment variables. In Xcode, choose your target from the dropdown in the toolbar, select `Edit Scheme...` `>` `Run` `>` `Arguments` and add environment variables to the list.

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`. These are some examples for valid environment variable declarations:
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 `ErrorType` errors alongside your events

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

```swift
let error: ErrorType // some error
debug("Something unexpected happened here!", error: error)
```


### Measuring Time

Easily measure the time between two events:
Expand Down

0 comments on commit 8f6a9fd

Please sign in to comment.