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