0

What we do: We are building an app used to communicate with an external hardware device. The application can receive log messages from the external device. The application already logs application events to a Sentry project.

What we want to do: We want the application to pass on the logs received from the external device to a separate Sentry project, independent of anything logged by the application itself. To achieve this, we have created a new project, and configured two Client objects using the two projects' DSNs.

The issue: Events are logged to the correct project, but the breadcrumbs appears to use a shared storage for both the Client objects, so they contain events from both projects.

Is there an easy way to define separate suites/domains/folders for the two Client objects or their BreadcrumbStores?

We have tried: We're looking into subclassing SentryFileManager and passing it to a new BreadcrumbStore, but it seems unnecessarily complicated for such a trivial task.

Oyvindkg
  • 111
  • 10

2 Answers2

0

1) Subclass SentryFileManager to modify its paths

The issue here appears to be SentryFileManager's hard coded path properties in init(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"];

Unfortunately, they are all private, so subclassing SentryFileManager still won't give us access to them. Although I wouldn't recommended it, we ended up setting them using key/value coding to circumvent the compiler's access control.

Remember to create the directories if they don't exist.

There is nothing exiting going on in this subclass. It simply overwrites the protected properties with unique, encapsulated URLs for the provided domain name.

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)
            }
        }
    }
}

As you can see, subclassing isn't strictly necessary, but other objects really shouldn't start messing with the SentryFileManagers private properties. Neither should subclasses, but they "really shouldn't" a bit less.

2) Update the clients breadcrumb store

Then we define a new BreadcrumbStore for the external device's Sentry client using our subclass.

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

client.breadcrumbs = BreadcrumbStore(fileManager: fileManager)

So, thats probably all, isn't it? Turns out it isn't.

3) Update the clients file manager

It turns out that the client itself contains a private file manager that causes trouble. Suddenly the breadcrumbs are stored at out encapsulated paths, but events are stored using the default SentryFileManager.

Once again we have to dive into the grit and circumvent the access control using key/value coding.

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

client.breadcrumbs = BreadcrumbStore(fileManager: fileManager)

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

And thats all folks!

Oyvindkg
  • 111
  • 10
0

Sentry 3.10+ supports logging to multiple projects

Oyvindkg
  • 111
  • 10