How to use Crashlytics logging in Swift?
Asked Answered
F

8

39

This article describes how to use Crashlytics logging in objective-c. However, after going throught the installation steps for propertly referencing Crashlytics and Fabric into my project, I don't seem to have access to that method.

Looking at the Crashlytics.h file, I can see it defined using compiler flags:

#ifdef DEBUG
#define CLS_LOG(__FORMAT__, ...) CLSNSLog((@"%s line %d $ " __FORMAT__), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__)
#else
#define CLS_LOG(__FORMAT__, ...) CLSLog((@"%s line %d $ " __FORMAT__), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__)
#endif

This block just appears to wrap the CLSNLog and the CLSLog functions depending on the compiler flag.

So, thinking I'd just go straight to the source, I tried to reference CLSLog directly from a swift file. Still no luck:

My-Bridging-Header.h:

#import <Crashlytics/Crashlytics.h>

Log.swift:

import Foundation
import Fabric
import Crashlytics

func Log(message: String) {
    NSLog("%@", message)
    CLS_LOG("%@", message)
    CLSLog("%@", message)
}

The last two lines in the Log function throw the error, Use of unresolved identifier. Crashlytics crash reporting works just fine, except for the logging feature. According to this article, logging support for Swift has been implemented.

As far as versions go, I'm running the latest version of Fabric/Crashlytics (December release, at the time of this post).

(Interesting note, I can see/use CLSLogv()...)

Does anyone know the correct way to incorporate CLS_LOG for use in a Swift project?

Fimbria answered 20/1, 2015 at 20:13 Comment(0)
E
11

You have to create an intermediary bridge like this:

CrashlyticsBridge.h:

#import <Foundation/Foundation.h>

@interface CrashlyticsBridge : NSObject

+ (void)log:(NSString *)message;

@end

CrashlyticsBridge.m

#import "CrashlyticsBridge.h"
#import <Crashlytics/Crashlytics.h>

@implementation CrashlyticsBridge

+ (void)log:(NSString *)message {
    CLS_LOG(@"%@", message);
}

@end

My-Bridging-Header.h:

#import "CrashlyticsBridge.h"

Then, you can simply add that to your Log function:

func Log(message: String) {
    CrashlyticsBridge.log(message)
}

This will give you the Crashlytics logging and NSLogging while you are debugging.

Evenfall answered 20/1, 2015 at 20:32 Comment(1)
The problem with this answer is that you lose all of the extra information that CLS_LOG() usually contains. __PRETTY_FUNCTION__ and __LINE__ become useless because they will simply print out information about your wrapper rather than the calling code. See my answer for a functioning adapter/bridge in swift where this information is printed correctly.Oba
S
38

Mike from Crashlytics here.

To use custom logging in Swift, just use CLSLogv or CLSNSLogv. You need to make an array and then call getVaList function on that array.

Here's a snippet:

CLSLogv("Log something %d %d %@", getVaList([1, 2, "three"]))

For CLSNSLogv:

CLSNSLogv("hello %@", getVaList(["goodbye"]))
Suggestibility answered 20/1, 2015 at 20:25 Comment(18)
Unfortunately this does not work for me. Using CLSLogv unlike expected, it doesn't NSLog in DEBUG mode. Using CLSNSLogv it doesn't even build.Premiere
What errors are you seeing? Can you also include the code snippets you are using?Suggestibility
Hey Greg, happy to help, but can you let me know more about what's unclear?Suggestibility
Hi @MikeB, unfortunately this answer and the documentation on your website does not include a function that allows us to get contextual information in our logs in swift. I am referring to __PRETTY_FUNCTION__ and __LINE__. I spent a bit of time and found a way around this natively in Swift. See my answer. I also emailed your support team with information about it because I think it would be useful to include in your SDK but I have not heard back yet.Oba
Thanks for letting me know Dima - really appreciate the feedback!Suggestibility
@MikeB - Is CLSLogv supposed to print to the console as well? I'm compiling fine but it's not printing for me with Fabric 1.3.4Lapse
@Lapse If you want to print out to the console, CLSLogv will work in debug builds, but in release builds, it won't. If you want this always, use CLSNSLogv. You can also take a look here for more info: docs.fabric.io/ios/crashlytics/enhanced-reports.htmlSuggestibility
Thanks Mike. That's really strange then that I'm not seeing anything printed in the console with CLSLogv with my debug build. I ended up doing something like this: let printData = "something to print" CLSLogv("%@", getVaList([printData])) println(printData)Lapse
@user392412 for NDK crashes - you would need to use the following: docs.fabric.io/android/crashlytics/…Suggestibility
The question isn't about how to do logging in Crashlytics; it's whether something similar to the CLS_Log macro is available in Swift to automatically toggle between calling CLSLogv and CLSNSLogv for Release and Debug builds. Your answer seems to be 'no'. Please update your answer to indicate whether there are plans to provide a similar feature in Swift or not, and to indicate whether there are any known workarounds in the interim.Odeen
@Odeen I believe my answer mentioned CLSNSLogV previously, but I've added a code snippet which works in a local test. There isn't a native Swift implementation in case that's the source of confusion.Suggestibility
@MikeBonnell unfortunately this does not have the same safeguards as the Objective-C macro. For example, CLSLogv("foo %@", getVaList([])) compiles without warning, but will crash. Crashlytics needs Swift logging functions that just take a String argument, so we can do the interpolation in swift.Ashlynashman
@MikeBonnell Are these log statements supposed to appear in the crash reports? I use the exact same approach like you described. Why aren't the logs available in the crash reports? Might have misunderstood something, but this isn't showing in my report, or maybe I can't find it?Peake
Yes, they'll show up on the crash reports. From an issue, click on "View all sessions" and then you'll see the logs. Since they are specific to a crash, they're not aggregated up to the full issue.Suggestibility
@MikeBonnell After how much time should a log message appear in the Fabric Dashboard?Heilungkiang
Logs appear when the crash has been processed so once you can see the crash, the logs will be present. One thing to keep in mind is that you need to log the data on each app session.Suggestibility
@MikeBonnell I'm using CLSLogv, and also setUserIdentifier. On Fabric panel, I can see the user identifier correctly set, but for logs it shows: "No logs found. Learn more!". What else should I do other then calling CLSLogv?Sarson
Is the logged value blank or nil? That would cause it to not be logged. A way to check this would be to log out the value you're logging to the console.Suggestibility
L
30

