30

So this answer Do I need to disable NSLog before release Application? gives a great way to disable NSLog in a production environment, but unfortunately, this solution does not seem to work for Swift projects. My approach was to place the following code in the bridging header .h file that I am using for some pods in my project.

#ifdef DEBUG
    #define DLog(...) NSLog(@"%s %@", __PRETTY_FUNCTION__, [NSString stringWithFormat:__VA_ARGS__])
#else
    #define DLog(...) do { } while (0)
#endif

However, using DLog in Swift code is causing the compiler to state that they are unrecongnized symbols. Is there somewhere else I should be placing this #ifdef or is there a different solution for Swift project in general?

Community
  • 1
  • 1
thatidiotguy
  • 8,701
  • 13
  • 60
  • 105

8 Answers8

43

You'll need to set up a compiler flag to use the Swift preprocessor - go to the Swift Compiler - Custom Flags section of Build Settings to set up a -D DEBUG flag:

DEBUG flag

Then in your code you can define a DLog() function and only print your message if the DEBUG flag is set:

func DLog(message: String, function: String = #function) {
    #if DEBUG
    println("\(function): \(message)")
    #endif
}
Valentin Shergin
  • 7,166
  • 2
  • 50
  • 53
Nate Cook
  • 92,417
  • 32
  • 217
  • 178
  • 1
    I actually have already added the `-D DEBUG` flag, but thank you anyway. Where would you suggest I place this function so it can be seen globally? This approach looks interesting. – thatidiotguy Nov 12 '14 at 17:43
  • 1
    It can be in any file that is compiled with your module/application - in the AppDelegate.swift file is fine, or you can make a separate file for global constants, etc. – Nate Cook Nov 12 '14 at 17:53
  • 1
    I guess we can count on the compiler to optimise out the empty function calls? – Gregzo Jun 26 '15 at 10:08
  • The provided `println()` (`print()` as of Swift 2.0) function accepts both `String` and `NSString` arguments. How can I achieve the same in the signature of `DLog()`? – Nicolas Miari Aug 28 '15 at 06:31
  • Oh, forget it... I found the solution in the signature of print() on the documentation. See my answer. – Nicolas Miari Aug 28 '15 at 06:34
  • I found another good article. https://www.marekbell.com/stripping-print-and-debugprint-in-swift-for-release-builds/ Explained where we shall put the code. – Cullen SUN Aug 13 '16 at 16:33
  • @CullenSUN Tried the method described at that link, but it didn't overtake any print calls at all. – Jonny Feb 23 '17 at 07:51
9

I am a pretty junior-level developer and need things defined a little more clearly to follow along at times. I had been having trouble with a similar issue, replacing println() instead, and when i asked that question my inquiry was marked as a duplicate of this question.

After lots of research, trial, error, error, error, and ERROR!!, I found that the sequence I needed to follow is:

  1. Click on the project name at the top of the File Navigator at the left of the Xcode project window. This is line that has the name of the project, how many build targets there are, and the iOS SDK version.
  2. Choose the Build Settings tab and scroll down to the "Swift Compiler - Custom Flags" section near the bottom. Click the Down Arrow next to Other Flags to expand the section.
  3. Click on the Debug line to select it. Place your mouse cursor over the right side of the line and double-click. A list view will appear. Click the + button at the lower left of the list view to add a value. A text field will become active.
  4. In the text field, enter the text -D DEBUG and press Return to commit the line.
  5. Add a new Swift file to your project. You are going to want to make a custom class for the file, so enter text along the lines of the following:

    class Log {
    
       var intFor : Int
    
      init() {
        intFor = 42
      }
    
      func DLog(message: String, function: String = __FUNCTION__) {
        #if DEBUG
          println("\(function): \(message)")
        #endif
      }
    }
    

(I was having trouble getting the class init to be accepted by Xcode today, so the init may be a bit more heavyweight than necessary.)

  1. Now you will need to reference your custom class in any class in which you intend to use the new custom function in place of println() Add this as a property in every applicable class:

       let logFor = Log()
    
  2. Now you can replace any instances of println() with logFor.DLog(). The output also includes the name of the function in which the line was called.

  3. Note that inside class functions I couldn't call the function unless I made a copy of the function as a class function in that class, and println() is also a bit more flexible with the input, so I couldn't use this in every instance in my code.
Nate Birkholz
  • 2,739
  • 4
  • 20
  • 29
6

Check out the Apple Swift blog entry on writing assert here

