# Logger

{% tabs %}
{% tab title="👔 Logger" %}
💾 程式： [replit](https://replit.com/@pegasusroe/Swift-Playground#Logs/Logger.swift)        ⬆️ 需要： [str.pad()](/ios/swift/type/category/basic/string/str.pad.md), [collection.columnWidths](/ios/swift/collections/collection/collection.columnwidths.md)

````swift
// ──────────────────────────────────────────────────────────────────
// 2022.01.27 * (v.1) + line, spaces, prefixDoubleSlash, log, underline, doubleline, debug
// 2022.01.28 / line()       replace "-" by "─" (longer dash), 
//            r line()       renmaed to "_line()"
//            / log()        `dot`: Bool -> String?
//            + line()       draw line 
//            + topline()    line on top of message
//            + error()      error message
// 2022.01.29 + codeBlock()  `///` prefixed code block
//            + table()      draw a table for 2D data
// 2022.01.30 / table()      + param options: TableOptions
//            / table()      + return value (Bool), + @discardableResult
// ──────────────────────────────────────────────────────────────────
// #todo: ■ □ ▨ 
// - table()     + ■ dot  □ vertical borders  □ vertical grid lines
// - codeBlock() + □ padding before code  □ comments after code

// import Extensions            // for String + .pad()

// ┌──────────────┐
// │    Logger    │
// └──────────────┘

public struct Logger {
    public static var prefixDoubleSlash = false
}

// helper
extension Logger {
    /// `Logger.line(3) == "-" * 3 == "---"` 
    static func _line  (_ n: Int) -> String { return "─" * n }
    /// `Logger.spaces(3) == " " * 3 == "   "`
    static func spaces(_ n: Int) -> String { return " " * n }
}

extension Logger {
    public struct TableOptions: OptionSet {
        
        public let rawValue: Int
        
        public init(rawValue: Int) {
            self.rawValue = rawValue
        }
        
        // ⭐️ unit sets (containing only one element)
        public static let dot    = TableOptions(rawValue: 1 << 0)
    }
}

extension Logger {
    
    /// `Logger.log(msg)`
    public static func log(
        _ message: String = "", 
        padding n: Int = 0,
        dot: String? = nil
    ) {
        let prefix = prefixDoubleSlash ? "// " : ""
        let padding = spaces(n)
        let dotted = (dot == nil) ? "" : "\(dot!) "
        print(prefix + padding + dotted + message)
    }
    
    /// `Logger.error(msg)`
    public static func error(
        _ message: String = "", 
        padding n: Int = 0
    ) {
        Logger.log(message, padding: n, dot: "⛔")
    }
    
    /// `Logger.line(n)`
    public static func line(_ n: Int, padding p: Int = 0) {
        log(Logger._line(n), padding: p)
    }
    
    /// `Logger.box(title)`
    public static func box(_ title: String, padding: Int = 4) {
        let w = title.count + padding * 2
        let line = "─" * w
        let spaces = " " * padding
        
        Logger.log("┌" + line + "┐")
        Logger.log("│" + spaces + title + spaces + "│")
        Logger.log("└" + line + "┘")
    }
    
    /// `Logger._drawline(msg)`
    static func _drawline(
        _ message : String, 
        length   n: Int? = nil,         // line length
        padding  p: Int  = 0,           // padding on both sides of message
        topline   : Bool = false,       // draw top line
        bottomline: Bool = true         // draw bottom line
    ) {
        let defaultLength = message.count + p * 2
        let line = Logger._line(n ?? defaultLength)
        let padding = spaces(p)
        
        if topline { log(line) }
        log(padding + message + padding)
        if bottomline { log(line) }
    }
    
    /// `Logger.underline(msg)`
    public static func underline(_ message: String, length n: Int? = nil, padding p: Int = 0) {
        _drawline(message, length: n, padding: p)
    }
    
    /// `Logger.doubleline(msg)`
    public static func doubleline(
        _ message: String, 
        length  n: Int? = nil,         // line length
        padding p: Int  = 4,           // padding on both sides of message
        newline  : Bool = true         // newline before message
    ) {
        if newline { log() }           // new line
        _drawline(message, length: n, padding: p, topline: true)
    }
    
    /// `Logger.topline(msg)`
    public static func topline(
        _ message: String, 
        length  n: Int? = nil,         // line length
        padding p: Int  = 0            // padding on both sides of message
    ) {
        _drawline(message, length: n, padding: p, topline: true, bottomline: false)
    }
    
    /// `Logger.debug(msg)`
    public static func debug(
        _ message: String  = "",
        file     : String  = #file,         // ⭐ file name
        line     : Int     = #line,         // ⭐ line number
        function : String  = #function,     // ⭐ function name
        note     : String? = nil            // additional info
    ) {
        let msgStr = "🐞 \(message)"
        let funcStr = "func: '\(function)'"
        let lineStr = "line: \(line)"
        let fileStr = "file: '\(file)'"
        let debugInfo = "   [\(funcStr), \(lineStr), \(fileStr)]"
        
        log()
        log(msgStr)
        log(debugInfo)
        if let note = note { log(note) }
    }
    
    /// print a `///` + ```-delimited code block to console.
    /// #todo: + 1. padding before code, 2. comments after code
    public static func codeBlock(_ code: [String]) {
        print("/// ```")
        code.forEach { print("/// " + $0) }
        print("/// ```")
    }
    
    /// log a table of data
    @discardableResult
    public static func table(
        _ data: [[String]], 
        padding p: Int = 2, 
        alignments: [Int:String.PadAlign] = [:],
        options: TableOptions = []
    ) -> Bool {
        // 🌀 Collection+
        guard let w = data.columnWidths else {
            return false        // Logger.table() failed
        }
        
        // all widths + spacing
        var lineLength = w.reduce(0, +) + (w.count) * 2 * p
        if options.contains(.dot) { lineLength += 1 + p }        // + (padding) + (dot) on left-side of 1st cell
        
        let hr = "─" * lineLength        // horizontal line
        let padding = " " * p
        
        // table top border
        Logger.log(hr)
        
        // for each row in the data
        for i in data.indices {
            var line = ""
            let row = data[i]
            // dot before each line
            let dotted = options.contains(.dot) 
                ? padding + (i == 0 ? " " : "🔸")    // header without dot
                : ""
            // for each string in the row
            for j in row.indices {
                let str = row[j]        // string in cell
                let width = w[j]        // cell width
                let align = i == 0 ? .center : alignments[j+1] ?? .left    // header is always centered
                // 🌀 String+
                let cell = str.pad(width: width, align: align)             // padded cell
                // padding on both sides of each cell
                line += padding + cell + padding
            }
            line = dotted + line
            Logger.log(line)
            if i == 0 { Logger.log(hr) }    // header line
        }
        
        // table bottom border
        Logger.log(hr)

        return true         // Logger.table() successful
    }
}
````

{% endtab %}

{% tab title="💈範例" %}
💾 程式： [replit](https://replit.com/@pegasusroe/Swift-Playground#test/testLogger.swift)

```swift
func testLogger() {
    
    Logger.log("log message prefixed with a `dot`.", dot: "🔸")
    Logger.debug("`debug` info from Logger.")

    // topline, underline, doubleline
    Logger.topline("`topline`")
    Logger.doubleline("`doubleline` without a newline before it.", newline: false)
    Logger.doubleline("`doubleline` (defalult: newline before it)")

    // box
    Logger.box("`box`")

    // table
    Logger.box("(dotted) table")
    Logger.table(MockData.persons, 
        alignments: [2: .right, 3: .center], 
        options: [.dot]
    )

    let data = [["a"],["b","c"]]

    // failed Logger.table()
    let successful = Logger.table(data)
    if !successful {
        Logger.debug("Rows have different lengths or empty data.")
        Logger.log("data = \(data)", dot: "🔸")
        Logger.log("lengths of rows = \(data.lengthsOfElements)", dot: "🔸")
    }
}
```

#### Result:

```
// 🔸 log message prefixed with a `dot`.
// 
// 🐞 `debug` info from Logger.
//    [func: 'testLogger()', line: 4, file: 'test/testLogger.swift']
// ─────────
// `topline`
// ─────────────────────────────────────────────────
//     `doubleline` without a newline before it.    
// ─────────────────────────────────────────────────
// 
// ──────────────────────────────────────────────────
//     `doubleline` (defalult: newline before it)    
// ──────────────────────────────────────────────────
// ┌─────────────┐
// │    `box`    │
// └─────────────┘
// ┌──────────────────────┐
// │    (dotted) table    │
// └──────────────────────┘
// ─────────────────────────────────
//      Name     Age       Type     
// ─────────────────────────────────
//   🔸  Joe        8       Boy      
//   🔸  May       24    Young Lady  
//   🔸  Peter    123     Old Man    
// ─────────────────────────────────
// 
// 🐞 Rows have different lengths or empty data.
//    [func: 'testLogger()', line: 25, file: 'test/testLogger.swift']
// 🔸 data = [["a"], ["b", "c"]]
// 🔸 lengths of rows = [1, 2]
```

{% endtab %}

{% tab title="📗 參考" %}

* [x] Ficow Shen ⟩ [Swift 中的 #function 到底是什么？](https://blog.ficowshen.com/page/post/60)
  {% endtab %}

{% tab title="📘 手冊" %}

* Swift Ref ⟩ Expressions ⟩ [Primary Expressions](https://docs.swift.org/swift-book/ReferenceManual/Expressions.html#ID389) ⟩ Literal Expression
  {% endtab %}

{% tab title="🗣 討論" %}

* [Weird value of #function literal in Swift 3.1](https://stackoverflow.com/a/45091446/5409815)
* [Macros in Swift?](https://stackoverflow.com/a/24116621/5409815) - custom log function using [#file, #line, #function](/ios/appendix/xcode/directives/file-line-function.md) ⭐️&#x20;
  {% endtab %}

{% tab title="👥 相關" %}

* [#file, #line, #function](/ios/appendix/xcode/directives/file-line-function.md)
* used by [HasMirrors](/ios/swift/debugging/hasmirrors.md).
* [.logType()](/ios/swiftui/view/view/view.md) - inspect a view's type.
* <mark style="color:purple;">**Logger.TableOptions**</mark> is an [OptionSet](/ios/swift/collections/optionset.md).
  {% endtab %}

{% tab title="⬇️ 應用" %}

* [Mirror.handleChildren()](/ios/swift/debugging/mirror/mirror.handlechildren.md)
* [log()](/ios/swift/debugging/log.md)
  {% endtab %}
  {% endtabs %}

## History

{% tabs %}
{% tab title="contents" %}

1. (2022.01.25) \* (v.1) as a global function.
2. (2022.01.28) + topline(), error()
   {% endtab %}

{% tab title="1" %}
💾 程式： [replit](https://replit.com/@pegasusroe/function-3#main.swift)

```swift
// ----------------------
//     ⭐ log function 
// ----------------------

func log(
    _ message: String  = "",
    file     : String  = #file,         // ⭐ file name
    line     : Int     = #line,         // ⭐ line number
    function : String  = #function,     // ⭐ function name
    note     : String? = nil            // additional info
) {
    let noteString = (note == nil) ? "" : "\n \(note!)" 
    print("\n📍 \(message)\n ( func: '\(function)', line: \(line), file: '\(file)' )\(noteString)")
}
```

{% endtab %}

{% tab title="2" %}

```swift
// ──────────────────────────────────────────────────────────────────
// 2022.01.27 * (v.1) + line, spaces, prefixDoubleSlash, log, underline, doubleline, debug
// 2022.01.28 - line: replace "-" by "─" (longer dash), renamed "_line"
//            / log(dot:): `dot`: Bool -> String?
//            + line (draw line), topline (line on top of message)
//            + error(msg)`
// ──────────────────────────────────────────────────────────────────

// ┌───────────────────────┐
// │    Character * Int    │
// └───────────────────────┘

/// `"-" * 3 == "---"`
func * (a: Character, n: Int) -> String {
    return String(repeating: a, count: n)
}

// ┌──────────────┐
// │    Logger    │
// └──────────────┘

public struct Logger {
    public static var prefixDoubleSlash = false
}

// helper
extension Logger {
    /// `Logger.line(3) == "-" * 3 == "---"` 
    static func _line  (_ n: Int) -> String { return "─" * n }
    /// `Logger.spaces(3) == " " * 3 == "   "`
    static func spaces(_ n: Int) -> String { return " " * n }
}

extension Logger {
    
    /// `Logger.log(msg)`
    public static func log(
        _ message: String = "", 
        padding n: Int = 0,
        dot: String? = nil
    ) {
        let prefix = prefixDoubleSlash ? "// " : ""
        let padding = spaces(n)
        let dotted = (dot == nil) ? "" : "\(dot!) "
        print(prefix + padding + dotted + message)
    }
    
    /// `Logger.error(msg)`
    public static func error(
        _ message: String = "", 
        padding n: Int = 0
    ) {
        Logger.log(message, padding: n, dot: "⛔")
    }
    
    /// `Logger.line(n)`
    public static func line(_ n: Int, padding p: Int = 0) {
        log(Logger._line(n), padding: p)
    }
    
    /// `Logger.box(title)`
    public static func box(_ title: String, padding: Int = 4) {
        let w = title.count + padding * 2
        let line = "─" * w
        let spaces = " " * padding
        
        Logger.log("┌" + line + "┐")
        Logger.log("│" + spaces + title + spaces + "│")
        Logger.log("└" + line + "┘")
    }
    
    /// `Logger._drawline(msg)`
    static func _drawline(
        _ message : String, 
        length   n: Int? = nil,         // line length
        padding  p: Int  = 0,           // padding on both sides of message
        topline   : Bool = false,       // draw top line
        bottomline: Bool = true         // draw bottom line
    ) {
        let defaultLength = message.count + p * 2
        let line = Logger._line(n ?? defaultLength)
        let padding = spaces(p)
        
        if topline { log(line) }
        log(padding + message + padding)
        if bottomline { log(line) }
    }
    
    /// `Logger.underline(msg)`
    public static func underline(_ message: String, length n: Int? = nil, padding p: Int = 0) {
        _drawline(message, length: n, padding: p)
    }
    
    /// `Logger.doubleline(msg)`
    public static func doubleline(
        _ message: String, 
        length  n: Int? = nil,         // line length
        padding p: Int  = 4,           // padding on both sides of message
        newline  : Bool = true         // newline before message
    ) {
        if newline { log() }           // new line
        _drawline(message, length: n, padding: p, topline: true)
    }
    
    /// `Logger.topline(msg)`
    public static func topline(
        _ message: String, 
        length  n: Int? = nil,         // line length
        padding p: Int  = 0            // padding on both sides of message
    ) {
        _drawline(message, length: n, padding: p, topline: true, bottomline: false)
    }
    
    /// `Logger.debug(msg)`
    public static func debug(
        _ message: String  = "",
        file     : String  = #file,         // ⭐ file name
        line     : Int     = #line,         // ⭐ line number
        function : String  = #function,     // ⭐ function name
        note     : String? = nil            // additional info
    ) {
        let msgStr = "📍 \(message)"
        let funcStr = "func: '\(function)'"
        let lineStr = "line: \(line)"
        let fileStr = "file: '\(file)'"
        let debugInfo = "   [\(funcStr), \(lineStr), \(fileStr)]"
        
        log()
        log(msgStr)
        log(debugInfo)
        if let note = note { log(note) }
    }
}
```

{% endtab %}
{% endtabs %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://lochiwei.gitbook.io/ios/swift/debugging/logger.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
