How to print out the method name and line number in swift

Here is an example of what I want to do:

func application(application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: NSError)
{
    let  nm =  NetworkModel()
    nm.sendlog("file name :AppDelegate , line number : 288", info: " Failed to register: \(error)")
}

current scenario i done that hard coded value line number and file name . but is it possible to programatically pick line number and file name .


Solution 1:

Literal        Type     Value

#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.
#dsohandle     UnsafeMutablePointer   The dso handle.

Example

print("Function: \(#function), line: \(#line)") 

With default values in parameters you can also create a function

public func track(_ message: String, file: String = #file, function: String = #function, line: Int = #line ) { 
    print("\(message) called from \(function) \(file):\(line)") 
}

which can be used like this

track("enters app")

In Swift 2.1

 Literal        Type     Value

__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.

for more info see the documentation

Solution 2:

You can use #function, #file, #line

Here is the implementation of log method in swift : https://github.com/InderKumarRathore/SwiftLog

Below is the snippet

public func debugLog(object: Any, functionName: String = #function, fileName: String = #file, lineNumber: Int = #line) {
  #if DEBUG
    let className = (fileName as NSString).lastPathComponent
    print("<\(className)> \(functionName) [#\(lineNumber)]| \(object)\n")
  #endif
}

Solution 3:

For swift 4 and swift 5:

func printLog(_ message: String, file: String = #file, function: String = #function, line: Int = #line) {
    #if DEVELOPMENT
        let className = file.components(separatedBy: "/").last
        print(" ❌ Error ----> File: \(className ?? ""), Function: \(function), Line: \(line), Message: \(message)")
    #endif
}

// "❌ Error ----> File: classNameViewController.swift, function: functionName(), Line: 123, Message: messageError"

Solution 4:

Example Logging Code (TL;DR)

import os.log

@available(OSX 11.0, iOS 14.0, *)
extension Logger {
  private static var subsystem = Bundle.main.bundleIdentifier!

  /// Logs the payment flows like Apple Pay.
  @available(OSX 11.0, iOS 14.0, *)
  static let payments = Logger(subsystem: subsystem, category: "payments")
}

static func DLog(message: StaticString, file: StaticString = #file, function: StaticString = #function, line: UInt = #line, column: UInt = #column, category: String, type: OSLogType = .info, bundle: Bundle = .main) {

  // This method is only for iOS 14+
  if #available(OSX 11.0, iOS 14.0, *) {

    Logger.payments.debug("\(file) : \(function) : \(line) : \(column) - \(message, privacy: .private)")
    // This makes the message unreadable without a debugger attached.

  } else {
    // Fallback on earlier versions

    let customLog = OSLog(subsystem: bundle.bundleIdentifier!,
                          category: category)

    // IMPORTANT: I have assumed here that you only print out non-sensitive data! Using %{private}@ or %{public}@ in an interpolated string is not portable to `Logger`!
    os_log(message, log: customLog, type: type)

    // Unfortunately this legacy API doesn't support non-StaticString logs. :(
  }
}

Note that you may need to rework this code for more flexibility over private/public access levels - os_log and Logger don’t handle privacy levels in the same way. This is just to show how to use the API if you don’t add os_log privacy levels in message.

Apple recommend using OS Logging which is why I have used this approach rather than print statements. I added this answer because of the new Logger API in iOS 14 that also enables string interpolation.

Motivation

I think the answers here solve the question for < iOS 14, but they can be improved further for memory efficiency since this Debug Logging function will likely be used all across a codebase in iOS 14+ (new Logger API). Apple recommend using OS Logging which is why I have used this approach rather than print statements (although these will still work).

