SweetLogger

A beautiful and sweet logger for Swift and SwiftUI. ?

Install

https://github.com/zhuanghongji/SweetLogger
  • Copy the URL of the repository provided above.
  • Paste it into the text field located at the top-right corner of the window by navigating to “File” -> “Add Package Dependencies…”.
  • Click on “Add Package”.

Init

You can enable Sweet Logger anytime and anywhere you want, for example:

@main
struct ExampleApp: App {

    init() {
#if DEBUG
        initLogger()
#endif
    }

    func initLogger() {
        Logger.options.enabled = true

        // Logger.options.brand = "Bee"
        
        // Logger.options.dateFormat = "yyyy-MM-dd HH:mm:ss.SSSS"
        // Logger.options.separator = "|"
        // Logger.options.terminator = "\n--------------------\n\n"

        // Logger.options.useLevelSymbol = false
        // Logger.options.useLevelDescription = false
        // Logger.options.useDebugPrint = false

        // SweetLogger.shared.tag = "Honey"
    }

    // ...
}

Note: Please ensure that Logger is enabled during debug, as it is disabled by default.

Options

Option Description Type Default
brand The brand text that using in each output. String “Sweet”
dateFormat The date format in output. String “HH:mm:ss.SSSS”
separator The flag is used before the message to separate it from contextual information. String “:”
terminator The global terminator for dividing each block of Logger output. String “\n”
maxDataNestingLevels The maximum number of data nesting levels. Int 5
useLevelSymbol When enabled, the output will be prefixed with an emojj that corresponds to the level. Bool true
useLevelDescription When enabled, the output will be prefixed with a brief description that corresponds to the level. Bool true
useDebugPrint The print function switcher. Bool true

Note: Please refer to the SweetLoggerOptions.swift source file for detailed information about the options. Additionally, you can find a comparison between print(_:separator:terminator:) and debugPrint(_:separator:terminator:) in WHATISPRINT.md.

Basic usage

Log by level

✍️ :

Logger.v("Verbose mssage")
Logger.i("Info message")
Logger.d("Debug message")
Logger.w("Warning message")
Logger.e("Error message")

✏️ :

⚪️ [V] 08:40:41.9400 Sweet Default : Verbose mssage

? [I] 08:40:41.9410 Sweet Default : Info message

? [D] 08:40:41.9410 Sweet Default : Debug message

? [W] 08:40:41.9410 Sweet Default : Warning message

? [E] 08:40:41.9410 Sweet Default : Error message

Log with items

✍️ :

Logger.v("With items", 1, 2, 3)
Logger.v("With items", "a", "b", "c")
Logger.v("With items(array)", [1, 2, 3])
Logger.v("With items(dictionary)", ["a": 1, "b": 2, "c": 3])
Logger.v("With items(empty string)", "")

let url = URL(string: "https://developer.apple.com")
Logger.v("The url is", url ?? "__nil__")

let point = CGPoint(x: 1, y: 2)
Logger.v("The point is", point)

let rect = CGRect(x: 1, y: 2, width: 3, height: 4)
Logger.v("The rect is", rect)

✏️ :

⚪️ [V] 08:40:41.9410 Sweet Default : With items
1 2 3

⚪️ [V] 08:40:41.9420 Sweet Default : With items
"a" "b" "c"

⚪️ [V] 08:40:41.9420 Sweet Default : With items(array)
[1, 2, 3]

⚪️ [V] 08:40:41.9420 Sweet Default : With items(dictionary)
["a": 1, "b": 2, "c": 3]

⚪️ [V] 08:40:41.9420 Sweet Default : With items(empty string)
""

⚪️ [V] 08:40:41.9420 Sweet Default : The url is
https://developer.apple.com

⚪️ [V] 08:40:41.9420 Sweet Default : The point is
(1.0, 2.0)

⚪️ [V] 08:40:41.9420 Sweet Default : The rect is
(1.0, 2.0, 3.0, 4.0)

Log with custom tag

✍️ :

Logger.t("MyTag").v("Custom tag")
Logger.t("AnotherTag").v("Another tag with items", true, false, separator: ", ")

✏️ :

⚪️ [V] 08:40:41.9420 Sweet MyTag : Custom tag

⚪️ [V] 08:40:41.9420 Sweet AnotherTag : Another tag with items
true, false

Advance usage

Log with data

✍️ :

class MyClass {
    var p1 = 1
    var p2 = "Two"
    var p3 = true
    var p4 = false
    var pEmptyString = ""
}

extension MyClass: CustomStringConvertible {
    var description: String {
        "MyClass(p1: \(p1), p2: \(p2), p3: \(p3), p3: \(p4), pEmptyString: \(pEmptyString))"
    }
}

extension MyClass: SweetLoggerDataProvider {
    func provideSweetLoggerData(data: SweetLoggerData) {
        data.type("MyClass")
            .with("p1", p1)
            .with("p2", p2)
            .with("p3", p3)
            .with("p4", p4)
            .with("pEmptyString", pEmptyString)
            .end()
    }
}

