diff --git a/android-agent/api/android-agent.api b/android-agent/api/android-agent.api index b9ea6249a..f534f0165 100644 --- a/android-agent/api/android-agent.api +++ b/android-agent/api/android-agent.api @@ -33,9 +33,11 @@ public final class io/opentelemetry/android/agent/dsl/DiskBufferingConfiguration public final class io/opentelemetry/android/agent/dsl/EndpointConfiguration { public final fun getCompression ()Lio/opentelemetry/android/agent/connectivity/Compression; + public final fun getFullUrl ()Ljava/lang/String; public final fun getHeaders ()Ljava/util/Map; public final fun getUrl ()Ljava/lang/String; public final fun setCompression (Lio/opentelemetry/android/agent/connectivity/Compression;)V + public final fun setFullUrl (Ljava/lang/String;)V public final fun setHeaders (Ljava/util/Map;)V public final fun setUrl (Ljava/lang/String;)V } diff --git a/android-agent/src/main/kotlin/io/opentelemetry/android/agent/connectivity/HttpEndpointConnectivity.kt b/android-agent/src/main/kotlin/io/opentelemetry/android/agent/connectivity/HttpEndpointConnectivity.kt index 5540b7260..a5ff19142 100644 --- a/android-agent/src/main/kotlin/io/opentelemetry/android/agent/connectivity/HttpEndpointConnectivity.kt +++ b/android-agent/src/main/kotlin/io/opentelemetry/android/agent/connectivity/HttpEndpointConnectivity.kt @@ -2,7 +2,6 @@ * Copyright The OpenTelemetry Authors * SPDX-License-Identifier: Apache-2.0 */ - package io.opentelemetry.android.agent.connectivity internal class HttpEndpointConnectivity private constructor( @@ -10,59 +9,61 @@ internal class HttpEndpointConnectivity private constructor( private val headers: Map, private val compression: Compression, private val sslContext: SSLContextConnectivity?, - private val clientTls: ClientTlsConnectivity? = null + private val clientTls: ClientTlsConnectivity? = null, ) : EndpointConnectivity { companion object { fun forTraces( baseUrl: String, + fullUrl: Boolean = false, headers: Map, compression: Compression, sslContext: SSLContextConnectivity?, - clientTls: ClientTlsConnectivity? - ): HttpEndpointConnectivity = HttpEndpointConnectivity( - baseUrl.trimEnd('/') + "/v1/traces", - headers, - compression, - sslContext, - clientTls - ) + clientTls: ClientTlsConnectivity?, + ): HttpEndpointConnectivity = + HttpEndpointConnectivity( + if (fullUrl) baseUrl else baseUrl.trimEnd('/') + "/v1/traces", + headers, + compression, + sslContext, + clientTls, + ) fun forLogs( baseUrl: String, + fullUrl: Boolean = false, headers: Map, compression: Compression, sslContext: SSLContextConnectivity?, - clientTls: ClientTlsConnectivity? - ): HttpEndpointConnectivity = HttpEndpointConnectivity( - baseUrl.trimEnd('/') + "/v1/logs", - headers, - compression, - sslContext, - clientTls - ) + clientTls: ClientTlsConnectivity?, + ): HttpEndpointConnectivity = + HttpEndpointConnectivity( + if (fullUrl) baseUrl else baseUrl.trimEnd('/') + "/v1/logs", + headers, + compression, + sslContext, + clientTls, + ) fun forMetrics( baseUrl: String, + fullUrl: Boolean = false, headers: Map, compression: Compression, sslContext: SSLContextConnectivity?, - clientTls: ClientTlsConnectivity? - ): HttpEndpointConnectivity = HttpEndpointConnectivity( - baseUrl.trimEnd('/') + "/v1/metrics", - headers, - compression, - sslContext, - clientTls - ) + clientTls: ClientTlsConnectivity?, + ): HttpEndpointConnectivity = + HttpEndpointConnectivity( + if (fullUrl) baseUrl else baseUrl.trimEnd('/') + "/v1/metrics", + headers, + compression, + sslContext, + clientTls, + ) } override fun getUrl(): String = url - override fun getHeaders(): Map = headers - override fun getCompression(): Compression = compression - override fun getSslContext(): SSLContextConnectivity? = sslContext - override fun getClientTls(): ClientTlsConnectivity? = clientTls } 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 index c925ac651..8f95ce7d7 100644 --- 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 @@ -2,7 +2,6 @@ * Copyright The OpenTelemetry Authors * SPDX-License-Identifier: Apache-2.0 */ - package io.opentelemetry.android.agent.dsl import io.opentelemetry.android.agent.connectivity.Compression @@ -13,9 +12,17 @@ import io.opentelemetry.android.agent.connectivity.Compression @OpenTelemetryDslMarker class EndpointConfiguration internal constructor( /** - * URL for HTTP export requests. + * Base URL for HTTP export requests. The signal-specific path (e.g. /v1/logs) will be + * appended automatically. */ var url: String, + /** + * Full URL for HTTP export requests. When set, this URL is used as-is without appending + * any signal-specific path. Use this to specify a completely custom endpoint path, + * for example "https://example.com/v2/logs". + * Note: when set, this overrides the baseUrl from the surrounding httpExport block entirely. + */ + var fullUrl: String? = null, /** * Headers that should be attached to HTTP export requests. */ 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 index 6672d06be..344249f9b 100644 --- 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 @@ -2,7 +2,6 @@ * Copyright The OpenTelemetry Authors * SPDX-License-Identifier: Apache-2.0 */ - package io.opentelemetry.android.agent.dsl import io.opentelemetry.android.Incubating @@ -50,33 +49,40 @@ class HttpExportConfiguration internal constructor() { internal fun spansEndpoint(): HttpEndpointConnectivity = HttpEndpointConnectivity.forTraces( chooseUrlSource(spansConfig), + isFullUrl(spansConfig), spansConfig.headers + baseHeaders, chooseCompression(spansConfig.compression), sslContext, - clientTls + clientTls, ) internal fun logsEndpoint(): HttpEndpointConnectivity = HttpEndpointConnectivity.forLogs( chooseUrlSource(logsConfig), + isFullUrl(logsConfig), logsConfig.headers + baseHeaders, chooseCompression(logsConfig.compression), sslContext, - clientTls + clientTls, ) internal fun metricsEndpoint(): HttpEndpointConnectivity = HttpEndpointConnectivity.forMetrics( chooseUrlSource(metricsConfig), + isFullUrl(metricsConfig), metricsConfig.headers + baseHeaders, chooseCompression(metricsConfig.compression), sslContext, - clientTls + clientTls, ) - private fun chooseUrlSource(cfg: EndpointConfiguration): String = cfg.url.ifBlank { baseUrl } + private fun chooseUrlSource(cfg: EndpointConfiguration): String = + cfg.fullUrl ?: cfg.url.ifBlank { baseUrl } + + private fun isFullUrl(cfg: EndpointConfiguration): Boolean = cfg.fullUrl != null - private fun chooseCompression(signalConfigCompression: Compression?): Compression = signalConfigCompression ?: this.compression + private fun chooseCompression(signalConfigCompression: Compression?): Compression = + signalConfigCompression ?: this.compression /** * Override the default configuration for the v1/traces endpoint only. diff --git a/android-agent/src/test/kotlin/io/opentelemetry/android/agent/connectivity/HttpEndpointConnectivityTest.kt b/android-agent/src/test/kotlin/io/opentelemetry/android/agent/connectivity/HttpEndpointConnectivityTest.kt index 131b7e3e9..8d09b5f37 100644 --- a/android-agent/src/test/kotlin/io/opentelemetry/android/agent/connectivity/HttpEndpointConnectivityTest.kt +++ b/android-agent/src/test/kotlin/io/opentelemetry/android/agent/connectivity/HttpEndpointConnectivityTest.kt @@ -17,11 +17,11 @@ class HttpEndpointConnectivityTest { val sslContext = SSLContextConnectivity(mockk(), mockk()) val clientTls: ClientTlsConnectivity = mockk() val tracesConnectivity = - HttpEndpointConnectivity.forTraces("http://some.endpoint", headers, compression, sslContext, clientTls) + HttpEndpointConnectivity.forTraces("http://some.endpoint", false, headers, compression, sslContext, clientTls) val logsConnectivity = - HttpEndpointConnectivity.forLogs("http://some.endpoint/", headers, compression, sslContext, clientTls) + HttpEndpointConnectivity.forLogs("http://some.endpoint/", false, headers, compression, sslContext, clientTls) val metricsConnectivity = - HttpEndpointConnectivity.forMetrics("http://some.endpoint", headers, compression, sslContext, clientTls) + HttpEndpointConnectivity.forMetrics("http://some.endpoint", false, headers, compression, sslContext, clientTls) assertThat(tracesConnectivity.getUrl()).isEqualTo("http://some.endpoint/v1/traces") assertThat(tracesConnectivity.getHeaders()).isEqualTo(headers) diff --git a/android-agent/src/test/kotlin/io/opentelemetry/android/agent/dsl/HttpExportConfigurationTest.kt b/android-agent/src/test/kotlin/io/opentelemetry/android/agent/dsl/HttpExportConfigurationTest.kt index b946cbabf..9e7492448 100644 --- a/android-agent/src/test/kotlin/io/opentelemetry/android/agent/dsl/HttpExportConfigurationTest.kt +++ b/android-agent/src/test/kotlin/io/opentelemetry/android/agent/dsl/HttpExportConfigurationTest.kt @@ -23,7 +23,7 @@ internal class HttpExportConfigurationTest { fun setUp() { otelConfig = OpenTelemetryConfiguration( clock = FakeClock(), - instrumentationLoader = FakeInstrumentationLoader() + instrumentationLoader = FakeInstrumentationLoader(), ) } @@ -41,14 +41,14 @@ internal class HttpExportConfigurationTest { expectedHeaders, expectedCompression, expectedSslContext, - expectedClientTls + expectedClientTls, ) config.logsEndpoint().assertEndpointConfig( "/v1/logs", expectedHeaders, expectedCompression, expectedSslContext, - expectedClientTls + expectedClientTls, ) config .metricsEndpoint() @@ -57,7 +57,7 @@ internal class HttpExportConfigurationTest { expectedHeaders, expectedCompression, expectedSslContext, - expectedClientTls + expectedClientTls, ) assertEquals("", config.baseUrl) assertEquals(expectedHeaders, config.baseHeaders) @@ -77,7 +77,8 @@ internal class HttpExportConfigurationTest { config.spansEndpoint() .assertEndpointConfig("${url}v1/traces", headers, Compression.GZIP, null, null) - config.logsEndpoint().assertEndpointConfig("${url}v1/logs", headers, Compression.GZIP, null, null) + config.logsEndpoint() + .assertEndpointConfig("${url}v1/logs", headers, Compression.GZIP, null, null) config.metricsEndpoint() .assertEndpointConfig("${url}v1/metrics", headers, Compression.GZIP, null, null) assertEquals(url, config.baseUrl) @@ -88,23 +89,18 @@ internal class HttpExportConfigurationTest { fun testIndividualEndpointOverrides() { val baseUrl = "http://localhost:4318/" val baseHeaders = mapOf("my-header" to "my-value") - val spanUrl = "http://localhost:4318/spans/" val spanHeaders = mapOf("span-header" to "span-value") - val logUrl = "http://localhost:4318/logs/" val logHeaders = mapOf("log-header" to "log-value") - val metricsUrl = "http://localhost:4318/metrics/" val metricsHeaders = mapOf("metrics-header" to "metrics-value") - val expectedCompression = Compression.NONE val config = otelConfig.exportConfig.apply { this.baseUrl = baseUrl this.baseHeaders = baseHeaders - spans { url = spanUrl headers = spanHeaders @@ -128,7 +124,7 @@ internal class HttpExportConfigurationTest { "${spanUrl}v1/traces", spanHeaders + baseHeaders, expectedCompression, - null + null, ) config .logsEndpoint() @@ -136,13 +132,13 @@ internal class HttpExportConfigurationTest { "${logUrl}v1/logs", logHeaders + baseHeaders, expectedCompression, - null + null, ) config.metricsEndpoint().assertEndpointConfig( "${metricsUrl}v1/metrics", metricsHeaders + baseHeaders, expectedCompression, - null + null, ) assertEquals(baseUrl, config.baseUrl) assertEquals(baseHeaders, config.baseHeaders) @@ -152,16 +148,12 @@ internal class HttpExportConfigurationTest { fun testIndividualEndpointOverrides2() { val baseUrl = "http://localhost:4318/" val baseHeaders = mapOf("my-header" to "my-value") - val spanUrl = "http://localhost:4318/spans/" val spanHeaders = mapOf("span-header" to "span-value") - val logUrl = "http://localhost:4318/logs/" val logHeaders = mapOf("log-header" to "log-value") - val metricsUrl = "http://localhost:4318/metrics/" val metricsHeaders = mapOf("metrics-header" to "metrics-value") - val expectedCompression = Compression.GZIP val config = @@ -178,9 +170,6 @@ internal class HttpExportConfigurationTest { url = metricsUrl headers = metricsHeaders } - - // altering base values after setting individual endpoints should give same result as - // when setting base values before. this.baseUrl = baseUrl this.baseHeaders = baseHeaders } @@ -191,7 +180,7 @@ internal class HttpExportConfigurationTest { "${spanUrl}v1/traces", spanHeaders + baseHeaders, expectedCompression, - null + null, ) config .logsEndpoint() @@ -199,13 +188,13 @@ internal class HttpExportConfigurationTest { "${logUrl}v1/logs", logHeaders + baseHeaders, expectedCompression, - null + null, ) config.metricsEndpoint().assertEndpointConfig( "${metricsUrl}v1/metrics", metricsHeaders + baseHeaders, expectedCompression, - null + null, ) assertEquals(baseUrl, config.baseUrl) assertEquals(baseHeaders, config.baseHeaders) @@ -224,26 +213,11 @@ internal class HttpExportConfigurationTest { } config.spansEndpoint() - .assertEndpointConfig( - "${url}v1/traces", - headers, - Compression.GZIP, - expectedSslContext - ) + .assertEndpointConfig("${url}v1/traces", headers, Compression.GZIP, expectedSslContext) config.logsEndpoint() - .assertEndpointConfig( - "${url}v1/logs", - headers, - Compression.GZIP, - expectedSslContext - ) + .assertEndpointConfig("${url}v1/logs", headers, Compression.GZIP, expectedSslContext) config.metricsEndpoint() - .assertEndpointConfig( - "${url}v1/metrics", - headers, - Compression.GZIP, - expectedSslContext - ) + .assertEndpointConfig("${url}v1/metrics", headers, Compression.GZIP, expectedSslContext) } @Test @@ -259,29 +233,11 @@ internal class HttpExportConfigurationTest { } config.spansEndpoint() - .assertEndpointConfig( - "${url}v1/traces", - headers, - Compression.GZIP, - null, - expectedClientTls - ) + .assertEndpointConfig("${url}v1/traces", headers, Compression.GZIP, null, expectedClientTls) config.logsEndpoint() - .assertEndpointConfig( - "${url}v1/logs", - headers, - Compression.GZIP, - null, - expectedClientTls - ) + .assertEndpointConfig("${url}v1/logs", headers, Compression.GZIP, null, expectedClientTls) config.metricsEndpoint() - .assertEndpointConfig( - "${url}v1/metrics", - headers, - Compression.GZIP, - null, - expectedClientTls - ) + .assertEndpointConfig("${url}v1/metrics", headers, Compression.GZIP, null, expectedClientTls) } private fun HttpEndpointConnectivity.assertEndpointConfig( @@ -289,7 +245,7 @@ internal class HttpExportConfigurationTest { expectedHeaders: Map, expectedCompression: Compression, expectedSslContext: SSLContextConnectivity?, - expectedClientTls: ClientTlsConnectivity? = null + expectedClientTls: ClientTlsConnectivity? = null, ) { assertEquals(expectedUrl, getUrl()) assertEquals(expectedHeaders, getHeaders()) @@ -316,4 +272,57 @@ internal class HttpExportConfigurationTest { return ClientTlsConnectivity(privateKeyPem, certificatePem) } + @Test + fun testFullUrlOverrideForLogs() { + val baseUrl = "http://localhost:4318/" + val customLogsUrl = "http://localhost:4318/v2/logs" + + val config = + otelConfig.exportConfig.apply { + this.baseUrl = baseUrl + logs { + fullUrl = customLogsUrl + } + } + + config.logsEndpoint().assertEndpointConfig(customLogsUrl, emptyMap(), Compression.GZIP, null) + config.spansEndpoint().assertEndpointConfig("${baseUrl}v1/traces", emptyMap(), Compression.GZIP, null) + config.metricsEndpoint().assertEndpointConfig("${baseUrl}v1/metrics", emptyMap(), Compression.GZIP, null) + } + + @Test + fun testFullUrlOverrideForAllSignals() { + val customSpansUrl = "http://traces.example.com/v2/traces" + val customLogsUrl = "http://logs.example.com/v2/logs" + val customMetricsUrl = "http://metrics.example.com/v2/metrics" + + val config = + otelConfig.exportConfig.apply { + spans { fullUrl = customSpansUrl } + logs { fullUrl = customLogsUrl } + metrics { fullUrl = customMetricsUrl } + } + + config.spansEndpoint().assertEndpointConfig(customSpansUrl, emptyMap(), Compression.GZIP, null) + config.logsEndpoint().assertEndpointConfig(customLogsUrl, emptyMap(), Compression.GZIP, null) + config.metricsEndpoint().assertEndpointConfig(customMetricsUrl, emptyMap(), Compression.GZIP, null) + } + + @Test + fun testFullUrlTakesPrecedenceOverUrl() { + val baseUrl = "http://localhost:4318/" + val signalUrl = "http://localhost:4318/logs/" + val customFullUrl = "http://localhost:4318/v2/logs" + + val config = + otelConfig.exportConfig.apply { + this.baseUrl = baseUrl + logs { + url = signalUrl + fullUrl = customFullUrl + } + } + + config.logsEndpoint().assertEndpointConfig(customFullUrl, emptyMap(), Compression.GZIP, null) + } }