Here is my version adapted from Dima's answer. I have no need of the arguments, since you can do all the formatting within the Swift string that you pass.

func DebugLog(_ message: String, file: StaticString = #file, function: StaticString = #function, line: Int = #line) {
    let output: String
    if let filename = URL(fileURLWithPath: file.description).lastPathComponent.components(separatedBy: ".").first {
        output = "\(filename).\(function) line \(line) $ \(message)"
    } else {
        output = "\(file).\(function) line \(line) $ \(message)"
    }

    #if targetEnvironment(simulator)
        NSLogv("%@", getVaList([output]))
    #elseif DEBUG
        CLSNSLogv("%@", getVaList([output]))
    #else
        CLSLogv("%@", getVaList([output]))
    #endif
}

And you would use it like this:

DebugLog("this is a log message")
DebugLog("this is a log message \(param1) \(param2)")

EDIT: Updated to Swift 3.1

Lindley answered 23/2, 2016 at 12:31 Comment(6)
@Lindley Are those messages visible via Crashlytics, even if no crash occured? And beside this the if/else shouldn't be necessary, from the Docs: "In Debug builds, CLS_LOG passes through to NSLog so you can see the output in Xcode and on the device or simulator.".Utilitarian
@FrederikA.Winkelsdorf CLS_LOG is just a define that points to CLSNSLog for debug builds, otherwise CLSLog, and does the fancy function and line number formatting.Lindley
This works well in Swift. But i have found that getVaList([]) sometimes fails in swift and crashes CLSLogv and your app. It seems like using getVaList([""]) with an empty string fixes this.Varioloid
Actually I had a crash because some log messages contained % characters, and like NSLog, CLSLogv first parameter is a format string, so if there is something like %d or %@ in the string, it will replace it with the parameters passed via getVaList. To avoid a crash, you should use the following line instead: CLSLogv("%@", getVaList([output]))Eire
Since you don't need the variadic arguments, why don't you use CLSNSLog and CLSLog instead?Hyperkinesia
@Koen Because CLSNSLog and CLSLog are not available in SwiftLindley
O
21

I needed something similar to CLS_LOG() in Swift that printed out contextual information about the location of the call. Normally this would not be possible without preprocessor directives but I found out how to replicate this behavior pretty closely in Swift here: https://developer.apple.com/swift/blog/?id=15

