diff --git a/agent-sdk/build.gradle.kts b/agent-sdk/build.gradle.kts index ed34a4153..7bb970e50 100644 --- a/agent-sdk/build.gradle.kts +++ b/agent-sdk/build.gradle.kts @@ -34,6 +34,7 @@ dependencies { implementation(libs.androidx.annotations) implementation(libs.androidx.core) implementation(libs.dsl.json) + testImplementation(project(":internal-tools:otel-test-common")) testImplementation(libs.wireMock) testImplementation(libs.opentelemetry.testing) testImplementation(libs.robolectric) diff --git a/agent-sdk/metadata/agent-sdk.api b/agent-sdk/metadata/agent-sdk.api index 227336722..b3678f1d8 100644 --- a/agent-sdk/metadata/agent-sdk.api +++ b/agent-sdk/metadata/agent-sdk.api @@ -173,10 +173,10 @@ public final class co/elastic/otel/android/exporters/configuration/ExporterConfi } public final class co/elastic/otel/android/extensions/ElasticOtelAgentExtensionsKt { - public static final fun log (Lco/elastic/otel/android/api/ElasticOtelAgent;Ljava/lang/String;Lio/opentelemetry/api/logs/Severity;Ljava/lang/String;Lio/opentelemetry/api/common/Attributes;Lio/opentelemetry/context/Context;Ljava/time/Instant;Ljava/time/Instant;)V - public static synthetic fun log$default (Lco/elastic/otel/android/api/ElasticOtelAgent;Ljava/lang/String;Lio/opentelemetry/api/logs/Severity;Ljava/lang/String;Lio/opentelemetry/api/common/Attributes;Lio/opentelemetry/context/Context;Ljava/time/Instant;Ljava/time/Instant;ILjava/lang/Object;)V - public static final fun span (Lco/elastic/otel/android/api/ElasticOtelAgent;Ljava/lang/String;Lio/opentelemetry/api/common/Attributes;Lio/opentelemetry/api/trace/SpanKind;Lio/opentelemetry/context/Context;ZLkotlin/jvm/functions/Function1;)V - public static synthetic fun span$default (Lco/elastic/otel/android/api/ElasticOtelAgent;Ljava/lang/String;Lio/opentelemetry/api/common/Attributes;Lio/opentelemetry/api/trace/SpanKind;Lio/opentelemetry/context/Context;ZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)V + public static final fun log (Lco/elastic/otel/android/api/ElasticOtelAgent;Ljava/lang/String;Lio/opentelemetry/api/logs/Severity;Ljava/lang/String;Lio/opentelemetry/api/common/Attributes;Lio/opentelemetry/context/Context;Ljava/time/Instant;Ljava/time/Instant;Ljava/lang/String;)V + public static synthetic fun log$default (Lco/elastic/otel/android/api/ElasticOtelAgent;Ljava/lang/String;Lio/opentelemetry/api/logs/Severity;Ljava/lang/String;Lio/opentelemetry/api/common/Attributes;Lio/opentelemetry/context/Context;Ljava/time/Instant;Ljava/time/Instant;Ljava/lang/String;ILjava/lang/Object;)V + public static final fun span (Lco/elastic/otel/android/api/ElasticOtelAgent;Ljava/lang/String;Lio/opentelemetry/api/common/Attributes;Lio/opentelemetry/api/trace/SpanKind;Lio/opentelemetry/context/Context;ZLjava/lang/String;Lkotlin/jvm/functions/Function1;)V + public static synthetic fun span$default (Lco/elastic/otel/android/api/ElasticOtelAgent;Ljava/lang/String;Lio/opentelemetry/api/common/Attributes;Lio/opentelemetry/api/trace/SpanKind;Lio/opentelemetry/context/Context;ZLjava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V } public abstract interface class co/elastic/otel/android/features/session/Session { diff --git a/agent-sdk/src/main/java/co/elastic/otel/android/extensions/ElasticOtelAgentExtensions.kt b/agent-sdk/src/main/java/co/elastic/otel/android/extensions/ElasticOtelAgentExtensions.kt index a6d5db798..ec037c970 100644 --- a/agent-sdk/src/main/java/co/elastic/otel/android/extensions/ElasticOtelAgentExtensions.kt +++ b/agent-sdk/src/main/java/co/elastic/otel/android/extensions/ElasticOtelAgentExtensions.kt @@ -28,8 +28,20 @@ import io.opentelemetry.context.Context import io.opentelemetry.context.Scope import java.time.Instant -private const val SCOPE_NAME = "co.elastic.otel.android.extensions" +private const val DEFAULT_SCOPE_NAME = "co.elastic.otel.android.extensions" +/** + * Convenience method to send OpenTelemetry's [Log Records](https://opentelemetry.io/docs/concepts/signals/logs/). + * + * @param body The log body/text. + * @param severity The log's severity. Defaults to OpenTelemetry SDK's default. + * @param severityText The log's severity text. Defaults to OpenTelemetry SDK's default. + * @param attributes The log's attributes. Defaults to OpenTelemetry SDK's default. + * @param context The log's context. Defaults to OpenTelemetry SDK's default. + * @param observedTimestamp The log's observed timestamp. Defaults to OpenTelemetry SDK's default. + * @param timestamp The log's timestamp. Defaults to OpenTelemetry SDK's default. + * @param scopeName The log's instrumentation scope name. Defaults to [DEFAULT_SCOPE_NAME]. + */ fun ElasticOtelAgent.log( body: String, severity: Severity? = null, @@ -37,9 +49,10 @@ fun ElasticOtelAgent.log( attributes: Attributes? = null, context: Context? = null, observedTimestamp: Instant? = null, - timestamp: Instant? = null + timestamp: Instant? = null, + scopeName: String = DEFAULT_SCOPE_NAME ) { - val logger = getOpenTelemetry().logsBridge.get(SCOPE_NAME).logRecordBuilder() + val logger = getOpenTelemetry().logsBridge.get(scopeName).logRecordBuilder() .setBody(body) severity?.let { logger.setSeverity(it) } severityText?.let { logger.setSeverityText(it) } @@ -50,15 +63,27 @@ fun ElasticOtelAgent.log( logger.emit() } +/** + * Convenience method to send OpenTelemetry's [Spans](https://opentelemetry.io/docs/concepts/signals/traces/#spans). + * + * @param name The span name. + * @param attributes The span's attributes. Defaults to OpenTelemetry SDK's default. + * @param kind The span's kind. Defaults to OpenTelemetry SDK's default. + * @param parentContext The span's parent context. Defaults to OpenTelemetry SDK's default. + * @param makeCurrent Whether the span will be automatically set as the "current one" within the thread it's created in, until its body's finished. Defaults to TRUE. + * @param scopeName The span's instrumentation scope name. Defaults to [DEFAULT_SCOPE_NAME]. + * @param body The span's body. The span will start right before executing its body and will end right after its body's finished or an uncaught exception happens. + */ fun ElasticOtelAgent.span( name: String, attributes: Attributes? = null, kind: SpanKind? = null, parentContext: Context? = null, makeCurrent: Boolean = true, + scopeName: String = DEFAULT_SCOPE_NAME, body: (Span) -> Unit ) { - val builder = getOpenTelemetry().getTracer(SCOPE_NAME).spanBuilder(name) + val builder = getOpenTelemetry().getTracer(scopeName).spanBuilder(name) attributes?.let { builder.setAllAttributes(it) } kind?.let { builder.setSpanKind(it) } parentContext?.let { builder.setParent(it) } @@ -67,9 +92,9 @@ fun ElasticOtelAgent.span( val scope: Scope? = if (makeCurrent) span.makeCurrent() else null try { body(span) - } catch (e: Throwable) { + } catch (t: Throwable) { span.setStatus(StatusCode.ERROR) - span.recordException(e) + throw t } finally { scope?.close() span.end() diff --git a/agent-sdk/src/main/java/co/elastic/otel/android/internal/features/conditionaldrop/ConditionalDropLogRecordExporter.kt b/agent-sdk/src/main/java/co/elastic/otel/android/internal/features/conditionaldrop/ConditionalDropLogRecordExporter.kt index 76ae4f2df..4f9fa15ca 100644 --- a/agent-sdk/src/main/java/co/elastic/otel/android/internal/features/conditionaldrop/ConditionalDropLogRecordExporter.kt +++ b/agent-sdk/src/main/java/co/elastic/otel/android/internal/features/conditionaldrop/ConditionalDropLogRecordExporter.kt @@ -21,7 +21,6 @@ package co.elastic.otel.android.internal.features.conditionaldrop import io.opentelemetry.sdk.common.CompletableResultCode import io.opentelemetry.sdk.logs.data.LogRecordData import io.opentelemetry.sdk.logs.export.LogRecordExporter -import java.util.function.Predicate /** * This class is internal and is hence not for public use. Its APIs are unstable and can change at @@ -29,11 +28,11 @@ import java.util.function.Predicate */ internal class ConditionalDropLogRecordExporter( private val delegate: LogRecordExporter, - private val drop: Predicate + private val drop: (SignalType) -> Boolean ) : LogRecordExporter { override fun export(logs: MutableCollection): CompletableResultCode { - if (drop.test(SignalType.LOG)) { + if (drop(SignalType.LOG)) { return CompletableResultCode.ofSuccess() } return delegate.export(logs) diff --git a/agent-sdk/src/main/java/co/elastic/otel/android/internal/features/conditionaldrop/ConditionalDropManager.kt b/agent-sdk/src/main/java/co/elastic/otel/android/internal/features/conditionaldrop/ConditionalDropManager.kt index fd6329a4e..8f12a8d44 100644 --- a/agent-sdk/src/main/java/co/elastic/otel/android/internal/features/conditionaldrop/ConditionalDropManager.kt +++ b/agent-sdk/src/main/java/co/elastic/otel/android/internal/features/conditionaldrop/ConditionalDropManager.kt @@ -21,16 +21,19 @@ package co.elastic.otel.android.internal.features.conditionaldrop import io.opentelemetry.sdk.logs.export.LogRecordExporter import io.opentelemetry.sdk.metrics.export.MetricExporter import io.opentelemetry.sdk.trace.export.SpanExporter -import java.util.function.Predicate /** * This class is internal and is hence not for public use. Its APIs are unstable and can change at * any time. */ -internal class ConditionalDropManager(private var dropCondition: Predicate? = null) { +internal class ConditionalDropManager(private var dropCondition: ((SignalType) -> Boolean)? = null) { - fun dropWhen(condition: Predicate) { - dropCondition = dropCondition?.or(condition) ?: condition + fun dropWhen(condition: (SignalType) -> Boolean) { + dropCondition = dropCondition?.let { + { type -> + it(type) || condition(type) + } + } ?: condition } fun createConditionalDropSpanExporter(delegate: SpanExporter): SpanExporter { diff --git a/agent-sdk/src/main/java/co/elastic/otel/android/internal/features/conditionaldrop/ConditionalDropMetricExporter.kt b/agent-sdk/src/main/java/co/elastic/otel/android/internal/features/conditionaldrop/ConditionalDropMetricExporter.kt index ee262f932..bcc849830 100644 --- a/agent-sdk/src/main/java/co/elastic/otel/android/internal/features/conditionaldrop/ConditionalDropMetricExporter.kt +++ b/agent-sdk/src/main/java/co/elastic/otel/android/internal/features/conditionaldrop/ConditionalDropMetricExporter.kt @@ -23,7 +23,6 @@ import io.opentelemetry.sdk.metrics.InstrumentType import io.opentelemetry.sdk.metrics.data.AggregationTemporality import io.opentelemetry.sdk.metrics.data.MetricData import io.opentelemetry.sdk.metrics.export.MetricExporter -import java.util.function.Predicate /** * This class is internal and is hence not for public use. Its APIs are unstable and can change at @@ -31,7 +30,7 @@ import java.util.function.Predicate */ internal class ConditionalDropMetricExporter( private val delegate: MetricExporter, - private val drop: Predicate + private val drop: (SignalType) -> Boolean ) : MetricExporter { override fun getAggregationTemporality(instrumentType: InstrumentType): AggregationTemporality { @@ -39,7 +38,7 @@ internal class ConditionalDropMetricExporter( } override fun export(metrics: MutableCollection): CompletableResultCode { - if (drop.test(SignalType.METRIC)) { + if (drop(SignalType.METRIC)) { return CompletableResultCode.ofSuccess() } return delegate.export(metrics) diff --git a/agent-sdk/src/main/java/co/elastic/otel/android/internal/features/conditionaldrop/ConditionalDropSpanExporter.kt b/agent-sdk/src/main/java/co/elastic/otel/android/internal/features/conditionaldrop/ConditionalDropSpanExporter.kt index 322add5c7..228c7dbf4 100644 --- a/agent-sdk/src/main/java/co/elastic/otel/android/internal/features/conditionaldrop/ConditionalDropSpanExporter.kt +++ b/agent-sdk/src/main/java/co/elastic/otel/android/internal/features/conditionaldrop/ConditionalDropSpanExporter.kt @@ -21,7 +21,6 @@ package co.elastic.otel.android.internal.features.conditionaldrop import io.opentelemetry.sdk.common.CompletableResultCode import io.opentelemetry.sdk.trace.data.SpanData import io.opentelemetry.sdk.trace.export.SpanExporter -import java.util.function.Predicate /** * This class is internal and is hence not for public use. Its APIs are unstable and can change at @@ -29,11 +28,11 @@ import java.util.function.Predicate */ internal class ConditionalDropSpanExporter( private val delegate: SpanExporter, - private val drop: Predicate + private val drop: (SignalType) -> Boolean ) : SpanExporter { override fun export(spans: MutableCollection): CompletableResultCode { - if (drop.test(SignalType.SPAN)) { + if (drop(SignalType.SPAN)) { return CompletableResultCode.ofSuccess() } return delegate.export(spans) diff --git a/agent-sdk/src/main/java/co/elastic/otel/android/internal/time/ntp/UdpClient.kt b/agent-sdk/src/main/java/co/elastic/otel/android/internal/time/ntp/UdpClient.kt index 91df144e4..a79f3d0ae 100644 --- a/agent-sdk/src/main/java/co/elastic/otel/android/internal/time/ntp/UdpClient.kt +++ b/agent-sdk/src/main/java/co/elastic/otel/android/internal/time/ntp/UdpClient.kt @@ -25,7 +25,9 @@ import java.net.InetAddress import java.net.SocketException import java.net.SocketTimeoutException import java.net.UnknownHostException -import java.time.Duration +import kotlin.time.Duration +import kotlin.time.Duration.Companion.seconds +import kotlin.time.DurationUnit /** * This class is internal and is hence not for public use. Its APIs are unstable and can change at @@ -40,13 +42,13 @@ internal class UdpClient internal constructor( private var address: InetAddress? = null @Throws(UnknownHostException::class, SocketTimeoutException::class, SocketException::class) - fun send(bytes: ByteArray, timeout: Duration = Duration.ofSeconds(5)): ByteArray = + fun send(bytes: ByteArray, timeout: Duration = 5.seconds): ByteArray = synchronized(this) { if (address == null) { address = InetAddress.getByName(host) } - socket.soTimeout = timeout.toMillis().toInt() + socket.soTimeout = timeout.toInt(DurationUnit.MILLISECONDS) val packet = DatagramPacket(bytes, bytes.size, address, port) socket.send(packet) diff --git a/agent-sdk/src/test/java/co/elastic/otel/android/functional/ElasticApmAgentTest.kt b/agent-sdk/src/test/java/co/elastic/otel/android/functional/ElasticApmAgentTest.kt index a38f185e2..644763302 100644 --- a/agent-sdk/src/test/java/co/elastic/otel/android/functional/ElasticApmAgentTest.kt +++ b/agent-sdk/src/test/java/co/elastic/otel/android/functional/ElasticApmAgentTest.kt @@ -26,9 +26,8 @@ import co.elastic.otel.android.interceptor.Interceptor import co.elastic.otel.android.internal.api.ManagedElasticOtelAgent import co.elastic.otel.android.internal.features.centralconfig.CentralConfigurationConnectivity import co.elastic.otel.android.internal.features.diskbuffering.DiskBufferingConfiguration -import co.elastic.otel.android.processors.ProcessorFactory -import co.elastic.otel.android.test.common.ElasticAttributes.getLogRecordDefaultAttributes -import co.elastic.otel.android.test.common.ElasticAttributes.getSpanDefaultAttributes +import co.elastic.otel.android.test.exporter.InMemoryExporterProvider +import co.elastic.otel.android.test.processor.SimpleProcessorFactory import co.elastic.otel.android.testutils.DummySntpClient import co.elastic.otel.android.testutils.WireMockRule import io.mockk.Runs @@ -40,26 +39,9 @@ import io.opentelemetry.api.OpenTelemetry import io.opentelemetry.api.common.AttributeKey import io.opentelemetry.api.common.Attributes import io.opentelemetry.sdk.common.CompletableResultCode -import io.opentelemetry.sdk.logs.LogRecordProcessor -import io.opentelemetry.sdk.logs.data.LogRecordData -import io.opentelemetry.sdk.logs.export.LogRecordExporter -import io.opentelemetry.sdk.logs.export.SimpleLogRecordProcessor -import io.opentelemetry.sdk.metrics.data.MetricData -import io.opentelemetry.sdk.metrics.export.MetricExporter -import io.opentelemetry.sdk.metrics.export.MetricReader -import io.opentelemetry.sdk.metrics.export.PeriodicMetricReader import io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat -import io.opentelemetry.sdk.testing.exporter.InMemoryLogRecordExporter -import io.opentelemetry.sdk.testing.exporter.InMemoryMetricExporter -import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter -import io.opentelemetry.sdk.trace.SpanProcessor -import io.opentelemetry.sdk.trace.data.SpanData -import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor -import io.opentelemetry.sdk.trace.export.SpanExporter -import java.io.File import java.time.Duration import java.util.concurrent.TimeUnit -import java.util.concurrent.atomic.AtomicReference import org.awaitility.core.ConditionTimeoutException import org.awaitility.kotlin.await import org.junit.After @@ -78,11 +60,6 @@ class ElasticApmAgentTest { private val inMemoryExporters = InMemoryExporterProvider() private val inMemoryExportersInterceptor = Interceptor { inMemoryExporters } - companion object { - private val SPAN_DEFAULT_ATTRIBUTES = getSpanDefaultAttributes() - private val LOG_DEFAULT_ATTRIBUTES = getLogRecordDefaultAttributes() - } - @get:Rule val wireMockRule = WireMockRule() @@ -746,10 +723,6 @@ class ElasticApmAgentTest { } } - private fun verifySessionId(attributes: Attributes, value: String) { - assertThat(attributes.get(AttributeKey.stringKey("session.id"))).isEqualTo(value) - } - private fun sendSpan(name: String = "span-name", attributes: Attributes = Attributes.empty()) { agent.getOpenTelemetry().getTracer("TestTracer") .spanBuilder(name) @@ -768,28 +741,7 @@ class ElasticApmAgentTest { .counterBuilder("counter") .build() .add(1) - simpleProcessorFactory.flush() - } - - private fun awaitAndTrackTimeMillis(condition: () -> Boolean): Long { - val waitStart = System.nanoTime() - await.until(condition) - val waitTimeMillis = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - waitStart) - return waitTimeMillis - } - - private fun awaitForCacheFileCreation(dirNames: List) { - val signalsDir = File(RuntimeEnvironment.getApplication().cacheDir, "opentelemetry/signals") - val dirs = dirNames.map { File(signalsDir, it) } - await.until { - var dirsNotEmpty = 0 - dirs.forEach { - if (it.list().isNotEmpty()) { - dirsNotEmpty++ - } - } - dirsNotEmpty == dirs.size - } + simpleProcessorFactory.flushMetrics().join(1, TimeUnit.SECONDS) } private fun awaitForOpenGates(maxSecondsToWait: Int = 1) { @@ -806,71 +758,4 @@ class ElasticApmAgentTest { throw e } } - - private interface FlushableProcessorFactory : ProcessorFactory { - fun flush() - } - - private class SimpleProcessorFactory : FlushableProcessorFactory { - private lateinit var metricReader: PeriodicMetricReader - - override fun createSpanProcessor(exporter: SpanExporter?): SpanProcessor? { - return SimpleSpanProcessor.create(exporter) - } - - override fun createLogRecordProcessor(exporter: LogRecordExporter?): LogRecordProcessor? { - return SimpleLogRecordProcessor.create(exporter) - } - - override fun createMetricReader(exporter: MetricExporter?): MetricReader { - metricReader = PeriodicMetricReader.create(exporter) - return metricReader - } - - override fun flush() { - metricReader.forceFlush() - } - } - - private class InMemoryExporterProvider : ExporterProvider { - private var spanExporter = AtomicReference(InMemorySpanExporter.create()) - private var logRecordExporter = AtomicReference(InMemoryLogRecordExporter.create()) - private var metricExporter = AtomicReference(InMemoryMetricExporter.create()) - - fun reset() { - spanExporter.set(InMemorySpanExporter.create()) - logRecordExporter.set(InMemoryLogRecordExporter.create()) - metricExporter.set(InMemoryMetricExporter.create()) - } - - fun resetExporters() { - spanExporter.get().reset() - logRecordExporter.get().reset() - metricExporter.get().reset() - } - - fun getFinishedSpans(): List { - return spanExporter.get().finishedSpanItems - } - - fun getFinishedLogRecords(): List { - return logRecordExporter.get().finishedLogRecordItems - } - - fun getFinishedMetrics(): List { - return metricExporter.get().finishedMetricItems - } - - override fun getSpanExporter(): SpanExporter? { - return spanExporter.get() - } - - override fun getLogRecordExporter(): LogRecordExporter? { - return logRecordExporter.get() - } - - override fun getMetricExporter(): MetricExporter? { - return metricExporter.get() - } - } } \ No newline at end of file diff --git a/agent-sdk/src/test/java/co/elastic/otel/android/functional/ManagedElasticOtelAgentTest.kt b/agent-sdk/src/test/java/co/elastic/otel/android/functional/ManagedElasticOtelAgentTest.kt index 0e7ce3d81..01d61a176 100644 --- a/agent-sdk/src/test/java/co/elastic/otel/android/functional/ManagedElasticOtelAgentTest.kt +++ b/agent-sdk/src/test/java/co/elastic/otel/android/functional/ManagedElasticOtelAgentTest.kt @@ -20,7 +20,6 @@ package co.elastic.otel.android.functional import android.app.Application import android.content.Intent -import co.elastic.otel.android.exporters.ExporterProvider import co.elastic.otel.android.features.session.SessionIdGenerator import co.elastic.otel.android.internal.api.ManagedElasticOtelAgent import co.elastic.otel.android.internal.features.clock.ElasticClockBroadcastReceiver @@ -32,6 +31,8 @@ import co.elastic.otel.android.internal.time.ntp.SntpClient import co.elastic.otel.android.processors.ProcessorFactory import co.elastic.otel.android.test.common.ElasticAttributes.getLogRecordDefaultAttributes import co.elastic.otel.android.test.common.ElasticAttributes.getSpanDefaultAttributes +import co.elastic.otel.android.test.exporter.InMemoryExporterProvider +import co.elastic.otel.android.test.processor.SimpleProcessorFactory import co.elastic.otel.android.testutils.DummySntpClient import co.elastic.otel.android.testutils.WireMockRule import io.mockk.Runs @@ -44,29 +45,20 @@ import io.mockk.verify import io.opentelemetry.api.common.AttributeKey import io.opentelemetry.api.common.Attributes import io.opentelemetry.sdk.logs.LogRecordProcessor -import io.opentelemetry.sdk.logs.data.LogRecordData import io.opentelemetry.sdk.logs.export.BatchLogRecordProcessor import io.opentelemetry.sdk.logs.export.LogRecordExporter -import io.opentelemetry.sdk.logs.export.SimpleLogRecordProcessor -import io.opentelemetry.sdk.metrics.data.MetricData import io.opentelemetry.sdk.metrics.export.MetricExporter import io.opentelemetry.sdk.metrics.export.MetricReader import io.opentelemetry.sdk.metrics.export.PeriodicMetricReader import io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat -import io.opentelemetry.sdk.testing.exporter.InMemoryLogRecordExporter -import io.opentelemetry.sdk.testing.exporter.InMemoryMetricExporter -import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter import io.opentelemetry.sdk.trace.SpanProcessor -import io.opentelemetry.sdk.trace.data.SpanData import io.opentelemetry.sdk.trace.export.BatchSpanProcessor -import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor import io.opentelemetry.sdk.trace.export.SpanExporter import java.io.File import java.io.IOException import java.time.Duration import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicLong -import java.util.concurrent.atomic.AtomicReference import org.awaitility.core.ConditionTimeoutException import org.awaitility.kotlin.await import org.junit.After @@ -804,7 +796,7 @@ class ManagedElasticOtelAgentTest { .counterBuilder("counter") .build() .add(1) - simpleProcessorFactory.flush() + simpleProcessorFactory.flushMetrics().join(1, TimeUnit.SECONDS) } private fun awaitAndTrackTimeMillis(condition: () -> Boolean): Long { @@ -843,73 +835,6 @@ class ManagedElasticOtelAgentTest { } } - private interface FlushableProcessorFactory : ProcessorFactory { - fun flush() - } - - private class SimpleProcessorFactory : FlushableProcessorFactory { - private lateinit var metricReader: PeriodicMetricReader - - override fun createSpanProcessor(exporter: SpanExporter?): SpanProcessor? { - return SimpleSpanProcessor.create(exporter) - } - - override fun createLogRecordProcessor(exporter: LogRecordExporter?): LogRecordProcessor? { - return SimpleLogRecordProcessor.create(exporter) - } - - override fun createMetricReader(exporter: MetricExporter?): MetricReader { - metricReader = PeriodicMetricReader.create(exporter) - return metricReader - } - - override fun flush() { - metricReader.forceFlush() - } - } - - private class InMemoryExporterProvider : ExporterProvider { - private var spanExporter = AtomicReference(InMemorySpanExporter.create()) - private var logRecordExporter = AtomicReference(InMemoryLogRecordExporter.create()) - private var metricExporter = AtomicReference(InMemoryMetricExporter.create()) - - fun reset() { - spanExporter.set(InMemorySpanExporter.create()) - logRecordExporter.set(InMemoryLogRecordExporter.create()) - metricExporter.set(InMemoryMetricExporter.create()) - } - - fun resetExporters() { - spanExporter.get().reset() - logRecordExporter.get().reset() - metricExporter.get().reset() - } - - fun getFinishedSpans(): List { - return spanExporter.get().finishedSpanItems - } - - fun getFinishedLogRecords(): List { - return logRecordExporter.get().finishedLogRecordItems - } - - fun getFinishedMetrics(): List { - return metricExporter.get().finishedMetricItems - } - - override fun getSpanExporter(): SpanExporter? { - return spanExporter.get() - } - - override fun getLogRecordExporter(): LogRecordExporter? { - return logRecordExporter.get() - } - - override fun getMetricExporter(): MetricExporter? { - return metricExporter.get() - } - } - class BatchProcessorFactory : ProcessorFactory { override fun createSpanProcessor(exporter: SpanExporter?): SpanProcessor? { return BatchSpanProcessor.builder(exporter) diff --git a/agent-sdk/src/test/java/co/elastic/otel/android/internal/time/ntp/UdpClientTest.kt b/agent-sdk/src/test/java/co/elastic/otel/android/internal/time/ntp/UdpClientTest.kt index e5dc60e7a..a5d886dac 100644 --- a/agent-sdk/src/test/java/co/elastic/otel/android/internal/time/ntp/UdpClientTest.kt +++ b/agent-sdk/src/test/java/co/elastic/otel/android/internal/time/ntp/UdpClientTest.kt @@ -25,6 +25,7 @@ import java.net.SocketTimeoutException import java.net.UnknownHostException import java.time.Duration import java.util.concurrent.CountDownLatch +import kotlin.time.Duration.Companion.seconds import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach @@ -91,7 +92,7 @@ class UdpClientTest { assertThrows { client.send( "Example".toByteArray(), - Duration.ofSeconds(1) + 1.seconds ) } } @@ -101,7 +102,7 @@ class UdpClientTest { client = UdpClient(SERVER_HOST, server.getPort() + 1, 256) assertThrows { - client.send("Example".toByteArray(), Duration.ofSeconds(1)) + client.send("Example".toByteArray(), 1.seconds) } } @@ -110,7 +111,7 @@ class UdpClientTest { server.close() assertThrows { - client.send("Example".toByteArray(), Duration.ofSeconds(1)) + client.send("Example".toByteArray(), 1.seconds) } } @@ -137,7 +138,7 @@ class UdpClientTest { assertThrows { latch.countDown() - client.send("Example".toByteArray(), Duration.ofSeconds(60)) + client.send("Example".toByteArray(), 60.seconds) } } } \ No newline at end of file diff --git a/android_tests.sh b/android_tests.sh index b475706b7..5d2253814 100755 --- a/android_tests.sh +++ b/android_tests.sh @@ -1 +1,2 @@ -./gradlew -p "instrumentation-test" connectedCheck \ No newline at end of file +./gradlew -p "instrumentation-test" connectedCheck +./gradlew -p "sample-app" connectedCheck -Pelastic.testing.automated=true diff --git a/build-tools/src/main/kotlin/elastic.instrumentation-library.gradle.kts b/build-tools/src/main/kotlin/elastic.instrumentation-library.gradle.kts index adab4b3f2..3f6afd8ce 100644 --- a/build-tools/src/main/kotlin/elastic.instrumentation-library.gradle.kts +++ b/build-tools/src/main/kotlin/elastic.instrumentation-library.gradle.kts @@ -1,6 +1,16 @@ plugins { id("elastic.android-library") id("kotlin-kapt") + id("com.github.gmazzo.buildconfig") +} + +val instrumentationGroupId = "${rootProject.group}.instrumentation" + +buildConfig { + val name = project.parent!!.name + packageName("${instrumentationGroupId}.generated.$name") + buildConfigField("INSTRUMENTATION_ID", "${instrumentationGroupId}.$name") + buildConfigField("INSTRUMENTATION_VERSION", "$version") } val libs = extensions.getByType().named("libs") diff --git a/build-tools/src/main/kotlin/elastic.instrumentation-plugin.gradle.kts b/build-tools/src/main/kotlin/elastic.instrumentation-plugin.gradle.kts index 4cc5b830b..e11895d47 100644 --- a/build-tools/src/main/kotlin/elastic.instrumentation-plugin.gradle.kts +++ b/build-tools/src/main/kotlin/elastic.instrumentation-plugin.gradle.kts @@ -7,18 +7,30 @@ plugins { } val instrumentationGroupId = "${rootProject.group}.instrumentation" +val parentName = project.parent!!.name buildConfig { - packageName("${instrumentationGroupId}.generated") + packageName("${instrumentationGroupId}.generated.$parentName") } abstract class ElasticBuildConfig @Inject constructor( private val buildConfigExtension: BuildConfigExtension, private val buildConfigGroupId: String, + private val parentProjectName: String, private val projectVersion: String ) { - fun projectUri(fieldName: String, artifactName: String) { - dependencyUri(fieldName, "${buildConfigGroupId}:$artifactName:$projectVersion") + fun libraryUri() { + dependencyUri( + "LIBRARY_URI", + "${buildConfigGroupId}:${parentProjectName}-library:$projectVersion" + ) + } + + fun byteBuddyPluginUri() { + dependencyUri( + "BYTEBUDDY_PLUGIN_URI", + "${buildConfigGroupId}:${parentProjectName}-bytebuddy:$projectVersion" + ) } fun dependencyUri(fieldName: String, dependencyUri: String) { @@ -32,9 +44,10 @@ abstract class ElasticBuildConfig @Inject constructor( abstract class InstrumentationPluginConfig @Inject constructor( private val gradlePlugin: GradlePluginDevelopmentExtension, - private val projectDescription: String + private val projectDescription: String, + private val parentProjectName: String ) { - fun create(id: String, action: Action) { + fun create(id: String = parentProjectName, action: Action) { val pluginDeclaration = gradlePlugin.plugins.create("${id}Instrumentation") pluginDeclaration.id = "co.elastic.otel.android.instrumentation-$id" pluginDeclaration.description = projectDescription @@ -48,6 +61,7 @@ project.extensions.create( ElasticBuildConfig::class, buildConfig, instrumentationGroupId, + parentName, version ) @@ -55,7 +69,8 @@ project.extensions.create( "elasticInstrumentationPlugins", InstrumentationPluginConfig::class, gradlePlugin, - project.description!! + project.description!!, + parentName ) dependencies { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a1b6151ea..ce2a878ad 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -28,7 +28,6 @@ junit5-vintage = { module = "org.junit.vintage:junit-vintage-engine", version.re opentelemetry-testing = { module = "io.opentelemetry:opentelemetry-sdk-testing", version.ref = "opentelemetry" } robolectric = "org.robolectric:robolectric:4.14.1" assertj = "org.assertj:assertj-core:3.27.3" -coreLib = "com.android.tools:desugar_jdk_libs:2.1.4" mockk = "io.mockk:mockk:1.13.16" awaitility = "org.awaitility:awaitility-kotlin:4.2.2" wireMock = "org.wiremock:wiremock:3.10.0" @@ -49,6 +48,7 @@ autoService-compiler = { module = "com.google.auto.service:auto-service", versio buildconfig-plugin = "com.github.gmazzo.buildconfig:com.github.gmazzo.buildconfig.gradle.plugin:5.5.1" animalsniffer-plugin = "ru.vyarus:gradle-animalsniffer-plugin:2.0.0" kotlin-binaryCompatibility = "org.jetbrains.kotlinx.binary-compatibility-validator:org.jetbrains.kotlinx.binary-compatibility-validator.gradle.plugin:0.17.0" +coreLib = "com.android.tools:desugar_jdk_libs:2.1.4" [bundles] mocking = ["mockk"] diff --git a/instrumentation/api/src/main/java/co/elastic/otel/android/instrumentation/internal/Instrumentation.kt b/instrumentation/api/src/main/java/co/elastic/otel/android/instrumentation/internal/Instrumentation.kt index f24c95bd1..1f7f41c9f 100644 --- a/instrumentation/api/src/main/java/co/elastic/otel/android/instrumentation/internal/Instrumentation.kt +++ b/instrumentation/api/src/main/java/co/elastic/otel/android/instrumentation/internal/Instrumentation.kt @@ -28,4 +28,8 @@ import co.elastic.otel.android.api.ElasticOtelAgent interface Instrumentation { fun install(application: Application, agent: ElasticOtelAgent) + + fun getId(): String + + fun getVersion(): String } \ No newline at end of file diff --git a/instrumentation/crash/library/src/main/java/co/elastic/otel/android/crash/internal/CrashInstrumentation.kt b/instrumentation/crash/library/src/main/java/co/elastic/otel/android/crash/internal/CrashInstrumentation.kt index fa8930a37..1d3946bde 100644 --- a/instrumentation/crash/library/src/main/java/co/elastic/otel/android/crash/internal/CrashInstrumentation.kt +++ b/instrumentation/crash/library/src/main/java/co/elastic/otel/android/crash/internal/CrashInstrumentation.kt @@ -21,6 +21,7 @@ package co.elastic.otel.android.crash.internal import android.app.Application import co.elastic.otel.android.api.ElasticOtelAgent import co.elastic.otel.android.crash.internal.handler.ElasticExceptionHandler +import co.elastic.otel.android.instrumentation.generated.crash.BuildConfig import co.elastic.otel.android.instrumentation.internal.Instrumentation import com.google.auto.service.AutoService @@ -34,4 +35,12 @@ class CrashInstrumentation : Instrumentation { override fun install(application: Application, agent: ElasticOtelAgent) { Thread.setDefaultUncaughtExceptionHandler(ElasticExceptionHandler.create(agent)) } + + override fun getId(): String { + return BuildConfig.INSTRUMENTATION_ID + } + + override fun getVersion(): String { + return BuildConfig.INSTRUMENTATION_VERSION + } } \ No newline at end of file diff --git a/instrumentation/crash/plugin/build.gradle.kts b/instrumentation/crash/plugin/build.gradle.kts index f94ae59bd..b5b6a476e 100644 --- a/instrumentation/crash/plugin/build.gradle.kts +++ b/instrumentation/crash/plugin/build.gradle.kts @@ -3,11 +3,11 @@ plugins { } elasticBuildConfig { - projectUri("LIBRARY_URI", "crash-library") + libraryUri() } elasticInstrumentationPlugins { - create("crash") { + create { implementationClass = "co.elastic.otel.android.crash.CrashInstrumentationPlugin" displayName = "Elastic OTel Android instrumentation for tracking app crashes" } diff --git a/instrumentation/crash/plugin/src/main/java/co/elastic/otel/android/crash/CrashInstrumentationPlugin.kt b/instrumentation/crash/plugin/src/main/java/co/elastic/otel/android/crash/CrashInstrumentationPlugin.kt index 3f077a7ce..f09316715 100644 --- a/instrumentation/crash/plugin/src/main/java/co/elastic/otel/android/crash/CrashInstrumentationPlugin.kt +++ b/instrumentation/crash/plugin/src/main/java/co/elastic/otel/android/crash/CrashInstrumentationPlugin.kt @@ -18,7 +18,7 @@ */ package co.elastic.otel.android.crash -import co.elastic.otel.android.instrumentation.generated.BuildConfig +import co.elastic.otel.android.instrumentation.generated.crash.BuildConfig import co.elastic.otel.android.plugin.ElasticAgentPlugin import co.elastic.otel.android.plugin.internal.InstrumentationPlugin import org.gradle.api.Project diff --git a/instrumentation/launchtime/library/src/main/java/co/elastic/otel/android/launchtime/internal/LaunchTimeInstrumentation.kt b/instrumentation/launchtime/library/src/main/java/co/elastic/otel/android/launchtime/internal/LaunchTimeInstrumentation.kt index 2aa81b9b5..9788fa35c 100644 --- a/instrumentation/launchtime/library/src/main/java/co/elastic/otel/android/launchtime/internal/LaunchTimeInstrumentation.kt +++ b/instrumentation/launchtime/library/src/main/java/co/elastic/otel/android/launchtime/internal/LaunchTimeInstrumentation.kt @@ -22,6 +22,7 @@ import android.app.Application import androidx.lifecycle.ProcessLifecycleOwner import co.elastic.otel.android.api.ElasticOtelAgent import co.elastic.otel.android.api.flusher.MetricFlusher +import co.elastic.otel.android.instrumentation.generated.launchtime.BuildConfig import co.elastic.otel.android.instrumentation.internal.Instrumentation import com.google.auto.service.AutoService @@ -62,4 +63,12 @@ class LaunchTimeInstrumentation : Instrumentation, LaunchTimeApplicationListener } batchCallback.close() } + + override fun getId(): String { + return BuildConfig.INSTRUMENTATION_ID + } + + override fun getVersion(): String { + return BuildConfig.INSTRUMENTATION_VERSION + } } \ No newline at end of file diff --git a/instrumentation/launchtime/plugin/build.gradle.kts b/instrumentation/launchtime/plugin/build.gradle.kts index aac73e907..33e9b8545 100644 --- a/instrumentation/launchtime/plugin/build.gradle.kts +++ b/instrumentation/launchtime/plugin/build.gradle.kts @@ -3,11 +3,11 @@ plugins { } elasticBuildConfig { - projectUri("LIBRARY_URI", "launchtime-library") + libraryUri() } elasticInstrumentationPlugins { - create("launchtime") { + create { implementationClass = "co.elastic.otel.android.launchtime.LaunchTimeInstrumentationPlugin" displayName = "Elastic OTel Android instrumentation for tracking app launch time" } diff --git a/instrumentation/launchtime/plugin/src/main/java/co/elastic/otel/android/launchtime/LaunchTimeInstrumentationPlugin.kt b/instrumentation/launchtime/plugin/src/main/java/co/elastic/otel/android/launchtime/LaunchTimeInstrumentationPlugin.kt index 74793f748..fa4e3605c 100644 --- a/instrumentation/launchtime/plugin/src/main/java/co/elastic/otel/android/launchtime/LaunchTimeInstrumentationPlugin.kt +++ b/instrumentation/launchtime/plugin/src/main/java/co/elastic/otel/android/launchtime/LaunchTimeInstrumentationPlugin.kt @@ -18,7 +18,7 @@ */ package co.elastic.otel.android.launchtime -import co.elastic.otel.android.instrumentation.generated.BuildConfig +import co.elastic.otel.android.instrumentation.generated.launchtime.BuildConfig import co.elastic.otel.android.plugin.ElasticAgentPlugin import co.elastic.otel.android.plugin.internal.InstrumentationPlugin import org.gradle.api.Project diff --git a/instrumentation/okhttp/library/src/main/java/co/elastic/otel/android/okhttp/internal/OkHttpInstrumentation.kt b/instrumentation/okhttp/library/src/main/java/co/elastic/otel/android/okhttp/internal/OkHttpInstrumentation.kt index 0f52b9dfe..03327698d 100644 --- a/instrumentation/okhttp/library/src/main/java/co/elastic/otel/android/okhttp/internal/OkHttpInstrumentation.kt +++ b/instrumentation/okhttp/library/src/main/java/co/elastic/otel/android/okhttp/internal/OkHttpInstrumentation.kt @@ -20,6 +20,7 @@ package co.elastic.otel.android.okhttp.internal import android.app.Application import co.elastic.otel.android.api.ElasticOtelAgent +import co.elastic.otel.android.instrumentation.generated.okhttp.BuildConfig import co.elastic.otel.android.instrumentation.internal.Instrumentation import co.elastic.otel.android.okhttp.internal.plugin.OkHttp3Singletons import com.google.auto.service.AutoService @@ -34,4 +35,12 @@ class OkHttpInstrumentation : Instrumentation { override fun install(application: Application, agent: ElasticOtelAgent) { OkHttp3Singletons.configure(agent.getOpenTelemetry()) } + + override fun getId(): String { + return BuildConfig.INSTRUMENTATION_ID + } + + override fun getVersion(): String { + return BuildConfig.INSTRUMENTATION_VERSION + } } \ No newline at end of file diff --git a/instrumentation/okhttp/plugin/build.gradle.kts b/instrumentation/okhttp/plugin/build.gradle.kts index 8d645e0cc..340dc89cf 100644 --- a/instrumentation/okhttp/plugin/build.gradle.kts +++ b/instrumentation/okhttp/plugin/build.gradle.kts @@ -3,12 +3,12 @@ plugins { } elasticBuildConfig { - projectUri("LIBRARY_URI", "okhttp-library") - projectUri("BYTEBUDDY_PLUGIN", "okhttp-bytebuddy") + libraryUri() + byteBuddyPluginUri() } elasticInstrumentationPlugins { - create("okhttp") { + create { implementationClass = "co.elastic.otel.android.okhttp.OkHttpInstrumentationPlugin" displayName = "Elastic OTel Android instrumentation for tracking OkHttp requests" } diff --git a/instrumentation/okhttp/plugin/src/main/java/co/elastic/otel/android/okhttp/OkHttpInstrumentationPlugin.kt b/instrumentation/okhttp/plugin/src/main/java/co/elastic/otel/android/okhttp/OkHttpInstrumentationPlugin.kt index 209855e15..9c5cbabc2 100644 --- a/instrumentation/okhttp/plugin/src/main/java/co/elastic/otel/android/okhttp/OkHttpInstrumentationPlugin.kt +++ b/instrumentation/okhttp/plugin/src/main/java/co/elastic/otel/android/okhttp/OkHttpInstrumentationPlugin.kt @@ -18,7 +18,7 @@ */ package co.elastic.otel.android.okhttp -import co.elastic.otel.android.instrumentation.generated.BuildConfig +import co.elastic.otel.android.instrumentation.generated.okhttp.BuildConfig import co.elastic.otel.android.plugin.ElasticAgentPlugin import co.elastic.otel.android.plugin.internal.ByteBuddyDependencyAttacher import co.elastic.otel.android.plugin.internal.InstrumentationPlugin @@ -34,7 +34,7 @@ class OkHttpInstrumentationPlugin : InstrumentationPlugin() { agentPlugin.addBuildVariantListener( ByteBuddyDependencyAttacher( target, - BuildConfig.BYTEBUDDY_PLUGIN + BuildConfig.BYTEBUDDY_PLUGIN_URI ) ) } diff --git a/internal-tools/otel-test-common/build.gradle.kts b/internal-tools/otel-test-common/build.gradle.kts new file mode 100644 index 000000000..f9064f24a --- /dev/null +++ b/internal-tools/otel-test-common/build.gradle.kts @@ -0,0 +1,8 @@ +plugins { + id("elastic.android-test-library") +} + +dependencies { + api(project(":agent-sdk")) + api(libs.opentelemetry.testing) +} \ No newline at end of file diff --git a/internal-tools/otel-test-common/src/main/AndroidManifest.xml b/internal-tools/otel-test-common/src/main/AndroidManifest.xml new file mode 100644 index 000000000..568741e54 --- /dev/null +++ b/internal-tools/otel-test-common/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/internal-tools/otel-test-common/src/main/java/co/elastic/otel/android/test/exporter/InMemoryExporterProvider.kt b/internal-tools/otel-test-common/src/main/java/co/elastic/otel/android/test/exporter/InMemoryExporterProvider.kt new file mode 100644 index 000000000..44f76c7c6 --- /dev/null +++ b/internal-tools/otel-test-common/src/main/java/co/elastic/otel/android/test/exporter/InMemoryExporterProvider.kt @@ -0,0 +1,55 @@ +package co.elastic.otel.android.test.exporter + +import co.elastic.otel.android.exporters.ExporterProvider +import io.opentelemetry.sdk.logs.data.LogRecordData +import io.opentelemetry.sdk.logs.export.LogRecordExporter +import io.opentelemetry.sdk.metrics.data.MetricData +import io.opentelemetry.sdk.metrics.export.MetricExporter +import io.opentelemetry.sdk.testing.exporter.InMemoryLogRecordExporter +import io.opentelemetry.sdk.testing.exporter.InMemoryMetricExporter +import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter +import io.opentelemetry.sdk.trace.data.SpanData +import io.opentelemetry.sdk.trace.export.SpanExporter +import java.util.concurrent.atomic.AtomicReference + +class InMemoryExporterProvider : ExporterProvider { + private var spanExporter = AtomicReference(InMemorySpanExporter.create()) + private var logRecordExporter = AtomicReference(InMemoryLogRecordExporter.create()) + private var metricExporter = AtomicReference(InMemoryMetricExporter.create()) + + fun reset() { + spanExporter.set(InMemorySpanExporter.create()) + logRecordExporter.set(InMemoryLogRecordExporter.create()) + metricExporter.set(InMemoryMetricExporter.create()) + } + + fun resetExporters() { + spanExporter.get().reset() + logRecordExporter.get().reset() + metricExporter.get().reset() + } + + fun getFinishedSpans(): List { + return spanExporter.get().finishedSpanItems + } + + fun getFinishedLogRecords(): List { + return logRecordExporter.get().finishedLogRecordItems + } + + fun getFinishedMetrics(): List { + return metricExporter.get().finishedMetricItems + } + + override fun getSpanExporter(): SpanExporter? { + return spanExporter.get() + } + + override fun getLogRecordExporter(): LogRecordExporter? { + return logRecordExporter.get() + } + + override fun getMetricExporter(): MetricExporter? { + return metricExporter.get() + } +} diff --git a/internal-tools/otel-test-common/src/main/java/co/elastic/otel/android/test/processor/SimpleProcessorFactory.kt b/internal-tools/otel-test-common/src/main/java/co/elastic/otel/android/test/processor/SimpleProcessorFactory.kt new file mode 100644 index 000000000..9588b04b7 --- /dev/null +++ b/internal-tools/otel-test-common/src/main/java/co/elastic/otel/android/test/processor/SimpleProcessorFactory.kt @@ -0,0 +1,35 @@ +package co.elastic.otel.android.test.processor + +import co.elastic.otel.android.api.flusher.MetricFlusher +import co.elastic.otel.android.processors.ProcessorFactory +import io.opentelemetry.sdk.common.CompletableResultCode +import io.opentelemetry.sdk.logs.LogRecordProcessor +import io.opentelemetry.sdk.logs.export.LogRecordExporter +import io.opentelemetry.sdk.logs.export.SimpleLogRecordProcessor +import io.opentelemetry.sdk.metrics.export.MetricExporter +import io.opentelemetry.sdk.metrics.export.MetricReader +import io.opentelemetry.sdk.metrics.export.PeriodicMetricReader +import io.opentelemetry.sdk.trace.SpanProcessor +import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor +import io.opentelemetry.sdk.trace.export.SpanExporter + +class SimpleProcessorFactory : ProcessorFactory, MetricFlusher { + private lateinit var metricReader: PeriodicMetricReader + + override fun createSpanProcessor(exporter: SpanExporter?): SpanProcessor? { + return SimpleSpanProcessor.create(exporter) + } + + override fun createLogRecordProcessor(exporter: LogRecordExporter?): LogRecordProcessor? { + return SimpleLogRecordProcessor.create(exporter) + } + + override fun createMetricReader(exporter: MetricExporter?): MetricReader { + metricReader = PeriodicMetricReader.create(exporter) + return metricReader + } + + override fun flushMetrics(): CompletableResultCode { + return metricReader.forceFlush() + } +} diff --git a/sample-app/app/androidtest-rules.pro b/sample-app/app/androidtest-rules.pro new file mode 100644 index 000000000..77e3e90a8 --- /dev/null +++ b/sample-app/app/androidtest-rules.pro @@ -0,0 +1,3 @@ +-dontwarn org.assertj.core.**.* +-dontwarn org.junit.jupiter.**.* +-dontwarn javax.annotation.** diff --git a/sample-app/app/build.gradle.kts b/sample-app/app/build.gradle.kts index 1fcfec292..c9821f98e 100644 --- a/sample-app/app/build.gradle.kts +++ b/sample-app/app/build.gradle.kts @@ -17,10 +17,22 @@ android { versionCode = 1 versionName = "1.0" - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + testInstrumentationRunner = "co.elastic.otel.android.sample.tools.SampleAppJunitRunner" } buildTypes { + debug { + if (project.hasProperty("elastic.testing.automated")) { + logger.warn("Building debug with minify enabled for instrumentation tests") + isMinifyEnabled = true + isDebuggable = false + testProguardFiles( + file("androidtest-rules.pro"), + rootProject.file("../shared-rules.pro") + ) + proguardFiles(file("test-rules.pro")) + } + } release { isMinifyEnabled = true signingConfig = signingConfigs["debug"] @@ -44,4 +56,6 @@ dependencies { implementation("androidx.navigation:navigation-ui-ktx:2.8.7") implementation("com.squareup.retrofit2:retrofit:$retrofitVersion") implementation("com.squareup.retrofit2:converter-gson:$retrofitVersion") + androidTestImplementation("co.elastic.otel.android:otel-test-common") + androidTestImplementation(instrumentationLibs.bundles.androidTest) } \ No newline at end of file diff --git a/sample-app/app/src/androidTest/java/co/elastic/otel/android/sample/OtelApisTest.kt b/sample-app/app/src/androidTest/java/co/elastic/otel/android/sample/OtelApisTest.kt new file mode 100644 index 000000000..f6f08461f --- /dev/null +++ b/sample-app/app/src/androidTest/java/co/elastic/otel/android/sample/OtelApisTest.kt @@ -0,0 +1,88 @@ +package co.elastic.otel.android.sample + +import co.elastic.otel.android.test.exporter.InMemoryExporterProvider +import io.opentelemetry.api.OpenTelemetry +import io.opentelemetry.api.common.Attributes +import io.opentelemetry.sdk.OpenTelemetrySdk +import io.opentelemetry.sdk.logs.SdkLoggerProvider +import io.opentelemetry.sdk.logs.export.BatchLogRecordProcessor +import io.opentelemetry.sdk.metrics.SdkMeterProvider +import io.opentelemetry.sdk.metrics.export.PeriodicMetricReader +import io.opentelemetry.sdk.trace.SdkTracerProvider +import io.opentelemetry.sdk.trace.export.BatchSpanProcessor +import java.util.concurrent.TimeUnit +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Test + +class OtelApisTest { + private lateinit var inMemoryExporterProvider: InMemoryExporterProvider + private lateinit var spanProcessor: BatchSpanProcessor + private lateinit var metricReader: PeriodicMetricReader + private lateinit var logRecordProcessor: BatchLogRecordProcessor + private lateinit var openTelemetry: OpenTelemetry + + @Before + fun setUp() { + inMemoryExporterProvider = InMemoryExporterProvider() + spanProcessor = + BatchSpanProcessor.builder(inMemoryExporterProvider.getSpanExporter()).build() + metricReader = + PeriodicMetricReader.builder(inMemoryExporterProvider.getMetricExporter()).build() + logRecordProcessor = + BatchLogRecordProcessor.builder(inMemoryExporterProvider.getLogRecordExporter()).build() + + openTelemetry = OpenTelemetrySdk.builder() + .setTracerProvider(SdkTracerProvider.builder().addSpanProcessor(spanProcessor).build()) + .setMeterProvider(SdkMeterProvider.builder().registerMetricReader(metricReader).build()) + .setLoggerProvider( + SdkLoggerProvider.builder().addLogRecordProcessor(logRecordProcessor).build() + ) + .build() + } + + @Test + fun checkSpans() { + val span = openTelemetry.getTracer("spanscope").spanBuilder("mySpan") + .setAllAttributes(Attributes.builder().put("attrkey", "attr value").build()) + .startSpan() + val scope = span.makeCurrent() + scope.close() + span.end() + + flushSpans() + + assertEquals(1, inMemoryExporterProvider.getFinishedSpans().size) + } + + @Test + fun checkLogs() { + openTelemetry.logsBridge.get("logscope").logRecordBuilder().setBody("log body").emit() + + flushLogs() + + assertEquals(1, inMemoryExporterProvider.getFinishedLogRecords().size) + } + + @Test + fun checkMetrics() { + val counter = openTelemetry.getMeter("metricscope").counterBuilder("counter").build() + counter.add(1) + + flushMetrics() + + assertEquals(1, inMemoryExporterProvider.getFinishedMetrics().size) + } + + private fun flushSpans() { + spanProcessor.forceFlush().join(1, TimeUnit.SECONDS) + } + + private fun flushLogs() { + logRecordProcessor.forceFlush().join(1, TimeUnit.SECONDS) + } + + private fun flushMetrics() { + metricReader.forceFlush().join(1, TimeUnit.SECONDS) + } +} \ No newline at end of file diff --git a/sample-app/app/src/androidTest/java/co/elastic/otel/android/sample/tools/SampleAppJunitRunner.kt b/sample-app/app/src/androidTest/java/co/elastic/otel/android/sample/tools/SampleAppJunitRunner.kt new file mode 100644 index 000000000..49fd80cb4 --- /dev/null +++ b/sample-app/app/src/androidTest/java/co/elastic/otel/android/sample/tools/SampleAppJunitRunner.kt @@ -0,0 +1,9 @@ +package co.elastic.otel.android.sample.tools + +import androidx.test.runner.AndroidJUnitRunner + +class SampleAppJunitRunner : AndroidJUnitRunner() { + init { + System.setProperty("io.opentelemetry.context.contextStorageProvider", "default") + } +} \ No newline at end of file diff --git a/sample-app/app/src/main/java/co/elastic/otel/android/sample/MyApp.kt b/sample-app/app/src/main/java/co/elastic/otel/android/sample/MyApp.kt index 79977301f..1d21766e4 100644 --- a/sample-app/app/src/main/java/co/elastic/otel/android/sample/MyApp.kt +++ b/sample-app/app/src/main/java/co/elastic/otel/android/sample/MyApp.kt @@ -4,6 +4,7 @@ import android.app.Application import co.elastic.otel.android.ElasticApmAgent import co.elastic.otel.android.api.ElasticOtelAgent import co.elastic.otel.android.extensions.log +import co.elastic.otel.android.extensions.span class MyApp : Application() { companion object { @@ -18,6 +19,8 @@ class MyApp : Application() { .setServiceName("weather-sample-app") .build() - agent.log("App created") + agent.span("Creating app") { + agent.log("During app creation") + } } } \ No newline at end of file diff --git a/sample-app/app/src/main/java/co/elastic/otel/android/sample/ui/MainActivity.kt b/sample-app/app/src/main/java/co/elastic/otel/android/sample/ui/MainActivity.kt index ad02607b5..f5a27805d 100644 --- a/sample-app/app/src/main/java/co/elastic/otel/android/sample/ui/MainActivity.kt +++ b/sample-app/app/src/main/java/co/elastic/otel/android/sample/ui/MainActivity.kt @@ -5,11 +5,12 @@ import androidx.appcompat.app.AppCompatActivity import androidx.navigation.findNavController import androidx.navigation.ui.AppBarConfiguration import androidx.navigation.ui.setupActionBarWithNavController -import co.elastic.otel.android.sample.MyApp.Companion.agent import co.elastic.otel.android.extensions.log import co.elastic.otel.android.extensions.span +import co.elastic.otel.android.sample.MyApp.Companion.agent import co.elastic.otel.android.sample.R import co.elastic.otel.android.sample.databinding.ActivityMainBinding +import io.opentelemetry.api.common.Attributes class MainActivity : AppCompatActivity() { @@ -29,7 +30,10 @@ class MainActivity : AppCompatActivity() { setupActionBarWithNavController(navController, appBarConfiguration) binding.fab.setOnClickListener { view -> - agent.log("Button click") + agent.log( + "Button click", + attributes = Attributes.builder().put("activity.name", "MainActivity").build() + ) } } } diff --git a/sample-app/app/test-rules.pro b/sample-app/app/test-rules.pro new file mode 100644 index 000000000..09c6602e9 --- /dev/null +++ b/sample-app/app/test-rules.pro @@ -0,0 +1,22 @@ +-keepnames class io.opentelemetry.sdk.metrics.export.PeriodicMetricReader { builder(io.opentelemetry.sdk.metrics.export.MetricExporter);forceFlush(); } +-keepnames class io.opentelemetry.sdk.metrics.export.PeriodicMetricReaderBuilder { build(); } +-keepnames class io.opentelemetry.sdk.trace.export.BatchSpanProcessor { builder(io.opentelemetry.sdk.trace.export.SpanExporter); } +-keepnames class io.opentelemetry.sdk.trace.export.BatchSpanProcessorBuilder { build(); } +-keepnames class io.opentelemetry.sdk.logs.export.BatchLogRecordProcessor { builder(io.opentelemetry.sdk.logs.export.LogRecordExporter); } +-keepnames class io.opentelemetry.sdk.logs.export.BatchLogRecordProcessorBuilder { build(); } +-keepnames class io.opentelemetry.sdk.OpenTelemetrySdk { builder(); } +-keepnames class io.opentelemetry.sdk.OpenTelemetrySdkBuilder { build();setTracerProvider(io.opentelemetry.sdk.trace.SdkTracerProvider);setLoggerProvider(io.opentelemetry.sdk.logs.SdkLoggerProvider);setMeterProvider(io.opentelemetry.sdk.metrics.SdkMeterProvider); } +-keepnames class io.opentelemetry.sdk.trace.SdkTracerProvider { builder(); } +-keepnames class io.opentelemetry.sdk.trace.SdkTracerProviderBuilder { addSpanProcessor(io.opentelemetry.sdk.trace.SpanProcessor);build(); } +-keepnames class io.opentelemetry.sdk.metrics.SdkMeterProvider { builder(); } +-keepnames class io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder { build();registerMetricReader(io.opentelemetry.sdk.metrics.export.MetricReader); } +-keepnames class io.opentelemetry.sdk.logs.SdkLoggerProvider { builder(); } +-keepnames class io.opentelemetry.sdk.logs.SdkLoggerProviderBuilder { build();addLogRecordProcessor(io.opentelemetry.sdk.logs.LogRecordProcessor); } +-keepnames class io.opentelemetry.api.common.Attributes { builder(); } +-keepnames class io.opentelemetry.api.common.AttributesBuilder { put(java.lang.String,java.lang.String); } +-keepnames class io.opentelemetry.context.ImplicitContextKeyed { makeCurrent(); } +-keepclassmembers class io.opentelemetry.api.metrics.LongCounter { add(long); } +-keepclassmembers class io.opentelemetry.api.OpenTelemetry { getTracer(java.lang.String);getLogsBridge();getMeter(java.lang.String); } +-keepclassmembers class io.opentelemetry.sdk.common.CompletableResultCode { join(long,java.util.concurrent.TimeUnit);ofSuccess(); } +-keep class kotlin.** { *; } +-keep class androidx.tracing.** { *; } diff --git a/sample-app/settings.gradle.kts b/sample-app/settings.gradle.kts index 60c36525b..0549e667d 100644 --- a/sample-app/settings.gradle.kts +++ b/sample-app/settings.gradle.kts @@ -1,5 +1,3 @@ -import org.gradle.api.initialization.resolve.RepositoriesMode - pluginManagement { repositories { mavenCentral() @@ -12,6 +10,9 @@ dependencyResolutionManagement { create("rootLibs") { from(files("../gradle/libs.versions.toml")) } + create("instrumentationLibs") { + from(files("../gradle/instrumentation.versions.toml")) + } } repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { diff --git a/shared-rules.pro b/shared-rules.pro index e42d73a36..74dbcdfec 100644 --- a/shared-rules.pro +++ b/shared-rules.pro @@ -1,12 +1,14 @@ -# Keep everything from open telemetry --keep class io.opentelemetry.** { *; } - +-keepclassmembers enum io.opentelemetry.** { + public static **[] values(); +} +-keepclassmembers enum co.elastic.otel.android.** { + public static **[] values(); +} -dontwarn com.fasterxml.jackson.** +-dontwarn com.google.auto.service.AutoService -dontwarn com.google.auto.value.** -dontwarn com.google.common.io.ByteStreams -dontwarn com.google.errorprone.annotations.** -dontwarn io.grpc.** -dontwarn java.awt.** -dontwarn javax.json.bind.spi.JsonbProvider --dontwarn org.osgi.annotation.bundle.Export --dontwarn com.google.auto.service.AutoService