Skip to content

A small Swift Package that makes logging in the Xcode Console a little bit more beautiful

License

Notifications You must be signed in to change notification settings

bennokress/PrettyLog

Repository files navigation

Welcome to PrettyLog πŸ‘‹

Swift Package Index License

PrettyLog is a small Swift Package that makes logging in the Xcode Console a little bit more beautiful.

Install

dependencies: [
    .package(url: "https://github.com/bennokress/PrettyLog", .upToNextMajor(from: "1.0.0"))
]

Basic Usage

With PrettyLog you can make your print statements prettier and more useful. Just replace this ...

print("User tapped Continue Button")

Console Output with default print statement

... with this:

logD("User tapped Continue Button")

Console Output with PrettyLog statement

So far so good. You have a lot more options with PrettyLog though ...

Advanced Usage

Log Levels

logV("A verbose log")
logD("A debug log")
logI("An info log")
logW("A warning log")
logE("An error log")

Console Output of multiple PrettyLog statements with different Log Levels

Log Categories

logV("Got an API Response ...", category: .service)
logI("Saving Token from API: \(token)", category: .storage)
logV("User tapped Continue Button", category: .user)
logE("Username and Password did not match", category: .manager)
logW("Or create your own Category ...", category: .custom("Announcement"))

Console Output of multiple PrettyLog statements with different Log Categories

Log Message Concatenation

logV(service, "Got an API Response ...", category: .service)
logW(screen, element, "Username too long", joinedBy: " β†’ ", category: .manager)

Console Output of multiple PrettyLog statements with different Log Categories

Errors and Exceptions

let error = NetworkError.notFound
let exception = NSException(name: .portTimeoutException, reason: nil)
log(error, category: .service)
log(exception, category: .service)

Console Output of PrettyLog statements that take in Error and NSException as arguments

Expert Usage

Custom Log Categories

Included in PrettyLog are the categories that I use routinely. Those may differ from what is useful in your project, so I made it easy for you to define your own categories. Simply extend LogCategory like this:

extension LogCategory {

    /// This custom category can be used like all the predefined ones: logV("Running Unit Tests ...", category: .todo)
    static var todo: LogCategory { .custom("To Do") }

}

Custom Log Levels

PrettyLog contains a few default Log Levels, but that doesn't mean that you are limited to them. To define your own level extend LogLevel like this:

extension LogLevel {

    /// This custom level can be used like all the predefined ones, it has to be called with the universal `log` method though: `log("The login method is not yet implemented", category: .todo, as: .todo)
    static var todo: LogLevel { .custom(emoji: "🟣", priority: 200) }

}

If you want to use your custom Log Level in line with all the predefined ones, you might want to define a global method like logT for it somewhere in your code:

/// Log messages in the provided order with TODO level
/// - Parameters:
///     - messages: One or more strings and string-convertible objects to include in the log statement
///     - separator: The separator between messages (defaults to `-`)
/// - Attention: No log will be created, if `messages` is empty or `nil`.
public func logT(_ messages: String?..., joinedBy separator: String = " - ") {
    PrettyLogProxy.log(messages, joinedBy: separator, as: .todo, category: .todo)
}

See the section 'Import once' in the Integration part of this README for a well suited place to define this.

Custom Log Targets

PrettyLog makes it easy to send your log messages to different destinations with Log Targets. Predefined in the package is ConsoleLog which sends statements of all Log Levels to the Xcode console. In real world apps you might want to log to Backends and Web Services or locally using different local solutions than the plain old print. That's where the LogTarget protocol comes in. It only requires you to specify how to handle a log statement consisting of a LogLevel, a message (String) and a LogCategory. Additionally you can specify a logPriorityRange that filters out incoming log statements that don't meet the defined range requirements before sending it to your custom defined handler.

An example would be a custom Console Log Target that only logs if the app is running in specific environments:

import Foundation
import PrettyLog

struct Console: LogTarget {

    /// Create the log statement with a consistent design.
    /// - Parameters:
    ///   - level: The log level is responsible for the emoji displayed in the log statement.
    ///   - message: The message is printed to the right of the log level emoji.
    ///   - category: The category is printed to the left of the log level emoji.
    func createLog(_ level: LogLevel, message: String, category: LogCategory) {
        print("\(prefix(level: level, category: category)) \(message)")
    }

    var logPriorityRange: ClosedRange<LogLevel>? {
        App.shared.isDeveloperVersion ? .allowAll : .allowNone
    }

    // MARK: Private Helpers

    private var currentTimestamp: String {
        let formatter = DateFormatter()
        formatter.dateFormat = "HH:mm:ss.SSS"
        return formatter.string(from: Date())
    }

    private func prefix(level: LogLevel, category: LogCategory) -> String {
        "\(currentTimestamp) \(category.truncatedOrPadded(to: 20)) \(level.emoji)"
    }

}

Integration

When using PrettyLog you have basically two options:

Import everywhere

Since you probably log throughout your app, importing PrettyLog in every single file might become cumbersome, but it's an option.

import Foundation
import PrettyLog

struct MyModel {
    func doStuff() {
        logV("Doing some stuff ...")
    }
}

Import once

