> For the complete documentation index, see [llms.txt](https://lochiwei.gitbook.io/ios/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://lochiwei.gitbook.io/ios/swift/debugging/logger.md).

# 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 %}
