Is there a Swift alternative for NSLog(@"%s", __PRETTY_FUNCTION__)
Asked Answered
B

12

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
Bruges answered 25/6, 2014 at 7:21 Comment(3)
I use NSLog("Running %@ : %@",NSStringFromClass(self.dynamicType),__FUNCTION__)Brinkema
I use github.com/goktugyil/QorumLogsAceae
I think your logging style should be the definition of "pretty function". Thanks for sharing.Penner
D
109

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.

Dickinson answered 25/6, 2014 at 7:30 Comment(3)
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.)Guzzle
In Swift 2.2 should use #function, #file and others as shown here: https://mcmap.net/q/234457/-is-there-a-swift-alternative-for-nslog-quot-s-quot-__pretty_function__Prosperous
I use print("\(self.classForCoder)::\(#function)") to get closer to __PRETTY_FUNCTION__ it's less elegant than Obj-C, but that's Swift.Bulgar
P
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
Prosperous answered 14/3, 2016 at 15:19 Comment(1)
This should be marked as the currently correct answer.Ens
C
22

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.

Concerted answered 5/3, 2015 at 17:21 Comment(4)
Need to change the declaration of file from String to NSString. lastPathComponent is not available on String.Santa
Awesome dude. Tiny change for Swift > 2.1: "println" has been renamed to "print". print("file:(file.debugDescription) function:(function) line:(line)")Stereoscope
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.Dyspeptic
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 practiceHamann
G
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
Guzzle answered 24/8, 2014 at 15:51 Comment(0)
V
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";
}
Veljkov answered 28/10, 2014 at 7:28 Comment(1)
You don't actually need a generic function here, because object parameter can be declared as Any instead of T.Fennelflower
H
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)")
Hoke answered 19/1, 2016 at 2:44 Comment(5)
This is superb. But then again.. LogW cannot be used exactly the same as print() (with parameters, separated by comma)..Hereabout
"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)")"Hoke
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 github.com/GuntisTreulands/ColorLogger-Swift Anyways, thanks a lot! :)Hereabout
Very useful! As of Swift 2.2, __FUNCTION__ becomes #function, __FILE__ becomes #file, and __LINE__ becomes #line.Angularity
We had trouble with the new values. We will wait until swift 3 until updating our code base.Hoke
R
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)")
}
Rarefied answered 28/11, 2014 at 11:50 Comment(0)
N
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 */

Norrie answered 18/2, 2015 at 20:18 Comment(0)
W
0

Another way to log function call:

NSLog("\(type(of:self)): %@", #function)
Wariness answered 22/12, 2015 at 14:33 Comment(0)
D
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)")
}
Dropline answered 16/8, 2016 at 8:42 Comment(0)
L
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
Longawa answered 2/5, 2017 at 9:20 Comment(0)
B
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.

Bulgar answered 20/7, 2022 at 18:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.