-
Notifications
You must be signed in to change notification settings - Fork 42
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Initialization events #397
Changes from 5 commits
b1fddb5
fe9da02
b1332d9
e6c7ab5
4d62f65
28b3bb3
38d065a
fe62a0a
a6b072b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -83,10 +83,11 @@ public final class OpenTelemetryRumBuilder { | |
private final OtelRumConfig config; | ||
private final VisibleScreenTracker visibleScreenTracker = new VisibleScreenTracker(); | ||
|
||
private Function<? super SpanExporter, ? extends SpanExporter> spanExporterCustomizer = a -> a; | ||
private final List<Consumer<InstrumentedApplication>> instrumentationInstallers = | ||
new ArrayList<>(); | ||
|
||
private final List<Consumer<OpenTelemetrySdk>> otelSdkReadyListeners = new ArrayList<>(); | ||
private Function<? super SpanExporter, ? extends SpanExporter> spanExporterCustomizer = a -> a; | ||
private Function<? super TextMapPropagator, ? extends TextMapPropagator> propagatorCustomizer = | ||
(a) -> a; | ||
|
||
|
@@ -325,6 +326,7 @@ OpenTelemetryRum build(ServiceManager serviceManager) { | |
Log.e(RumConstants.OTEL_RUM_LOG_TAG, "Could not initialize disk exporters.", e); | ||
} | ||
} | ||
initializationEvents.spanExporterInitialized(spanExporter); | ||
|
||
OpenTelemetrySdk sdk = | ||
OpenTelemetrySdk.builder() | ||
|
@@ -377,14 +379,30 @@ private void scheduleDiskTelemetryReader( | |
} | ||
} | ||
|
||
/** | ||
* Adds a callback to be invoked after the OpenTelemetry SDK has been initialized. This can be | ||
* used to defer some early lifecycle functionality until the working SDK is ready. | ||
* | ||
* @param callback - A callback that receives the OpenTelemetry SDK instance. | ||
* @return this | ||
*/ | ||
public OpenTelemetryRumBuilder addOtelSdkReadyListener(Consumer<OpenTelemetrySdk> callback) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consumer is API 24+ only, but I guess its desugared if isCoreLibraryDesugaringEnabled enabled right There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think so, yeah...and it's used elsewhere. |
||
otelSdkReadyListeners.add(callback); | ||
return this; | ||
} | ||
|
||
/** Leverage the configuration to wire up various instrumentation components. */ | ||
private void applyConfiguration() { | ||
if (config.shouldGenerateSdkInitializationEvents()) { | ||
if (initializationEvents == InitializationEvents.NO_OP) { | ||
initializationEvents = new SdkInitializationEvents(); | ||
SdkInitializationEvents sdkInitEvents = new SdkInitializationEvents(); | ||
addOtelSdkReadyListener(sdkInitEvents::finish); | ||
initializationEvents = sdkInitEvents; | ||
} | ||
Map<String, String> configMap = new HashMap<>(); | ||
// TODO: Convert config to map | ||
// breedx-splk: Left incomplete for now, because I think Cesar is making changes around | ||
// this | ||
initializationEvents.recordConfiguration(configMap); | ||
} | ||
initializationEvents.sdkInitializationStarted(); | ||
|
@@ -491,7 +509,6 @@ private SdkTracerProvider buildTracerProvider( | |
.setResource(resource) | ||
.addSpanProcessor(new SessionIdSpanAppender(sessionId)); | ||
|
||
initializationEvents.spanExporterInitialized(spanExporter); | ||
BatchSpanProcessor batchSpanProcessor = BatchSpanProcessor.builder(spanExporter).build(); | ||
tracerProviderBuilder.addSpanProcessor(batchSpanProcessor); | ||
|
||
|
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
/* | ||
* Copyright The OpenTelemetry Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package io.opentelemetry.android.instrumentation.startup | ||
|
||
import io.opentelemetry.sdk.trace.export.SpanExporter | ||
|
||
interface InitializationEvents { | ||
fun sdkInitializationStarted() | ||
|
||
fun recordConfiguration(config: Map<String, String>) | ||
|
||
fun currentNetworkProviderInitialized() | ||
|
||
fun networkMonitorInitialized() | ||
|
||
fun anrMonitorInitialized() | ||
|
||
fun slowRenderingDetectorInitialized() | ||
|
||
fun crashReportingInitialized() | ||
|
||
fun spanExporterInitialized(spanExporter: SpanExporter) | ||
|
||
companion object { | ||
@JvmField | ||
val NO_OP: InitializationEvents = | ||
object : InitializationEvents { | ||
override fun sdkInitializationStarted() {} | ||
|
||
override fun recordConfiguration(config: Map<String, String>) {} | ||
|
||
override fun currentNetworkProviderInitialized() {} | ||
|
||
override fun networkMonitorInitialized() {} | ||
|
||
override fun anrMonitorInitialized() {} | ||
|
||
override fun slowRenderingDetectorInitialized() {} | ||
|
||
override fun crashReportingInitialized() {} | ||
|
||
override fun spanExporterInitialized(spanExporter: SpanExporter) {} | ||
} | ||
} | ||
} |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
/* | ||
* Copyright The OpenTelemetry Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package io.opentelemetry.android.instrumentation.startup | ||
|
||
import io.opentelemetry.android.common.RumConstants | ||
import io.opentelemetry.api.common.AttributeKey | ||
import io.opentelemetry.api.common.Attributes | ||
import io.opentelemetry.api.incubator.logs.AnyValue | ||
import io.opentelemetry.sdk.OpenTelemetrySdk | ||
import io.opentelemetry.sdk.logs.internal.SdkEventLoggerProvider | ||
import io.opentelemetry.sdk.trace.export.SpanExporter | ||
import java.time.Instant | ||
import java.util.function.Consumer | ||
import java.util.function.Supplier | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
||
class SdkInitializationEvents(private val clock: Supplier<Instant> = Supplier { Instant.now() }) : InitializationEvents { | ||
private val events: MutableList<Event> = ArrayList() | ||
breedx-splk marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
override fun sdkInitializationStarted() { | ||
addEvent(RumConstants.Events.INIT_EVENT_STARTED) | ||
} | ||
|
||
override fun recordConfiguration(config: Map<String, String>) { | ||
val map: MutableMap<String, AnyValue<*>> = HashMap() | ||
breedx-splk marked this conversation as resolved.
Show resolved
Hide resolved
|
||
config.entries.forEach( | ||
Consumer { e: Map.Entry<String, String> -> | ||
map[e.key] = AnyValue.of(e.value) | ||
}, | ||
) | ||
val body = AnyValue.of(map) | ||
addEvent(RumConstants.Events.INIT_EVENT_CONFIG, body) | ||
} | ||
|
||
override fun currentNetworkProviderInitialized() { | ||
addEvent(RumConstants.Events.INIT_EVENT_NET_PROVIDER) | ||
} | ||
|
||
override fun networkMonitorInitialized() { | ||
addEvent(RumConstants.Events.INIT_EVENT_NET_MONITOR) | ||
} | ||
|
||
override fun anrMonitorInitialized() { | ||
addEvent(RumConstants.Events.INIT_EVENT_ANR_MONITOR) | ||
} | ||
|
||
override fun slowRenderingDetectorInitialized() { | ||
addEvent(RumConstants.Events.INIT_EVENT_JANK_MONITOR) | ||
} | ||
|
||
override fun crashReportingInitialized() { | ||
addEvent(RumConstants.Events.INIT_EVENT_CRASH_REPORTER) | ||
} | ||
|
||
override fun spanExporterInitialized(spanExporter: SpanExporter) { | ||
val attributes = | ||
Attributes.of(AttributeKey.stringKey("span.exporter"), spanExporter.toString()) | ||
addEvent(RumConstants.Events.INIT_EVENT_SPAN_EXPORTER, attributes) | ||
} | ||
|
||
fun finish(sdk: OpenTelemetrySdk) { | ||
val loggerProvider = sdk.sdkLoggerProvider | ||
val eventLogger = | ||
SdkEventLoggerProvider.create(loggerProvider).get("otel.initialization.events") | ||
events.forEach { event: Event -> | ||
val eventBuilder = | ||
eventLogger.builder(event.name) | ||
.setTimestamp(event.timestamp) | ||
.setAttributes(event.attributes) | ||
if (event.body != null) { | ||
// TODO: Config is technically correct because config is the only startup event | ||
// with a body, but this is ultimately clunky/fragile. | ||
eventBuilder.put("config", event.body) | ||
} | ||
eventBuilder.emit() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think I misunderstood the goal of the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Generating events that track milestones in the agent startup and configuration is the main goal with this. These events have been extremely helpful when looking at startup times and understanding how long the agent itself takes to reach certain states in the initialization. I don't see any benefit in exposing these hooks to user/application code. What would a user do at any of these times? They can't send their own events, because the sdk has not been initialized.
Yes, there is
I don't think it has to happen first, but yes, it would be correct to have defined semantic conventions for these.
Almost certainly yes, but that didn't exist when I wrote it! :) Moving so fast over here....heh. We can pick that up as a follow-on item I hope. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
We technically can't either :) though there's always a way... In any case, I get the idea now, it's certainly helpful to get this info for debugging the Android agent 👍 I'm generally hesitant about producing telemetry out of the box because it assumes that we know what every user of this lib will want to emit, which sounds a bit opinionated, so it's nice that we provide a way to opt-out.
This use case seems to be pretty specific to this agent, so I'm fine with moving forward without adding these events to the semconv first, however, I'd like to have some sort of litmus test to know when it's fine to send telemetry without conventions and when it's not, because we're going to need to be prepared when someone asks about it (unless it's a common practice around OTel impls to send telemetry that's not yet defined in the conventions, in which case then we probably shouldn't stress too much about it).
Sounds good! It seems like you need to have this feature available before the instrumentation API work is done, so I think it's fine to move forward as is for now. I'm planning to adapt all the existing automatically generated telemetry to the new API anyway, so I think I can pick this one up too. |
||
} | ||
} | ||
|
||
private fun addEvent( | ||
name: String, | ||
body: AnyValue<*>, | ||
) { | ||
addEvent(name, null, body) | ||
} | ||
|
||
private fun addEvent( | ||
name: String, | ||
attr: Attributes, | ||
) { | ||
addEvent(name, attr, null) | ||
} | ||
|
||
private fun addEvent( | ||
name: String, | ||
attr: Attributes? = null, | ||
body: AnyValue<*>? = null, | ||
) { | ||
events.add(Event(clock.get(), name, attr, body)) | ||
} | ||
breedx-splk marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
private class Event( | ||
val timestamp: Instant, | ||
val name: String, | ||
val attributes: Attributes?, | ||
val body: AnyValue<*>? = null, | ||
) { | ||
private constructor(timestamp: Instant, name: String, body: AnyValue<*>) : this( | ||
breedx-splk marked this conversation as resolved.
Show resolved
Hide resolved
|
||
timestamp, | ||
name, | ||
null, | ||
body, | ||
) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This reminds me of what I tried to do in this other PR, which so far seemed like it was superseded by the Instrumentation API, as it provides a way for instrumentations to get notified when the SDK is ready to be used. What use case is this method trying to solve?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's similarity for sure. It's a straightforward means for a component (in this case, the init events) to get notified immediately after the otel sdk has been initialized. In this case, the init events have to be buffered until they can be emitted. Without this register/notify mechanism, there's no way to know when it's "safe" to emit the events.
I made it a bit generic, because I thought that there could be other uses for this. However, there's the YAGNI school of thought, which might suggest that I remove this and just explicitly call a
flush
oremitNow
method on the InitEvents instead. I don't feel strongly either way.