91

In Objective C you can log the method that is being called using:

NSLog(@"%s", __PRETTY_FUNCTION__)

Usually this is used from a logging macro.

Although Swift does not support macro's (I think) I still would like to use a generic log statement that includes the name of the function that was called. Is that possible in Swift?

Update: I now use this global function for logging which can be found here: https://github.com/evermeer/Stuff#print And which you can install using:

pod 'Stuff/Print'

Here is the code:

public class Stuff {

    public enum logLevel: Int {
        case info = 1
        case debug = 2
        case warn = 3
        case error = 4
        case fatal = 5
        case none = 6

        public func description() -> String {
            switch self {
            case .info:
                return "❓"
            case .debug:
                return "✳️"
            case .warn:
                return "⚠️"
            case .error:
                return ""
            case .fatal:
                return ""
            case .none:
                return ""
            }
        }
    }

    public static var minimumLogLevel: logLevel = .info

    public static func print<T>(_ object: T, _ level: logLevel = .debug, filename: String = #file, line: Int = #line, funcname: String = #function) {
        if level.rawValue >= Stuff.minimumLogLevel.rawValue {
            let dateFormatter = DateFormatter()
            dateFormatter.dateFormat = "MM/dd/yyyy HH:mm:ss:SSS"
            let process = ProcessInfo.processInfo
            let threadId = "?"
            let file = URL(string: filename)?.lastPathComponent ?? ""
            Swift.print("\n\(level.description()) .\(level) ⏱ \(dateFormatter.string(from: Foundation.Date()))  \(process.processName) [\(process.processIdentifier):\(threadId)]  \(file)(\(line)) ⚙️ \(funcname) ➡️\r\t\(object)")
        }
    }
}

Which you can use like this:

Stuff.print("Just as the standard print but now with detailed information")
Stuff.print("Now it's a warning", .warn)
Stuff.print("Or even an error", .error)

Stuff.minimumLogLevel = .error
Stuff.print("Now you won't see normal log output")
Stuff.print("Only errors are shown", .error)

Stuff.minimumLogLevel = .none
Stuff.print("Or if it's disabled you won't see any log", .error)    

Which will result in:

✳️ .debug ⏱ 02/13/2017 09:52:51:852  xctest [18960:?]  PrintStuffTests.swift(15) ⚙️ testExample() ➡️
    Just as the standard print but now with detailed information

⚠️ .warn ⏱ 02/13/2017 09:52:51:855  xctest [18960:?]  PrintStuffTests.swift(16) ⚙️ testExample() ➡️
    Now it's a warning

 .error ⏱ 02/13/2017 09:52:51:855  xctest [18960:?]  PrintStuffTests.swift(17) ⚙️ testExample() ➡️
    Or even an error

 .error ⏱ 02/13/2017 09:52:51:855  xctest [18960:?]  PrintStuffTests.swift(21) ⚙️ testExample() ➡️
    Only errors are shown
Edwin Vermeer
  • 13,017
  • 2
  • 34
  • 58

12 Answers12

108

Swift has #file, #function, #line and #column. From Swift Programming Language:

#file - String - The name of the file in which it appears.

#line - Int - The line number on which it appears.

#column - Int - The column number in which it begins.

#function - String - The name of the declaration in which it appears.

inigo333
  • 3,088
  • 1
  • 36
  • 41
