从单个 iOS 应用程序登录到多个 Sentry 项目

Logging to multiple Sentry projects from a single iOS application

我们做什么: 我们正在构建一个用于与外部硬件设备通信的应用程序。该应用程序可以从外部设备接收日志消息。该应用程序已将应用程序事件记录到 Sentry 项目。

我们想做的事情: 我们希望应用程序将从外部设备接收到的日志传递给一个单独的哨兵项目,独立于应用程序本身记录的任何内容。为此,我们创建了一个新项目,并使用两个项目的 DSN 配置了两个 Client 对象。

问题: 事件记录到正确的项目中,但面包屑似乎为两个 Client 对象使用共享存储,因此它们包含来自两个项目的事件。

有没有简单的方法来为两个 Client 对象或它们的 BreadcrumbStore 定义单独的 suites/domains/folders?

我们已经尝试过: 我们正在研究子类化 SentryFileManager 并将其传递给新的 BreadcrumbStore,但对于这样一个微不足道的任务来说,它似乎不必要地复杂。

1) 子类化 SentryFileManager 以修改其路径

这里的问题似乎是 SentryFileManagerinit(error:) 中的硬编码路径属性:

NSString *cachePath = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject;

self.sentryPath = [cachePath stringByAppendingPathComponent:@"io.sentry"];
...
self.breadcrumbsPath = [self.sentryPath stringByAppendingPathComponent:@"breadcrumbs"];
...
self.eventsPath = [self.sentryPath stringByAppendingPathComponent:@"events"];

不幸的是,它们都是私有的,因此子类化 SentryFileManager 仍然无法让我们访问它们。虽然我不推荐它,但我们最终使用 key/value 编码来设置它们以绕过编译器的访问控制。

如果目录不存在,记得创建目录。

这个子类中没有任何退出。它只是用提供的域名的唯一封装 URL 覆盖受保护的属性。

class DomainFileManager: SentryFileManager {

    private enum Component {
        static let sentry      = "io.sentry"
        static let breadcrumbs = "breadcrumbs"
        static let events      = "events"
    }

    private enum Key {
        static let sentryPath      = "sentryPath"
        static let breadcrumbsPath = "breadcrumbsPath"
        static let eventsPath      = "eventsPath"
    }


    private static var cacheURL: URL {
        return FileManager.default.urls(
            for: .cachesDirectory,
            in:  .userDomainMask
        )[0]
    }

    private let domainURL: URL

    private var sentryURL: URL {
        return domainURL.appendingPathComponent(Component.sentry)
    }

    private var breadcrumbsURL: URL {
        return sentryURL.appendingPathComponent(Component.breadcrumbs)
    }

    private var eventsURL: URL {
        return sentryURL.appendingPathComponent(Component.events)
    }


    init(domain: String, error: ()) throws {

        domainURL = DomainFileManager.cacheURL.appendingPathComponent(domain)

        try super.init(error: error)

        setValue(sentryURL.path, forKey: Key.sentryPath)
        setValue(breadcrumbsURL.path, forKey: Key.breadcrumbsPath)
        setValue(eventsURL.path, forKey: Key.eventsPath)

        for url in [sentryURL, breadcrumbsURL, eventsURL] {
            if !FileManager.default.fileExists(atPath: url.path) {
                try DomainFileManager.createDirectory(atPath: url.path)
            }
        }
    }
}

如您所见,子类化并不是绝对必要的,但其他对象确实不应该开始扰乱 SentryFileManager 的私有属性。子类也不应该,但它们"really shouldn't"少一点。

2) 更新客户端面包屑存储

然后我们使用我们的子类为外部设备的哨兵客户端定义一个新的BreadcrumbStore

let fileManager = try DomainFileManager(domain: "domain", error: ())

client.breadcrumbs = BreadcrumbStore(fileManager: fileManager)

所以,这可能就是全部,不是吗?原来不是。

3) 更新客户端文件管理器

事实证明,客户端本身包含一个导致麻烦的私有文件管理器。面包屑突然存储在封装路径中,但事件使用默认 SentryFileManager 存储。

我们必须再次深入研究并使用 key/value 编码来规避访问控制。

let fileManager = try DomainFileManager(domain: "domain", error: ())

client.breadcrumbs = BreadcrumbStore(fileManager: fileManager)

client.setValue(fileManager, forKey: "fileManager")

这就是所有人!

Sentry 3.10+ 支持记录到多个项目