Briefly, the answer is to write DLog as:

func DLog(message:String, function:String = __FUNCTION__) {
#if !NDEBUG
    NSLog("%@, %@", function, message)
#endif
}
David Berry
  • 40,941
  • 12
  • 84
  • 95
  • Where would you suggest I place this function so it's available globally? In my `AppDelegate`? – thatidiotguy Nov 12 '14 at 17:43
  • 1
    For stuff like this I generally create a separate file. My version sits in "Logging.swift" That way when I need it again, it's pretty easy to pick it up and drop it somewhere else. – David Berry Nov 12 '14 at 21:59
  • 1
    Look like the most relevant answer. One thing to note is that as of Swift 3, `__FUNCTION__` is deprecated. Instead, `#function` should be used in place of `__FUNCTION__`. – Paul B Feb 10 '19 at 11:45
2

Below, I have modified the answer by Nate Cook given above. This version works with both String and NSString arguments:

func DLog<T>(message:T, function: String = __FUNCTION__) {
    #if DEBUG
        if let text = message as? String {

            print("\(function): \(text)")
        }
    #endif
}

It can be handy when you need to do something like this:

DLog("Received data:")
DLog(NSString(data:httpResponseBodyData, encoding:NSUTF8StringEncoding))
Nicolas Miari
  • 16,006
  • 8
  • 81
  • 189
1

Pure magic _isDebugAssertConfiguration() does all stuff instead of the Custom Flags-related mess:

func log<T>(argument: T, file: String = #file, line: Int = #line, function: String = #function) {
    guard _isDebugAssertConfiguration() else {
        return
    }

    let fileName = NSURL(fileURLWithPath: file, isDirectory: false).URLByDeletingPathExtension?.lastPathComponent ?? "Unknown"

    print("\(fileName)@\(line)/\(function): \(argument)")
}

See more info (and options) about it here: https://stackoverflow.com/a/34532569/496389 .

Community
  • 1
  • 1
Valentin Shergin
  • 7,166
  • 2
  • 50
  • 53
1
#if DEBUG
#else
public func NSLog(_ format: String, _ args: CVarArg...)
{
    // noop
}
#endif

The quest leading to this: swift debugPrint vs print

Anton Tropashko
  • 5,486
  • 5
  • 41
  • 66
0

I just found this amazing global function: https://gist.github.com/Abizern/a81f31a75e1ad98ff80d

This matches Nate Cook's answer and additionally prints the filename and line number of the encountered debug print statement.

uclagamer
  • 987
  • 6
  • 6
  • 2
    Try to register the code here, because the link may become unavailable. – Haroldo Gondim Aug 22 '15 at 17:08
  • 1
    @uclagamer The way we do it on Stackoverflow is include code in answer, not just link to code somewhere else. That's why you were voted down, I assume. – Jonny Feb 23 '17 at 07:53
0

I agree with uclagamer's answer, which is essentially a souped up print function wrapped in a preprocessor conditional:

func gLog<T>(@autoclosure object: () -> T, _ file: String = __FILE__, _ function: String = __FUNCTION__, _ line: Int = __LINE__)
    {
    #if DEBUG
        let value = object()
        let stringRepresentation: String

        if let value = value as? CustomDebugStringConvertible
            {
            stringRepresentation = value.debugDescription
            }
        else if let value = value as? CustomStringConvertible
            {
            stringRepresentation = value.description
            }
        else
            {
            fatalError("gLog only works for values that conform to CustomDebugStringConvertible or CustomStringConvertible")
            }

        let fileURL = NSURL(string: file)?.lastPathComponent ?? "Unknown file"
        let queue = NSThread.isMainThread() ? "UI" : "BG"
        let gFormatter = NSDateFormatter()
        gFormatter.dateFormat = "HH:mm:ss:SSS"
        let timestamp = gFormatter.stringFromDate(NSDate())

        print("\(timestamp) \(queue) = \(fileURL) | \(function)[\(line)]: " + stringRepresentation)
    #endif
    }

This is updated for Swift 2.0. All credit goes to Abider Nasir. His original blog post (and linked gist) can be found here:

http://abizern.org/2015/02/01/debug-logging-in-swift/

IMPORTANT NOTE: if anyone is struggling to find the "Swift Compiler - Custom Flags section of Build Settings", which has been mentioned many, many times, you need to make sure that "All" build settings are shown, not just the "Basic" build settings, as is the default.

enter image description here

Gene Loparco
  • 2,157
  • 23
  • 23