let myClass = MyClass()
Logger.v("When myClass with item", myClass)
Logger.v("When myClass with data", data: myClass)

✏️ :

⚪️ [V] 08:40:41.9430 Sweet Default : When myClass with item
MyClass(p1: 1, p2: Two, p3: true, p3: false, pEmptyString: )

⚪️ [V] 08:40:41.9430 Sweet Default : When myClass with data
MyClass {
    p1: 1
    p2: "Two"
    p3: true
    p4: false
    pEmptyString: ""
}

Log with optional data

✍️ :

var myClass: MyClass? = MyClass()
Logger.v("When myClass with optional", optional: myClass)

myClass = nil
Logger.v("When myClass is exactly nil", optional: myClass)

✏️ :

⚪️ [V] 08:40:41.9430 Sweet Default : When myClass with optional
MyClass {
    p1: 1
    p2: "Two"
    p3: true
    p4: false
    pEmptyString: ""
}

⚪️ [V] 08:40:41.9430 Sweet Default : When myClass is exactly nil
nil

Log with preseted data

✍️ :

let url = URL(string: "https://developer.apple.com/abc?v1=1")!
Logger.v("The url with data", data: url)

let point = CGPoint(x: 1, y: 2)
Logger.v("The point with data", data: point)

let rect = CGRect(x: 1, y: 2, width: 3, height: 4)
Logger.v("The rect with data", data: rect)

✏️ :

⚪️ [V] 08:40:41.9430 Sweet Default : The url with data
URL {
    absoluteString: "https://developer.apple.com/abc?v1=1"
    fragment: nil
    host: Optional("developer.apple.com")
    lastPathComponent: "abc"
    pathExtension: ""
    port: nil
    query: Optional("v1=1")
    scheme: Optional("https")
}

⚪️ [V] 08:40:41.9430 Sweet Default : The point with data
CGPoint {
    x: 1.0
    y: 2.0
}

⚪️ [V] 08:40:41.9440 Sweet Default : The rect with data
CGRect {
    x: 1.0
    y: 2.0
    width: 3.0
    height: 4.0
    minX: 1.0
    minY: 2.0
    midX: 2.5
    midY: 4.0
    maxX: 4.0
    maxY: 6.0
}

Using logger modifiers

loggerAppearance(name:)

A wrapper of onAppear(perform:) and onDisappear(perform:), for example:

struct TestLoggerModifiersView1: View {
    @State private var visible = true

    var body: some View {
        VStack {
            if visible {
                Image(systemName: "globe")
                    .loggerAppearance(name: "GlobeImage")
            }
            Button {
                visible.toggle()
            } label: {
                Text("Toggle Visibility")
            }
        }
        .padding()
    }
}

When the view appears, prints:

⚪️ [V] 08:40:41.9440 Sweet Default : loggerAppearance
onAppear(name: "GlobeImage")

After a tap on “Toggle Visibility” button, it will print:

⚪️ [V] 08:43:29.1100 Sweet Default : loggerAppearance
onDisappear(name: "GlobeImage")

loggerChange(of:initial:name:)

A wrapper of onChange(of:initial:_:), for example:

struct TestLoggerModifiersView2: View {
    @State private var count = 0

    var countDescription: String {
        "\(count)"
    }

    var body: some View {
        VStack {
            Text(countDescription)
            Button {
                count += 1
            } label: {
                withAnimation {
                    Text("Add Count")
                }
            }
        }
        .padding()
        .loggerChange(of: countDescription, initial: true, name: "countDescription")
        .loggerChange(of: count, initial: true, name: "count")
        .loggerChange(of: count)
    }
}

When this view initially appears, it will print:

⚪️ [V] 08:40:41.9440 Sweet Default : loggerChange
onChange(name: "countDescription", oldValue: "0", newValue: "0")

⚪️ [V] 08:40:41.9440 Sweet Default : loggerChange
onChange(name: "count", oldValue: 0, newValue: 0)

After a tap on “Add Count” button, it will print:

⚪️ [V] 08:43:30.0390 Sweet Default : loggerChange
onChange(oldValue: 0, newValue: 1)

⚪️ [V] 08:43:30.0400 Sweet Default : loggerChange
onChange(name: "count", oldValue: 0, newValue: 1)

⚪️ [V] 08:43:30.0400 Sweet Default : loggerChange
onChange(name: "countDescription", oldValue: "0", newValue: "1")

Using a wrapper instead of Logger

Sometimes we don’t want a third-party library to be tightly coupled with our own project. SweetLogger understands this and has been designed accordingly. It exposes the Logger as the main API, which can be wrapped within an enum or a class that you can use throughout your project.

You can find an example in WRAPPER.md.

License

SweetLogger is available under the MIT license. See the LICENSE file for more info.

GitHub

View Github