Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions android-agent/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ dependencies {
api(project(":core"))
implementation(project(":common"))
implementation(libs.opentelemetry.instrumentation.api)
implementation(libs.opentelemetry.exporter.otlp)

// Default instrumentations:
api(project(":instrumentation:activity"))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.android.agent

import android.app.Application
import io.opentelemetry.android.OpenTelemetryRum
import io.opentelemetry.android.OpenTelemetryRumBuilder
import io.opentelemetry.android.agent.connectivity.EndpointConnectivity
import io.opentelemetry.android.agent.connectivity.HttpEndpointConnectivity
import io.opentelemetry.android.config.OtelRumConfig
import io.opentelemetry.exporter.otlp.http.logs.OtlpHttpLogRecordExporter
import io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporter
import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter

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 rumConfig Configuration used by [OpenTelemetryRumBuilder].
*/
@JvmStatic
fun initialize(
application: Application,
endpointBaseUrl: String,
endpointHeaders: Map<String, String> = emptyMap(),
spanEndpointConnectivity: EndpointConnectivity =
HttpEndpointConnectivity.forTraces(
endpointBaseUrl,
endpointHeaders,
),
logEndpointConnectivity: EndpointConnectivity =
HttpEndpointConnectivity.forLogs(
endpointBaseUrl,
endpointHeaders,
),
metricEndpointConnectivity: EndpointConnectivity =
HttpEndpointConnectivity.forMetrics(
endpointBaseUrl,
endpointHeaders,
),
rumConfig: OtelRumConfig = OtelRumConfig(),
): OpenTelemetryRum =
OpenTelemetryRum
.builder(application, rumConfig)
.addSpanExporterCustomizer {
OtlpHttpSpanExporter
.builder()
.setEndpoint(spanEndpointConnectivity.getUrl())
.setHeaders(spanEndpointConnectivity::getHeaders)
.build()
}.addLogRecordExporterCustomizer {
OtlpHttpLogRecordExporter
.builder()
.setEndpoint(logEndpointConnectivity.getUrl())
.setHeaders(logEndpointConnectivity::getHeaders)
.build()
}.addMetricExporterCustomizer {
OtlpHttpMetricExporter
.builder()
.setEndpoint(metricEndpointConnectivity.getUrl())
.setHeaders(metricEndpointConnectivity::getHeaders)
.build()
}.build()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.android.agent.connectivity

interface EndpointConnectivity {
fun getUrl(): String

fun getHeaders(): Map<String, String>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.android.agent.connectivity

class HttpEndpointConnectivity private constructor(
private val url: String,
private val headers: Map<String, String>,
) : EndpointConnectivity {
companion object {
fun forTraces(
baseUrl: String,
headers: Map<String, String> = emptyMap(),
): HttpEndpointConnectivity = HttpEndpointConnectivity(baseUrl.trimEnd('/') + "/v1/traces", headers)

fun forLogs(
baseUrl: String,
headers: Map<String, String> = emptyMap(),
): HttpEndpointConnectivity = HttpEndpointConnectivity(baseUrl.trimEnd('/') + "/v1/logs", headers)

fun forMetrics(
baseUrl: String,
headers: Map<String, String> = emptyMap(),
): HttpEndpointConnectivity = HttpEndpointConnectivity(baseUrl.trimEnd('/') + "/v1/metrics", headers)
}

override fun getUrl(): String = url

override fun getHeaders(): Map<String, String> = headers
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.android.agent.connectivity

import org.assertj.core.api.Assertions.assertThat
import org.junit.Test

class HttpEndpointConnectivityTest {
@Test
fun `Validate exporter endpoint configuration`() {
val headers = mapOf("Authorization" to "Basic something")
val tracesConnectivity = HttpEndpointConnectivity.forTraces("http://some.endpoint", headers)
val logsConnectivity = HttpEndpointConnectivity.forLogs("http://some.endpoint/", headers)
val metricsConnectivity =
HttpEndpointConnectivity.forMetrics("http://some.endpoint", headers)

assertThat(tracesConnectivity.getUrl()).isEqualTo("http://some.endpoint/v1/traces")
assertThat(tracesConnectivity.getHeaders()).isEqualTo(headers)
assertThat(logsConnectivity.getUrl()).isEqualTo("http://some.endpoint/v1/logs")
assertThat(logsConnectivity.getHeaders()).isEqualTo(headers)
assertThat(metricsConnectivity.getUrl()).isEqualTo("http://some.endpoint/v1/metrics")
assertThat(metricsConnectivity.getHeaders()).isEqualTo(headers)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -570,19 +570,16 @@ private SdkLoggerProvider buildLoggerProvider(
}

private SpanExporter buildSpanExporter() {
// TODO: Default to otlp...but how can we make endpoint and auth mandatory?
SpanExporter defaultExporter = LoggingSpanExporter.create();
return spanExporterCustomizer.apply(defaultExporter);
}

private MetricExporter buildMetricExporter() {
// TODO: Default to otlp...but how can we make endpoint and auth mandatory?
MetricExporter defaultExporter = LoggingMetricExporter.create();
return metricExporterCustomizer.apply(defaultExporter);
}

private LogRecordExporter buildLogsExporter() {
// TODO: Default to otlp...but how can we make endpoint and auth mandatory?
LogRecordExporter defaultExporter = SystemOutLogRecordExporter.create();
return logRecordExporterCustomizer.apply(defaultExporter);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,15 @@ import android.annotation.SuppressLint
import android.app.Application
import android.util.Log
import io.opentelemetry.android.OpenTelemetryRum
import io.opentelemetry.android.OpenTelemetryRumBuilder
import io.opentelemetry.android.agent.OpenTelemetryRumInitializer
import io.opentelemetry.android.config.OtelRumConfig
import io.opentelemetry.android.features.diskbuffering.DiskBufferingConfig
import io.opentelemetry.android.instrumentation.sessions.SessionInstrumentation
import io.opentelemetry.api.common.AttributeKey.stringKey
import io.opentelemetry.api.common.Attributes
import io.opentelemetry.api.incubator.logs.ExtendedLogRecordBuilder
import io.opentelemetry.api.logs.LogRecordBuilder
import io.opentelemetry.api.metrics.LongCounter
import io.opentelemetry.api.trace.Tracer
import io.opentelemetry.exporter.otlp.http.logs.OtlpHttpLogRecordExporter
import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter
import io.opentelemetry.exporter.otlp.logs.OtlpGrpcLogRecordExporter
import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter

Expand All @@ -40,24 +37,8 @@ class OtelDemoApplication : Application() {
.setDiskBufferingConfig(diskBufferingConfig)

// 10.0.2.2 is apparently a special binding to the host running the emulator
val spansIngestUrl = "http://10.0.2.2:4318/v1/traces"
val logsIngestUrl = "http://10.0.2.2:4318/v1/logs"
val otelRumBuilder: OpenTelemetryRumBuilder =
OpenTelemetryRum.builder(this, config)
.addSpanExporterCustomizer {
OtlpHttpSpanExporter.builder()
.setEndpoint(spansIngestUrl)
.build()
}
.addLogRecordExporterCustomizer {
OtlpHttpLogRecordExporter.builder()
.setEndpoint(logsIngestUrl)
.build()
}
// TODO: This should NOT be necessary if it's in the runtime classpath...
.addInstrumentation(SessionInstrumentation())
try {
rum = otelRumBuilder.build()
rum = OpenTelemetryRumInitializer.initialize(this, "http://10.0.2.2:4318")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OpenTelemetryRumInitializer is an object (singleton) but looks like it should be just a function or extension?
the single class holds no state or anything.

@LikeTheSalad LikeTheSalad Apr 17, 2025

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right. The reason I went with an object is to try and provide a consistent experience for users calling it either from Kotlin or Java code. Though I'm open to other approaches. If we turned it into a standalone function, for example, would it be a way for people from Java to still call it this way: OpenTelemetryRumInitializer.initialize(...)?

We could also decide not to think/worry about Java usage apis, though I'd like to get some feedback on whether that could cause issues or not.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as a kotlin object, i think Java users have to do eg OpenTelemetryRumInitializer.INSTANCE.initialize
I think for OpenTelemetryRumInitializer.initialize you'd need a @JvmStatic annotation within a companion object

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True, though it seems to work with objects with @JvmStatic annotations too, without having to be inside a companion object (at least that was the case when I tried it locally).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah i was not sure about being within the companion object anyway, if it works, all good

Log.d(TAG, "RUM session started: " + rum!!.rumSessionId)
} catch (e: Exception) {
Log.e(TAG, "Oh no!", e)
Expand Down