From d99d6a6b3668a47834c95c6ddcb9f9a48550efa6 Mon Sep 17 00:00:00 2001 From: Jamie Lynch Date: Mon, 6 Oct 2025 13:07:45 +0100 Subject: [PATCH] wip: demonstrate type-safe dsl for initializing opentelemetry --- android-agent/api/android-agent.api | 36 ++++++++++- .../agent/OpenTelemetryRumInitializer.kt | 60 ++++++------------- .../agent/dsl/EndpointConfiguration.kt | 12 ++++ .../agent/dsl/HttpExportConfiguration.kt | 57 ++++++++++++++++++ .../agent/dsl/OpenTelemetryConfiguration.kt | 50 ++++++++++++++++ .../android/agent/dsl/SessionConfiguration.kt | 16 +++++ .../agent/OpenTelemetryRumInitializerTest.kt | 9 +-- .../android/demo/OtelDemoApplication.kt | 13 ++-- 8 files changed, 202 insertions(+), 51 deletions(-) create mode 100644 android-agent/src/main/kotlin/io/opentelemetry/android/agent/dsl/EndpointConfiguration.kt create mode 100644 android-agent/src/main/kotlin/io/opentelemetry/android/agent/dsl/HttpExportConfiguration.kt create mode 100644 android-agent/src/main/kotlin/io/opentelemetry/android/agent/dsl/OpenTelemetryConfiguration.kt create mode 100644 android-agent/src/main/kotlin/io/opentelemetry/android/agent/dsl/SessionConfiguration.kt diff --git a/android-agent/api/android-agent.api b/android-agent/api/android-agent.api index c5ff0c7a0..53f63c296 100644 --- a/android-agent/api/android-agent.api +++ b/android-agent/api/android-agent.api @@ -3,8 +3,8 @@ public abstract interface annotation class io/opentelemetry/android/Incubating : public final class io/opentelemetry/android/agent/OpenTelemetryRumInitializer { public static final field INSTANCE Lio/opentelemetry/android/agent/OpenTelemetryRumInitializer; - public static final fun initialize (Landroid/app/Application;Ljava/lang/String;Ljava/util/Map;Lio/opentelemetry/android/agent/connectivity/EndpointConnectivity;Lio/opentelemetry/android/agent/connectivity/EndpointConnectivity;Lio/opentelemetry/android/agent/connectivity/EndpointConnectivity;Lio/opentelemetry/android/agent/session/SessionConfig;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Lio/opentelemetry/android/OpenTelemetryRum; - public static synthetic fun initialize$default (Landroid/app/Application;Ljava/lang/String;Ljava/util/Map;Lio/opentelemetry/android/agent/connectivity/EndpointConnectivity;Lio/opentelemetry/android/agent/connectivity/EndpointConnectivity;Lio/opentelemetry/android/agent/connectivity/EndpointConnectivity;Lio/opentelemetry/android/agent/session/SessionConfig;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lio/opentelemetry/android/OpenTelemetryRum; + public static final fun initialize (Landroid/app/Application;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Lio/opentelemetry/android/OpenTelemetryRum; + public static synthetic fun initialize$default (Landroid/app/Application;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lio/opentelemetry/android/OpenTelemetryRum; } public abstract interface class io/opentelemetry/android/agent/connectivity/EndpointConnectivity { @@ -32,6 +32,38 @@ public final class io/opentelemetry/android/agent/dsl/DiskBufferingConfiguration public fun enabled (Z)V } +public final class io/opentelemetry/android/agent/dsl/EndpointConfiguration { + public final fun getHeaders ()Ljava/util/Map; + public final fun getUrl ()Ljava/lang/String; + public final fun setHeaders (Ljava/util/Map;)V + public final fun setUrl (Ljava/lang/String;)V +} + +public final class io/opentelemetry/android/agent/dsl/HttpExportConfiguration { + public final fun getBaseHeaders ()Ljava/util/Map; + public final fun getBaseUrl ()Ljava/lang/String; + public final fun logs (Lkotlin/jvm/functions/Function1;)V + public final fun metrics (Lkotlin/jvm/functions/Function1;)V + public final fun setBaseHeaders (Ljava/util/Map;)V + public final fun setBaseUrl (Ljava/lang/String;)V + public final fun spans (Lkotlin/jvm/functions/Function1;)V +} + +public final class io/opentelemetry/android/agent/dsl/OpenTelemetryConfiguration { + public fun ()V + public final fun httpExport (Lkotlin/jvm/functions/Function1;)V + public final fun instrumentations (Lkotlin/jvm/functions/Function1;)V + public final fun session (Lkotlin/jvm/functions/Function1;)V +} + +public final class io/opentelemetry/android/agent/dsl/SessionConfiguration { + public synthetic fun (JJILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun getBackgroundInactivityTimeout-UwyO8pc ()J + public final fun getMaxLifetime-UwyO8pc ()J + public final fun setBackgroundInactivityTimeout-LRDsOJo (J)V + public final fun setMaxLifetime-LRDsOJo (J)V +} + public final class io/opentelemetry/android/agent/dsl/instrumentation/ActivityLifecycleConfiguration : io/opentelemetry/android/agent/dsl/instrumentation/CanBeEnabledAndDisabled, io/opentelemetry/android/agent/dsl/instrumentation/ScreenLifecycleConfigurable { public fun enabled (Z)V public fun screenNameExtractor (Lio/opentelemetry/android/instrumentation/common/ScreenNameExtractor;)V diff --git a/android-agent/src/main/kotlin/io/opentelemetry/android/agent/OpenTelemetryRumInitializer.kt b/android-agent/src/main/kotlin/io/opentelemetry/android/agent/OpenTelemetryRumInitializer.kt index 423f4136c..1d4153c2d 100644 --- a/android-agent/src/main/kotlin/io/opentelemetry/android/agent/OpenTelemetryRumInitializer.kt +++ b/android-agent/src/main/kotlin/io/opentelemetry/android/agent/OpenTelemetryRumInitializer.kt @@ -8,10 +8,8 @@ package io.opentelemetry.android.agent import android.app.Application import io.opentelemetry.android.Incubating import io.opentelemetry.android.OpenTelemetryRum -import io.opentelemetry.android.agent.connectivity.EndpointConnectivity -import io.opentelemetry.android.agent.connectivity.HttpEndpointConnectivity import io.opentelemetry.android.agent.dsl.DiskBufferingConfigurationSpec -import io.opentelemetry.android.agent.dsl.instrumentation.InstrumentationConfiguration +import io.opentelemetry.android.agent.dsl.OpenTelemetryConfiguration import io.opentelemetry.android.agent.session.SessionConfig import io.opentelemetry.android.agent.session.SessionIdTimeoutHandler import io.opentelemetry.android.agent.session.SessionManager @@ -30,47 +28,21 @@ object OpenTelemetryRumInitializer { * Opinionated [OpenTelemetryRum] initialization. * * @param application Your android app's application object. - * @param endpointBaseUrl The base endpoint for exporting all your signals. - * @param endpointHeaders These will be added to each signal export request. - * @param spanEndpointConnectivity Span-specific endpoint configuration. - * @param logEndpointConnectivity Log-specific endpoint configuration. - * @param metricEndpointConnectivity Metric-specific endpoint configuration. - * @param sessionConfig The session configuration, which includes inactivity timeout and maximum lifetime durations. * @param globalAttributes Configures the set of global attributes to emit with every span and event. * @param diskBuffering Configures the disk buffering feature. - * @param instrumentations Configurations for all the default instrumentations. + * @param configuration Type-safe config DSL that controls how OpenTelemetry + * should behave. */ - @Suppress("LongParameterList") @JvmStatic fun initialize( application: Application, - endpointBaseUrl: String, - endpointHeaders: Map = emptyMap(), - spanEndpointConnectivity: EndpointConnectivity = - HttpEndpointConnectivity.forTraces( - endpointBaseUrl, - endpointHeaders, - ), - logEndpointConnectivity: EndpointConnectivity = - HttpEndpointConnectivity.forLogs( - endpointBaseUrl, - endpointHeaders, - ), - metricEndpointConnectivity: EndpointConnectivity = - HttpEndpointConnectivity.forMetrics( - endpointBaseUrl, - endpointHeaders, - ), - sessionConfig: SessionConfig = SessionConfig.withDefaults(), globalAttributes: (() -> Attributes)? = null, diskBuffering: (DiskBufferingConfigurationSpec.() -> Unit)? = null, - instrumentations: (InstrumentationConfiguration.() -> Unit)? = null, + configuration: (OpenTelemetryConfiguration.() -> Unit) = {}, ): OpenTelemetryRum { val rumConfig = OtelRumConfig() - - instrumentations?.let { configure -> - InstrumentationConfiguration(rumConfig).configure() - } + val cfg = OpenTelemetryConfiguration(rumConfig) + configuration(cfg) val diskBufferingConfigurationSpec = DiskBufferingConfigurationSpec() diskBuffering?.invoke(diskBufferingConfigurationSpec) @@ -79,26 +51,32 @@ object OpenTelemetryRumInitializer { globalAttributes?.let { rumConfig.setGlobalAttributes(it::invoke) } + + val sessionConfig = + SessionConfig( + cfg.sessionConfig.backgroundInactivityTimeout, + cfg.sessionConfig.maxLifetime, + ) return OpenTelemetryRum - .builder(application, rumConfig) + .builder(application, cfg.rumConfig) .setSessionProvider(createSessionProvider(application, sessionConfig)) .addSpanExporterCustomizer { OtlpHttpSpanExporter .builder() - .setEndpoint(spanEndpointConnectivity.getUrl()) - .setHeaders(spanEndpointConnectivity::getHeaders) + .setEndpoint(cfg.exportConfig.spansConfig.url) + .setHeaders(cfg.exportConfig.spansConfig::headers) .build() }.addLogRecordExporterCustomizer { OtlpHttpLogRecordExporter .builder() - .setEndpoint(logEndpointConnectivity.getUrl()) - .setHeaders(logEndpointConnectivity::getHeaders) + .setEndpoint(cfg.exportConfig.logsConfig.url) + .setHeaders(cfg.exportConfig.logsConfig::headers) .build() }.addMetricExporterCustomizer { OtlpHttpMetricExporter .builder() - .setEndpoint(metricEndpointConnectivity.getUrl()) - .setHeaders(metricEndpointConnectivity::getHeaders) + .setEndpoint(cfg.exportConfig.metricsConfig.url) + .setHeaders(cfg.exportConfig.metricsConfig::headers) .build() }.build() } diff --git a/android-agent/src/main/kotlin/io/opentelemetry/android/agent/dsl/EndpointConfiguration.kt b/android-agent/src/main/kotlin/io/opentelemetry/android/agent/dsl/EndpointConfiguration.kt new file mode 100644 index 000000000..93f60e84f --- /dev/null +++ b/android-agent/src/main/kotlin/io/opentelemetry/android/agent/dsl/EndpointConfiguration.kt @@ -0,0 +1,12 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.android.agent.dsl + +@OpenTelemetryDslMarker +class EndpointConfiguration internal constructor( + var url: String, + var headers: Map = emptyMap(), +) diff --git a/android-agent/src/main/kotlin/io/opentelemetry/android/agent/dsl/HttpExportConfiguration.kt b/android-agent/src/main/kotlin/io/opentelemetry/android/agent/dsl/HttpExportConfiguration.kt new file mode 100644 index 000000000..39a97ff65 --- /dev/null +++ b/android-agent/src/main/kotlin/io/opentelemetry/android/agent/dsl/HttpExportConfiguration.kt @@ -0,0 +1,57 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.android.agent.dsl + +import io.opentelemetry.android.agent.connectivity.HttpEndpointConnectivity + +@OpenTelemetryDslMarker +class HttpExportConfiguration internal constructor() { + /** + * Global URL for HTTP export requests. + */ + var baseUrl: String = "" + + /** + * Global headers that should be attached to any HTTP export requests. + */ + var baseHeaders: Map = emptyMap() + + internal val spansConfig: EndpointConfiguration = + HttpEndpointConnectivity + .forTraces( + baseUrl, + baseHeaders, + ).let(::toEndpointConfiguration) + + internal val logsConfig: EndpointConfiguration = + HttpEndpointConnectivity + .forLogs( + baseUrl, + baseHeaders, + ).let(::toEndpointConfiguration) + + internal val metricsConfig: EndpointConfiguration = + HttpEndpointConnectivity + .forMetrics( + baseUrl, + baseHeaders, + ).let(::toEndpointConfiguration) + + fun spans(action: EndpointConfiguration.() -> Unit) { + spansConfig.action() + } + + fun logs(action: EndpointConfiguration.() -> Unit) { + logsConfig.action() + } + + fun metrics(action: EndpointConfiguration.() -> Unit) { + metricsConfig.action() + } + + private fun toEndpointConfiguration(connectivity: HttpEndpointConnectivity): EndpointConfiguration = + EndpointConfiguration(connectivity.getUrl(), connectivity.getHeaders()) +} diff --git a/android-agent/src/main/kotlin/io/opentelemetry/android/agent/dsl/OpenTelemetryConfiguration.kt b/android-agent/src/main/kotlin/io/opentelemetry/android/agent/dsl/OpenTelemetryConfiguration.kt new file mode 100644 index 000000000..35c327610 --- /dev/null +++ b/android-agent/src/main/kotlin/io/opentelemetry/android/agent/dsl/OpenTelemetryConfiguration.kt @@ -0,0 +1,50 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.android.agent.dsl + +import io.opentelemetry.android.Incubating +import io.opentelemetry.android.agent.dsl.instrumentation.InstrumentationConfiguration +import io.opentelemetry.android.config.OtelRumConfig + +/** + * Type-safe config DSL that controls how OpenTelemetry should behave. + */ +@OptIn(Incubating::class) +@OpenTelemetryDslMarker +class OpenTelemetryConfiguration internal constructor( + internal val rumConfig: OtelRumConfig = OtelRumConfig(), +) { + internal val exportConfig: HttpExportConfiguration = HttpExportConfiguration() + + internal val sessionConfig: SessionConfiguration = SessionConfiguration() + + /** + * Configures individual instrumentations. + */ + internal val instrumentations: InstrumentationConfiguration = + InstrumentationConfiguration(rumConfig) + + /** + * Configures how OpenTelemetry should export telemetry over HTTP. + */ + fun httpExport(action: HttpExportConfiguration.() -> Unit) { + exportConfig.action() + } + + /** + * Configures individual instrumentations. + */ + fun instrumentations(action: InstrumentationConfiguration.() -> Unit) { + instrumentations.action() + } + + /** + * Configures session behavior. + */ + fun session(action: SessionConfiguration.() -> Unit) { + sessionConfig.action() + } +} diff --git a/android-agent/src/main/kotlin/io/opentelemetry/android/agent/dsl/SessionConfiguration.kt b/android-agent/src/main/kotlin/io/opentelemetry/android/agent/dsl/SessionConfiguration.kt new file mode 100644 index 000000000..25995c9f6 --- /dev/null +++ b/android-agent/src/main/kotlin/io/opentelemetry/android/agent/dsl/SessionConfiguration.kt @@ -0,0 +1,16 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.android.agent.dsl + +import kotlin.time.Duration +import kotlin.time.Duration.Companion.hours +import kotlin.time.Duration.Companion.minutes + +@OpenTelemetryDslMarker +class SessionConfiguration internal constructor( + var backgroundInactivityTimeout: Duration = 15.minutes, + var maxLifetime: Duration = 4.hours, +) diff --git a/android-agent/src/test/kotlin/io/opentelemetry/android/agent/OpenTelemetryRumInitializerTest.kt b/android-agent/src/test/kotlin/io/opentelemetry/android/agent/OpenTelemetryRumInitializerTest.kt index e223bf683..3dd3f0988 100644 --- a/android-agent/src/test/kotlin/io/opentelemetry/android/agent/OpenTelemetryRumInitializerTest.kt +++ b/android-agent/src/test/kotlin/io/opentelemetry/android/agent/OpenTelemetryRumInitializerTest.kt @@ -33,10 +33,11 @@ class OpenTelemetryRumInitializerTest { fun `Verify timeoutHandler initialization`() { createAndSetServiceManager() - OpenTelemetryRumInitializer.initialize( - RuntimeEnvironment.getApplication(), - "http://127.0.0.1:4318", - ) + OpenTelemetryRumInitializer.initialize(RuntimeEnvironment.getApplication()) { + httpExport { + baseUrl = "http://127.0.0.1:4318" + } + } verify { appLifecycle.registerListener(any()) diff --git a/demo-app/src/main/java/io/opentelemetry/android/demo/OtelDemoApplication.kt b/demo-app/src/main/java/io/opentelemetry/android/demo/OtelDemoApplication.kt index 2adf6a789..07e9f19c8 100644 --- a/demo-app/src/main/java/io/opentelemetry/android/demo/OtelDemoApplication.kt +++ b/demo-app/src/main/java/io/opentelemetry/android/demo/OtelDemoApplication.kt @@ -31,12 +31,16 @@ class OtelDemoApplication : Application() { Log.i(TAG, "Initializing the opentelemetry-android-agent") - // 10.0.2.2 is apparently a special binding to the host running the emulator + // 10.0.2.2 is a special binding to the host running the emulator try { rum = OpenTelemetryRumInitializer.initialize( application = this, - endpointBaseUrl = "http://10.0.2.2:4318", - globalAttributes = { Attributes.of(stringKey("toolkit"), "jetpack compose") } + globalAttributes = { Attributes.of(stringKey("toolkit"), "jetpack compose") }, + configuration = { + httpExport { + baseUrl = "http://10.0.2.2:4318" + } + } ) Log.d(TAG, "RUM session started: " + rum?.getRumSessionId()) } catch (e: Exception) { @@ -72,7 +76,8 @@ class OtelDemoApplication : Application() { } fun counter(name: String): LongCounter? { - return rum?.getOpenTelemetry()?.meterProvider?.get("demo.app")?.counterBuilder(name)?.build() + return rum?.getOpenTelemetry()?.meterProvider?.get("demo.app")?.counterBuilder(name) + ?.build() } fun eventBuilder(scopeName: String, eventName: String): LogRecordBuilder {