The identifiers we need (#file, #function, #line) show information about the caller if you set them as default values in an argument list.

Note: If you are logging errors that may have % symbols in them, such as network query strings, this may crash. You'll need to join the string first (e.g. let string = "\(filename).\(function) line \(line) $ \(message)")

Swift 3 version (note: this is a global function, so it should be placed outside of any struct or class definition):

/// Usage:
///
/// CLS.log("message!")
/// CLS.log("message with parameter 1: %@ and 2: %@", ["First", "Second"])
///
func CLS_LOG_SWIFT(format: String = "", _ args: [CVarArg] = [], file: String = #file, function: String = #function, line: Int = #line) 
{
    let filename = URL(string: file)?.lastPathComponent.components(separatedBy: ".").first

    #if DEBUG
        CLSNSLogv("\(filename).\(function) line \(line) $ \(format)", getVaList(args))
    #else
        CLSLogv("\(filename).\(function) line \(line) $ \(format)", getVaList(args))
    #endif
}

Swift 2 version:

// CLS_LOG_SWIFT()
// CLS_LOG_SWIFT("message!")
// CLS_LOG_SWIFT("message with parameter 1: %@ and 2: %@", ["First", "Second"])
func CLS_LOG_SWIFT(format: String = "",
    _ args:[CVarArgType] = [],
    file: String = __FILE__,
    function: String = __FUNCTION__,
    line: Int = __LINE__)
{
    let filename = NSURL(string:file)?.lastPathComponent?.componentsSeparatedByString(".").first

    #if DEBUG
        CLSNSLogv("\(filename).\(function) line \(line) $ \(format)", getVaList(args))
    #else
        CLSLogv("\(filename).\(function) line \(line) $ \(format)", getVaList(args))
    #endif

}

// CLS_LOG() output: -[ClassName methodName:] line 10 $
// CLS_LOG_SWIFT() output: ClassName.methodName line 10 $

And here is a gist with some more information and the actual file I put this code in: https://gist.github.com/DimaVartanian/a8aa73ba814a61f749c0

As you can see it is pretty close to the original macro and only differs in that you can't see if you are calling a class method or an instance method, and that you need to include your format argument list enclosed in an array. Both are limitations I believe there is no way around right now but pretty minor. You also need to make sure DEBUG is defined in your Swift compiler flags. It does not carry over from your regular flags automatically.

Oba answered 9/3, 2015 at 23:50 Comment(2)
Swift 2.0 varian of filename: let filename = NSURL(string:file)?.lastPathComponent?.componentsSeparatedByString(".").firstKidwell
Getting compiler error "use of unresolved identifier 'CLSLogv' in Swift 4Lustig
E
11

You have to create an intermediary bridge like this:

CrashlyticsBridge.h:

#import <Foundation/Foundation.h>

@interface CrashlyticsBridge : NSObject

+ (void)log:(NSString *)message;

@end

CrashlyticsBridge.m

#import "CrashlyticsBridge.h"
#import <Crashlytics/Crashlytics.h>

@implementation CrashlyticsBridge

+ (void)log:(NSString *)message {
    CLS_LOG(@"%@", message);
}

@end

My-Bridging-Header.h:

#import "CrashlyticsBridge.h"

Then, you can simply add that to your Log function:

func Log(message: String) {
    CrashlyticsBridge.log(message)
}

This will give you the Crashlytics logging and NSLogging while you are debugging.

Evenfall answered 20/1, 2015 at 20:32 Comment(1)
The problem with this answer is that you lose all of the extra information that CLS_LOG() usually contains. __PRETTY_FUNCTION__ and __LINE__ become useless because they will simply print out information about your wrapper rather than the calling code. See my answer for a functioning adapter/bridge in swift where this information is printed correctly.Oba
M
3

Swift 3 compatible

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

enter image description here

func dLog(message: Any, filename: String = #file, function: String = #function, line: Int = #line) {
    #if DEBUG
         print("[\(filename.lastPathComponent):\(line)] \(function) - \(message)")
        #else
        CLSLogv("[\(filename.lastPathComponent):\(line)] \(function) - \(message)", getVaList([""]))
    #endif
}


 dLog(object)
Monkey answered 7/7, 2016 at 16:6 Comment(1)
This gives a compile error. 'lastPathComponent' is unavailable: Use lastPathComponent on URL instead.Scotland
C
2

Swift 3 compatible version for log message in Crashlytics

func CLS_LOG_SWIFT(_ format: String = "", _ args: [CVarArg] = [], file: String = #file, function: String = #function, line: Int = #line) {

    let formatString: String!

    if let filename =  file.components(separatedBy: "/").last?.components(separatedBy: ".").first {

           formatString = "\(filename).\(function) line \(line) $ \(format)"

    }else{

           formatString = "\(file).\(function) line \(line) $ \(format)"
    }

    #if DEBUG
        CLSNSLogv(formatString, getVaList(args))
    #else
        CLSLogv(formatString, getVaList(args))
    #endif
}
Cytogenetics answered 19/9, 2017 at 9:24 Comment(0)
E
1

How about like this?

import Foundation
import Crashlytics

func CLSLog(_ format: String = "", _ args: CVarArg..., file: String = #file, function: String = #function, line: Int = #line) {
    let formatString: String!
    if let filename =  file.components(separatedBy: "/").last?.components(separatedBy: ".").first {
        formatString = "\(filename).\(function) line \(line) $ \(format)"
    } else {
        formatString = "\(file).\(function) line \(line) $ \(format)"
    }

    #if DEBUG
    CLSNSLogv(formatString, getVaList(args))
    #else
    CLSLogv(formatString, getVaList(args))
    #endif
}

No need for the array then, just list the variadic parameters

CLSLog("message")
CLSLog("message %@ %@", "one", "two")
Eventual answered 28/6, 2018 at 13:47 Comment(0)
G
0

Any one who want to log error usin Crashlytics can use the below code and its working fine for me :)

Crashlytics.sharedInstance().recordError(error)

error is NSERROR object that holds the error that produced during some action

Godson answered 18/1, 2017 at 8:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.