如何在 Swift 中使用 Crashlytics 登录?

Posted

技术标签:

【中文标题】如何在 Swift 中使用 Crashlytics 登录?【英文标题】:How to use Crashlytics logging in Swift? 【发布时间】:2015-03-19 05:04:46 【问题描述】:

This 文章介绍了如何在objective-c 中使用Crashlytics 登录。但是,在完成将 Crashlytics 和 Fabric 正确引用到我的项目中的安装步骤后,我似乎无法使用该方法。

查看 Crashlytics.h 文件,我可以看到它是使用编译器标志定义的:

#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

此块似乎只是根据编译器标志包装了 CLSNLogCLSLog 函数。

所以,我以为我会直接找到源代码,所以我尝试直接从 swift 文件中引用 CLSLog。还是没有运气:

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)

Log 函数的最后两行抛出错误,Use of unresolved identifier。 Crashlytics 崩溃报告工作得很好,除了日志记录功能。根据this 的文章,已经实现了对 Swift 的日志记录支持。

就版本而言,我正在运行最新版本的 Fabric/Crashlytics(发布本文时为 12 月版本)。

(有趣的注释,我可以看到/使用CLSLogv()...)

有谁知道在 Swift 项目中使用 CLS_LOG 的正确方法?

【问题讨论】:

【参考方案1】:

来自 Crashlytics 的 Mike 在这里。

要在 Swift 中使用自定义日志记录,只需使用 CLSLogv 或 CLSNSLogv。您需要创建一个数组,然后在该数组上调用 getVaList 函数。

这是一个sn-p:

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

对于 CLSNSLogv:

CLSNSLogv("hello %@", getVaList(["goodbye"]))

【讨论】:

不幸的是,这对我不起作用。使用 CLSLogv 与预期不同,它不会在 DEBUG 模式下使用 NSLog。使用 CLSNSLogv 它甚至无法构建。 您看到了什么错误?能否也包括您正在使用的代码 sn-ps? @Oren 如果您想打印到控制台,CLSLogv 将在调试版本中工作,但在发布版本中则不行。如果您总是想要这个,请使用 CLSNSLogv。您也可以在这里查看更多信息:docs.fabric.io/ios/crashlytics/enhanced-reports.html @MikeBonnell 不幸的是,它没有与 Objective-C 宏相同的保护措施。例如,CLSLogv("foo %@", getVaList([])) 编译时没有警告,但会崩溃。 Crashlytics 需要 Swift 日志函数,它只接受一个 String 参数,所以我们可以在 swift 中进行插值。 是的,它们会出现在崩溃报告中。从问题中,单击“查看所有会话”,然后您将看到日志。由于它们是特定于崩溃的,因此它们不会汇总为完整的问题。【参考方案2】:

这是我根据 Dima 的回答改编的版本。我不需要参数,因为您可以在传递的 Swift 字符串中进行所有格式设置。

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

你会这样使用它:

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

编辑:更新到 Swift 3.1

【讨论】:

@ian 即使没有发生崩溃,这些消息是否可以通过 Crashlytics 看到?除此之外,if/else 不是必需的,来自 Docs:“在 Debug 构建中,CLS_LOG 传递到 NSLog,因此您可以在 Xcode 和设备或模拟器上看到输出。”。 @FrederikA.Winkelsdorf CLS_LOG 只是一个指向 CLSNSLog 以进行调试构建的定义,否则是 CLSLog,并执行花哨的功能和行号格式化。 这在 Swift 中运行良好。但我发现getVaList([]) 有时会快速失败并导致CLSLogv 和您的应用程序崩溃。似乎使用带有空字符串的getVaList([""]) 可以解决此问题。 其实我崩溃了,因为一些日志消息包含%字符,和NSLog一样,CLSLogv第一个参数是一个格式字符串,所以如果字符串中有%d或%@之类的东西,它会将其替换为通过 getVaList 传递的参数。为避免崩溃,您应该改用以下行:CLSLogv("%@", getVaList([output])) @Koen 因为 CLSNSLogCLSLog 在 Swift 中不可用【参考方案3】:

我需要类似于 Swift 中的CLS_LOG() 的东西,它可以打印出有关呼叫位置的上下文信息。通常,如果没有预处理器指令,这是不可能的,但我在这里找到了如何在 Swift 中非常接近地复制这种行为: https://developer.apple.com/swift/blog/?id=15

如果您将它们设置为参数列表中的默认值,我们需要的标识符 (#file, #function, #line) 会显示有关调用者的信息。

注意:如果您记录的错误中可能包含% 符号,例如网络查询字符串,这可能会崩溃。您需要先加入字符串(例如let string = "\(filename).\(function) line \(line) $ \(message)"

Swift 3 版本(注意:这是一个全局函数,所以它应该放在任何结构或类定义之外):

/// 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 版本:

// 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 $

这里是一个包含更多信息的要点以及我将此代码放入的实际文件:https://gist.github.com/DimaVartanian/a8aa73ba814a61f749c0

如您所见,它与原始宏非常接近,不同之处在于您看不到您是在调用类方法还是实例方法,并且您需要将格式参数列表包含在一个大批。两者都是限制,我相信现在没有办法解决,但非常小。您还需要确保在您的 Swift 编译器标志中定义了 DEBUG。它不会自动从您的常规标志中继承。

【讨论】:

Swift 2.0 文件名变体:let filename = NSURL(string:file)?.lastPathComponent?.componentsSeparatedByString(".").first 获取编译器错误“在 Swift 4 中使用未解析的标识符 'CLSLogv'【参考方案4】:

你必须像这样创建一个中间桥:

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"

然后,您可以简单地将其添加到您的 Log 函数中:

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

这将在您调试时为您提供 Crashlytics 日志记录和 NSLogging。

【讨论】:

这个答案的问题是你丢失了CLS_LOG() 通常包含的所有额外信息。 __PRETTY_FUNCTION____LINE__ 变得毫无用处,因为它们只会打印出有关您的包装器的信息而不是调用代码。在正确打印此信息的情况下,请快速查看我对正常工作的适配器/网桥的回答。【参考方案5】:

兼容 Swift 3

您需要设置编译器标志以使用 Swift 预处理器 - 转到 Build SettingsSwift Compiler - Custom Flags 部分以设置 -D DEBUG 标志

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)

【讨论】:

这会产生编译错误。 'lastPathComponent' 不可用:请改用 URL 上的 lastPathComponent。【参考方案6】:

用于 Crashlytics 中的日志消息的 Swift 3 兼容版本

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

【讨论】:

【参考方案7】:

这样怎么样?

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

那么就不需要数组了,只列出可变参数

CLSLog("message")
CLSLog("message %@ %@", "one", "two")

【讨论】:

【参考方案8】:

任何想在 Crashlytics 中记录错误的人都可以使用下面的代码,它对我来说可以正常工作:)

Crashlytics.sharedInstance().recordError(error)

error 是 NSERROR 对象,它保存在某些操作期间产生的错误

【讨论】:

以上是关于如何在 Swift 中使用 Crashlytics 登录?的主要内容,如果未能解决你的问题,请参考以下文章

如何安装 Fabric 和 Crashlytics

如何安装 Firebase Crashlytics Swift 3.0

Crashlytics iOS - 第 0 行崩溃 - Swift 来源

Firebase crashlytics 没有告诉我 swift 的确切崩溃问题

Firebase Crashlytics 调试模式不在 ios 中发送报告

如何在 iOS 中使用 Crashlytics 而不使用 Fabric