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
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.
Starting from Swift 2.2 we should use:
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
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With