Kreiri
  • 7,840
  • 5
  • 30
  • 36
  • 11
    Well sure -- those all forward from C. But that didn't answer the question about `__PRETTY_FUNCTION__`, which isn't easily created from the given options. (Is there a `__CLASS__` ? If so, that'd help.) – Olie Aug 24 '14 at 15:33
  • 10
    In Swift 2.2 should use #function, #file and others as shown here: http://stackoverflow.com/a/35991392/1151916 – Ramis Mar 22 '16 at 07:11
  • I use `print("\(self.classForCoder)::\(#function)")` to get closer to `__PRETTY_FUNCTION__` it's less elegant than Obj-C, but that's Swift. – Roy Lovejoy Jul 20 '22 at 18:27
73

Starting from Swift 2.2 we should use:

  • #file (String) The name of the file in which it appears.
  • #line (Int) The line number on which it appears.
  • #column (Int) The column number in which it begins.
  • #function (String) The name of the declaration in which it appears.

From The Swift Programming Language (Swift 3.1) at page 894.

func specialLiterals() {
    print("#file literal from file: \(#file)")
    print("#function literal from function: \(#function)")
    print("#line: \(#line) -> #column: \(#column)")
}
// Output:
// #file literal from file: My.playground
// #function literal from function: specialLiterals()
// #line: 10 -> #column: 42
Ramis
  • 13,985
  • 7
  • 81
  • 100
20

Swift 4
Here's my approach:

func pretty_function(_ file: String = #file, function: String = #function, line: Int = #line) {

    let fileString: NSString = NSString(string: file)

    if Thread.isMainThread {
        print("file:\(fileString.lastPathComponent) function:\(function) line:\(line) [M]")
    } else {
        print("file:\(fileString.lastPathComponent) function:\(function) line:\(line) [T]")
    }
}

Make this a global function and just call

pretty_function()

Bonus: You will see the thread is executed on, [T] for a background thread and [M] for the Main thread.

Pau Ballada
  • 1,491
  • 14
  • 13
  • Need to change the declaration of file from String to NSString. lastPathComponent is not available on String. – primulaveris Nov 05 '15 at 11:39
  • 1
    Awesome dude. Tiny change for Swift > 2.1: "println" has been renamed to "print". print("file:\(file.debugDescription) function:\(function) line:\(line)") – John Doe Feb 02 '16 at 22:02
  • Cool, good that it works. Would be also great to be able to pass class/object into it somehow (one option is to use an explicit self argument). Thanks. – Sea Coast of Tibet Nov 02 '16 at 15:26
  • Problems with your approach: - This function isn't thread-safe. If you call it from different threads at once, be prepared for some bad surprises - Using global functions is bad practice – Karoly Nyisztor Dec 27 '18 at 16:39
9

As of XCode beta 6, you can use reflect(self).summary to get the class name and __FUNCTION__ to get the function name, but things are a bit mangled, right now. Hopefully, they'll come up with a better solution. It might be worthwhile to use a #define until we're out of beta.

This code:

NSLog("[%@ %@]", reflect(self).summary, __FUNCTION__)

gives results like this:

2014-08-24 08:46:26.606 SwiftLessons[427:16981938] [C12SwiftLessons24HelloWorldViewController (has 2 children) goodbyeActiongoodbyeAction]

EDIT: This is more code, but got me closer to what I needed, which I think is what you wanted.

func intFromString(str: String) -> Int
{
    var result = 0;
    for chr in str.unicodeScalars
    {
        if (chr.isDigit())
        {
            let value = chr - "0";
            result *= 10;
            result += value;
        }
        else
        {
            break;
        }
    }

    return result;
}


@IBAction func flowAction(AnyObject)
{
    let cname = _stdlib_getTypeName(self)
    var parse = cname.substringFromIndex(1)                                 // strip off the "C"
    var count = self.intFromString(parse)
    var countStr = String(format: "%d", count)                              // get the number at the beginning
    parse = parse.substringFromIndex(countStr.lengthOfBytesUsingEncoding(NSUTF8StringEncoding))
    let appName = parse.substringToIndex(count)                             // pull the app name

    parse = parse.substringFromIndex(count);                                // now get the class name
    count = self.intFromString(parse)
    countStr = String(format: "%d", count)
    parse = parse.substringFromIndex(countStr.lengthOfBytesUsingEncoding(NSUTF8StringEncoding))
    let className = parse.substringToIndex(count)
    NSLog("app: %@ class: %@ func: %@", appName, className, __FUNCTION__)
}

It gives output like this:

2014-08-24 09:52:12.159 SwiftLessons[1397:17145716] app: SwiftLessons class: ViewController func: flowAction
Olie
  • 24,597
  • 18
  • 99
  • 131
8

I prefer to define a global log function:

[Swift 3.1]

func ZYLog(_ object: Any?, filename: String = #file, line: Int = #line, funcname: String = #function) {
    #if DEBUG
    print("****\(Date()) \(filename)(\(line)) \(funcname):\r\(object ?? "nil")\n")
    #endif
}

[Swift 3.0]

func ZYLog<T>(_ object: T?, filename: String = #file, line: Int = #line, funcname: String = #function) {
    #if DEBUG
    print("****\(Date()) \(filename)(\(line)) \(funcname):\r\(object)\n")
    #endif
}

[Swift 2.0]

func ZYLog<T>(object: T, filename: String = __FILE__, line: Int = __LINE__, funcname: String = __FUNCTION__) {
    println("****\(filename.lastPathComponent)(\(line)) \(funcname):\r\(object)\n")
}

the output is something like:

****ZYHttpSessionManager.swift(78) POST(_:parameters:success:failure:):
[POST] user/login, {
    "auth_key" = xxx;
    "auth_type" = 0;
    pwd = xxx;
    user = "xxx";
}

****PointViewController.swift(162) loadData():
review/list [limit: 30, skip: 0]

****ZYHttpSessionManager.swift(66) GET(_:parameters:success:failure:):
[GET] review/list, {
    "auth_key" = xxx;
    uuid = "xxx";
}
ZYiOS
  • 5,204
  • 3
  • 39
  • 45
  • You don't actually need a generic function here, because `object` parameter can be declared as `Any` instead of `T`. – werediver Dec 28 '15 at 11:38
5

Here is an updated Swift 2 answer.

func LogW(msg:String, function: String = __FUNCTION__, file: String = __FILE__, line: Int = __LINE__){
    print("[WARNING]\(makeTag(function, file: file, line: line)) : \(msg)")
}

private func makeTag(function: String, file: String, line: Int) -> String{
    let url = NSURL(fileURLWithPath: file)
    let className:String! = url.lastPathComponent == nil ? file: url.lastPathComponent!
    return "\(className) \(function)[\(line)]"
}

Example of use:

LogW("Socket connection error: \(error)")
Daniel Ryan
  • 6,976
  • 5
  • 45
  • 62
  • 1
    This is superb. But then again.. LogW cannot be used exactly the same as print() (with parameters, separated by comma).. – Guntis Treulands Jan 21 '16 at 15:53
  • "LogW cannot be used exactly the same as print() (with parameters, separated by comma" I was thinking to add this support but I found I didn't need it. "LogW("Socket connection error: \(error) other info: \(otherInfo)")" – Daniel Ryan Jan 22 '16 at 01:08
  • 1
    True. Well I tinkered around and only other solution I found was - using extra () to hold the statement, to make it as similar to print() as possible. Used your answer to create this one https://github.com/GuntisTreulands/ColorLogger-Swift Anyways, thanks a lot! :) – Guntis Treulands Jan 22 '16 at 09:24
  • Very useful! As of Swift 2.2, `__FUNCTION__ becomes #function, __FILE__ becomes #file, and __LINE__ becomes #line.` – Carl Smith Jul 28 '16 at 01:24
  • We had trouble with the new values. We will wait until swift 3 until updating our code base. – Daniel Ryan Jul 28 '16 at 01:53
0

I use, this is all that is required in a swift file, all other files will pick it up (as a global function). When you want to release the application just comment out the line.

import UIKit

func logFunctionName(file:NSString = __FILE__, fnc:String = __FUNCTION__){  
    println("\(file.lastPathComponent):\(fnc)")
}
Andrej
  • 7,266
  • 4
  • 38
  • 57
iCyberPaul
  • 650
  • 4
  • 15
0

Or slight function modification with:

func logFunctionName(file:String = __FILE__, fnc:String = __FUNCTION__, line:(Int)=__LINE__) {
    var className = file.lastPathComponent.componentsSeparatedByString(".")
    println("\(className[0]):\(fnc):\(line)")

}

/* will produce an execution trace like: AppDelegate:application(_:didFinishLaunchingWithOptions:):18 Product:init(type:name:year:price:):34 FirstViewController:viewDidLoad():15 AppDelegate:applicationDidBecomeActive:62 */

Oprisk
  • 41
  • 3
0

Another way to log function call:

NSLog("\(type(of:self)): %@", #function)
Max MacLeod
  • 26,115
  • 13
  • 104
  • 132
Ako
  • 956
  • 1
  • 10
  • 13
0

Swift 3.0

public func LogFunction<T>(object: T, filename: String = #file, line: Int = #line, funcname: String = #function) {
    let dateFormatter = DateFormatter()
    dateFormatter.dateFormat = "MM/dd/yyyy HH:mm:ss:SSS"
    let process = ProcessInfo.processInfo()
    let threadId = "?"
    print("\(dateFormatter.string(from:Date())) \(process.processName) [\(process.processIdentifier):\(threadId)] \(filename)(\(line)) \(funcname)::: \(object)")
}
AleyRobotics
  • 970
  • 1
  • 10
  • 17
0

Swift 3.x+

If you don't want the entire file name then here's a quick fix for that.

func trace(fileName:String = #file, lineNumber:Int = #line, functionName:String = #function) -> Void {
    print("filename: \(fileName.components(separatedBy: "/").last!) function: \(functionName) line: #\(lineNumber)")
}

filename: ViewController.swift function: viewDidLoad() line: #42
Hemang
  • 26,840
  • 19
  • 119
  • 186
0

I use print("\(self.classForCoder)::\(#function)") to get closer to __PRETTY_FUNCTION__ - it's less elegant than one macro, but it is what it is.

Roy Lovejoy
  • 141
  • 6