These are minor improvements but I think they can help because even minor improvements add up. In fact, the Swift Standard Library uses these optimizations everywhere they can)! These optimisations are contributing factors to the incredible memory efficiency of Swift despite being a high-level language (and part of great API Design!). (-:

Because this function would probably fit well as part of a generalized (OS) Logging service, I have also included considerations I make when logging within apps. I think these could help you when logging for debugging purposes (I answered this question because it was probably for debug logging!).

Improvements

  1. I prefer to use UInt in certain situations if I know the Int is going to be positive, and any edge case crashes are unlikely. Note, I don't use UInt when interfacing with Foundation classes that use Int types for this reason. This allocates less memory at runtime and I find being more specific also helps me understand the code better.
  2. I prefer to use StaticString where I know the string is known at compile-time. From the docs, StaticString only provides low-level access to String contents and is immutable. Thus only use it where appropriate. Even concatenated String literals cannot be used to initialize a StaticString. Because its functionality is more restricted than String this means that it is lightweight - only requiring an address pointer and length under the hood. Using StaticString also provides a small performance boost with OS-level memory management because it is neither allocated nor deallocated (no need for reference counting).
  3. Using Apple's recommended Logger API for logging is better than print statements for multiple reasons: performance, privacy and unified management. It can be better for debugging issues in released products (OS Logs provide more context).

I would recommend using StaticString where you can, for example with the function and file names, and UInt for the line and column numbers. This is only now possible with the Logger iOS 14+ API.

(Bonus) Logging Considerations

Should I do LOTS of logging?

I think logging is a balancing act. Lots of logging can be very helpful to debug issues when viewing crash reports if you have access to these. However, you need to balance Privacy, Size of the Compiled Binary and Overwhelming the Logging System.

1. Privacy: Redact sensitive info using "\(message, privacy: .private)" and "\(message, privacy: .public)" with Logger, and DON'T print sensitive information when using NSLog or print statements. I would recommend replacing print statements (for production) with OS Logs when they are for debugging purposes. Or use a preprocessor directive (#if DEBUG) inside the logging service to print only on a Debug scheme..

2. Size of the Compiled Binary: Logging statements are essentially more lines of code. The more code you add, the bigger your binary. This is usually not an issue, as long as you don't log everything under the sun since only a large increase in this metric would affect the binary size.

3. Overwhelming the logging system: If you log excessively, the device may have to discard some logs in order to keep running (limited RAM or swap space at runtime due to OS multitasking - worse for older devices). Apple is usually reliable in handling lots of user logging, and it's hard to achieve this in practice. Nevertheless, if you do encounter this, some more useful log events could be pushed out of the window.

OS Logs

The most important thing to get right is the log level. By default on iOS log entries at .info and below will be suppressed at the point of log generation, so the only real negative with those is the binary size. If you log at .log (.default for os_log) or higher, you need to be careful about not logging too much. Apple's general advice is that you look at each of these higher-level log entries to make sure they contain info that might be useful while debugging.

Finally, make sure to set the subsystem and category. The unified logging system processes a lot of log entries - the subsystem with category makes it much easier for you to focus on specific problems.

How to choose the category and subsystem?

This is a pretty opinionated topic, so I will focus on what I would think about when deciding. YMMV: there may be better options and if so, please let me know.

1. By feature (product-specific): For example, if you are a shopping app, maybe organise the subsystem by the payment, login or other app flows. If you aren't already using a modular codebase for organisation into features (or frameworks for shared code) then I can recommend this tutorial series.

2. By technology: What I mean by this is the domain of the code, is it for Push Notifications, Deep Links, User Defaults (or persistence) logic. This could be helpful for the category parameter.

3. By target: I like to also use the bundleIdentifier of the current Bundle if I can for the subsystem. This makes even more sense if you have Extension Targets (Push Notifications), multiple Targets, or different Platforms (like WatchOS, SiriKit extensions and similar). You can get the current bundle, using this code:

let myBundle = Bundle(for: MyClass.self)

Debugging user issues using OS device logs

See here for a detailed walkthrough (Access Device Console Logs). The main idea is to connect the device to your Mac (or use the Mac itself for Mac apps ;)), open Console.app, run the app and try to reproduce the crash. If you manage to get that far in the first place, then you can correlate the time of the crash with the logs for more context.

Any other reasons for logging less?

If you are an Apple Early-Adopter then you know how buggy some of the iOS Betas can be ;). When posting bug reports to Apple, the OS Logs get included in sysdiagnose files, so you may get a faster turnaround time because your bug reports have less noise.

Should I use a third-party logging service?

Depends if you can afford it and whether you really need it. I prefer to minimise the number of third-party dependencies I import in my code (if I can control this) and the native (Apple) Logging APIs worked fine for most of my use cases. These services charge extra per month, but the advantage is the user cannot view the logs (even if he wants to) by hooking his device up to Console.app on his Mac. Note that this is NOT an issue if you apply the .private log levels, so I don't usually use third-party services (YMMV). A potential reason to go for web-logging is more security against reverse-engineering though, as long as you trust the third-party vendor against data breaches (BUT also it's not as eco-friendly ;)).