Simple Swift logging class with inferred file/function/line

Been using a lot of print() when working on iOS projects. Hoping to see more information printed to aid in debugging, I did up a simple logging class in Swift which infers the file, function and line number where the logging is done. Not using the stack trace as the output from Thread.callStackSymbols is not informational enough.

// Z.swift
import Foundation

public class Z {
    public static func log(
        _ message: String,
        _ extra: String = "",
        file: String = #file,
        function: String = #function,
        line: Int = #line
    ) {
        printLog(message, extra, file:file, function:function, line:line)
    }

    public static func log(
        _ message: Int,
        _ extra: String = "",
        file: String = #file,
        function: String = #function,
        line: Int = #line
    ) {
        printLog(String(message), extra, file:file, function:function, line:line)
    }

    public static func log(
        _ message: Any?,
        _ extra: String = "",
        file: String = #file,
        function: String = #function,
        line: Int = #line
    ) {
        // Passing [MyClass] to log() will end up coming here, hence a check 
        // to see if it is an array
        if let array = message as? [Any] {
            logList(array, extra, file:file, function:function, line:line)
        } else {
            printLog(
                getObjectAsString(message), 
                extra, 
                file:file, 
                function:function, 
                line:line
            )
        }
    }

    static func logList(
        _ message: [Any]?,
        _ extra: String = "",
        file: String = #file,
        function: String = #function,
        line: Int = #line
    ) {
        if extra != "" {
            printLog(extra, "", file:file, function:function, line:line)
        }

        if message != nil {
            message!.forEach({ (item) in
                printLog(
                    getObjectAsString(item), 
                    "", 
                    file:file, 
                    function:function, 
                    line:line
                )
            })
        }
    }

    static func printLog(
        _ message: String,
        _ extra: String = "",
        file: String = #file,
        function: String = #function,
        line: Int = #line
    ) {
        // Extract the filename from the full path
        let filename: String = (file as NSString).lastPathComponent

        // Extract the method name without the parameter list
        var methodName = function
        if let regex = try? NSRegularExpression(pattern: "\\([^\\)]*\\)") {
            methodName = regex.stringByReplacingMatches(
                in: function,
                options: [],
                range: NSRange(location: 0, length: function.count),
                withTemplate: ""
            )
        }

        print("\(filename):\(methodName):L\(line): \(message)\(extra)")
    }

    static func getObjectAsString(_ object: Any?) -> String {
        var output: String = ""

        if object != nil {
            let mirror = Mirror(reflecting: object!)
            for (property, value) in mirror.children {
                guard let property = property else { continue }

                if ["id", "name"].contains(property) {
                    output += "\(property):\(value); "
                }
            }
        }

        output += String(describing: object)

        return output
    }
}

To use, just call Z.log(). The user does not need to cast the arguments to String first. The 2nd parameter, extras, is optional but useful when logging numbers. Line numbers included in the output are useful for tracing output from callbacks. For objects, public properties id/name are used if they exist. Below is an example of how it is used.

//  ViewController.swift
import UIKit

class ViewController: UIViewController {
    func test() {
        // ViewController:test:L8: Hello World
        Z.log("Hello World") // this is line 8 of the code, hence L8 in output

        // ViewController:test:L11: 10 items
        Z.log(10, " items")     

        // ViewController:test:L16: id:1; name:A; Optional(<Person: 0x2826d528>)
        // Assume Person class with id and name properties.
        // This works on [Person] as well.
        Z.log(Person(id:1, name:"A"))

        // ViewController:test:L22: [Strings]
        // ViewController:test:L22: Optional("alpha")
        // ViewController:test:L22: Optional("beta")
        let items: [String] = ["alpha", "beta"]
        Z.log(items, "[Strings]") // not sure why log output has Optional
    }
}