Implement `app.installation.id` resource attribute
The experimental semconv app.installation.id got accepted recently (https://github.com/open-telemetry/semantic-conventions/pull/1897), I think it would be a useful addition to the existing attributes provided by the SDK.
What makes its implementation a bit tricky, is that there are multiple ways you can implement this resource attribute on Android. In the specs we have listed these four options:
- Firebase Installation ID.
- A globally unique UUID which is persisted across sessions in your application.
- App set ID.
Settings.getString(Settings.Secure.ANDROID_ID).
As @bidetofevil pointed out the most practical choice for this SDK is probably number two (UUID), however, I could imaging exposing a setter for each of the options, as they all have pros and cons.
OpenTelemetryRum.builder(this, config)
.appInstallationIdProvider(UuidAppInstallationIdProvider()) // default behavior, if nothing is specified
// or
.appInstallationIdProvider(FirebaseAppInstallationIdProvider())
// or
.appInstallationIdProvider(AppSetIdAppInstallationIdProvider())
// or
.appInstallationIdProvider(AndroidIdAppInstallationIdProvider())
// or
.appInstallationIdProvider(object : AppInstallationIdProvider() { ... })
// ...
For now, I think generating one and using it will be sufficient. If there's a need to override, we can take that in a separate PR?
Sure, sounds good to me.
I started looking into implementing these and I think it's considerably trickier than we think, primarily due to the fact that this is a resource attribute, and resources are immutable and created in the hot path at app start.
- uuid persistence -- does IO and would block the main thread, violating strict mode.
- firebase installation id - retrieving this uses an async api, which would have to be made synchronous and would block the main thread, violating strict mode.
- app set id - appears to use the
AppSetManager.getAppSetIdwhich is also async and has the same problems as the above Settings.Secure.getString(app.contentResolver, ANDROID_ID)- appears to be synchronous, and it does generate a warning in IntelliJ but is probably fine.
The docs around Settings.getString(ANDROID_ID) (here) seem to suggest decent support below api 26 too, so this is what I'm leaning toward right now. Other ideas?
@breedx-splk that's a really tricky one. The approach, which I'd consider the most optimal from the SDK user's point of view is to defer the creation of the Resource object until the async part, as it is only needed when the BufferDelegatingXXXExporters receive their actual exporters. So essentially the same trick would be needed as #709, but this time for the Resource object instead of the exporters.
That said, I don't see any straight-forward way to achieve this, as the Resource object is passed when creating the SdkLoggerProvider & co, which takes place in the synchronous block when constructing the OpenTelemetry object, and it is registered to a bunch of immutable objects (e.g. to LoggerSharedState, which is shared across all SdkLoggers created by the SdkLoggerProvider). I tried a hacky way to change this field with reflection, but it doesn't work unfortunately due to the JDK being strict when it comes to changing final fields on 'hidden' classes.
All the alternatives I can think of for delaying the registration of the Resource object are more intrusive or require huge refactors to the Java SDK.
So going forward I see these possible approaches:
- Look for other hacky ways to change the immutably registered Resource object
- Do not create the
OpenTelemetryobject asynchronously -> might actually speed up the app startup as the complex synchronous initialization code would take place on a background thread, however, using theOpenTelemetryobject would become significantly trickier for the SDK users, as they cannot create traces, counters, etc., until the async code completes. - Use the
Settings.getString(ANDROID_ID)field, as it is synchronous - Do IO in the main thread -> probably not acceptable, as fast app startup times are non-negotiable for mobile apps
At work I've implemented the 2nd alternative built on top of the Java SDK. It was easy for us, because we are only using logging, and our Logback OpenTelemetryAppender buffers log records in memory until the OpenTelemetry object is install()ed. Users of the Android SDK want to start traces, counters and the like early on, so it isn't that easy in this case.
This is where I currently am in this thought process, and I still haven't made up my mind, which of these alternatives to recommend. I was optimistic for the first one, but my simplest approach didn't work sadly.
Which of these alternatives do you find the most optimal? Can you think of other alternative solutions?
For app.installation.id, the disk IO is unavoidable IMO, but it could be minimized.
For anything that is tied to the built APK, we can generate a file at build time and load that in via the classloader. That has worked really well for us
Per our discussion at the SIG today, I think we should just store it in shared preferences and read it, and make a note in the docs saying that while this can cause a strict mode violation, it's the lesser of all evils and the practice impact of the implementation should be minimal (and lets make sure it is :-))