Another option is to create a Swift file somewhere in your app that serves as a proxy to PrettyLog. This enables you to import PrettyLog and define the log methods globally once.

//
// πŸ“„ PrettyLog.swift
// πŸ‘¨πŸΌβ€πŸ’» Author: Benno Kress
//

import Foundation
import PrettyLog

/// Log messages in the provided order with VERBOSE level
/// - Parameters:
///     - messages: One or more strings and string-convertible objects to include in the log statement
///     - separator: The separator between messages (defaults to `-`)
///     - category: The category of the log message (defaults to `.uncategorized`)
/// - Attention: No log will be created, if `messages` is empty or `nil`.
func logV(_ messages: String?..., joinedBy separator: String = " - ", category: LogCategory = .uncategorized) {
    PrettyLogProxy.logV(messages, joinedBy: separator, category: category)
}

/// Log messages in the provided order with DEBUG level
/// - Parameters:
///     - messages: One or more strings and string-convertible objects to include in the log statement
///     - separator: The separator between messages (defaults to `-`)
///     - category: The category of the log message (defaults to `.uncategorized`)
/// - Attention: No log will be created, if `messages` is empty or `nil`.
func logD(_ messages: String?..., joinedBy separator: String = " - ", category: LogCategory = .uncategorized) {
    PrettyLogProxy.logD(messages, joinedBy: separator, category: category)
}

/// Log messages in the provided order with INFO level
/// - Parameters:
///     - messages: One or more strings and string-convertible objects to include in the log statement
///     - separator: The separator between messages (defaults to `-`)
///     - category: The category of the log message (defaults to `.uncategorized`)
/// - Attention: No log will be created, if `messages` is empty or `nil`.
func logI(_ messages: String?..., joinedBy separator: String = " - ", category: LogCategory = .uncategorized) {
    PrettyLogProxy.logI(messages, joinedBy: separator, category: category)
}

/// Log messages in the provided order with WARNING level
/// - Parameters:
///     - messages: One or more strings and string-convertible objects to include in the log statement
///     - separator: The separator between messages (defaults to `-`)
///     - category: The category of the log message (defaults to `.uncategorized`)
/// - Attention: No log will be created, if `messages` is empty or `nil`.
func logW(_ messages: String?..., joinedBy separator: String = " - ", category: LogCategory = .uncategorized) {
    PrettyLogProxy.logW(messages, joinedBy: separator, category: category)
}

/// Log messages in the provided order with ERROR level
/// - Parameters:
///     - messages: One or more strings and string-convertible objects to include in the log statement
///     - separator: The separator between messages (defaults to `-`)
///     - category: The category of the log message (defaults to `.uncategorized`)
/// - Attention: No log will be created, if `messages` is empty or `nil`.
func logE(_ messages: String?..., joinedBy separator: String = " - ", category: LogCategory = .uncategorized) {
    PrettyLogProxy.logE(messages, joinedBy: separator, category: category)
}

/// Log an `Error` with ERROR level.
/// - Parameters:
///     - error: The error to log
///     - category: The category of the log message (defaults to `.uncategorized`)
/// - Attention: No log will be created, if `error` is `nil`.
func log(_ error: Error?, category: LogCategory = .uncategorized) {
    PrettyLogProxy.log(error, category: category)
}

/// Log a `NSException` with ERROR level.
/// - Parameters:
///     - exception: The exception to log
///     - category: The category of the log message (defaults to `.uncategorized`)
/// - Attention: No log will be created, if `exception` is `nil`.
func log(_ exception: NSException?, category: LogCategory = .uncategorized) {
    PrettyLogProxy.log(exception, category: category)
}

/// Log with ERROR level and crash the app.
/// - Parameters:
///     - messages: One or more strings and string-convertible objects to include in the log statement
///     - separator: The separator between messages (defaults to `-`)
///     - category: The category of the log message
/// - Attention: No log will be created, if `messages` is empty or `nil`.
func fatalLog(_ messages: String?..., joinedBy separator: String = " - ", category: LogCategory = .uncategorized) -> Never {
    PrettyLogProxy.logE(messages, joinedBy: separator, category: category)
    let messageComponents = messages.compactMap { $0 }
    let statement = messageComponents.joined(separator: separator)
    fatalError(statement)
}

// TODO: Add custom global log methods if needed -> for example: if you have custom LogLevel and LogCategory `.todo`, you could define `logT` for that.

// /// Log messages in the provided order with TODO level
// /// - Parameters:
// ///     - messages: One or more strings and string-convertible objects to include in the log statement
// ///     - separator: The separator between messages (defaults to `-`)
// /// - Attention: No log will be created, if `messages` is empty or `nil`.
// public func logT(_ messages: String?..., joinedBy separator: String = " - ") {
//     PrettyLogProxy.log(messages, joinedBy: separator, as: .todo, category: .todo)
// }

Author

πŸ‘¨πŸ»β€πŸ’» Benno Kress

🀝 Contributing

Contributions, issues and feature requests are welcome!
Feel free to check issues page.

πŸ“ License

Copyright Β© 2022 Benno Kress.
This project is MIT licensed.


This README was generated with ❀️ by readme-md-generator

About

A small Swift Package that makes logging in the Xcode Console a little bit more beautiful

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages