diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bb90c81d3..effdfddc6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,8 +35,18 @@ jobs: - uses: ./.github/actions/setup - name: Build run: ./gradlew assemble - - name: Test - run: ./tests.sh + - name: Checks and unit tests + run: ./checks.sh + - name: Enable KVM for Android tests + run: | + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm + - name: Android tests + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: 29 + script: ./android_tests.sh - name: Store test results if: success() || failure() uses: actions/upload-artifact@v4 @@ -45,6 +55,7 @@ jobs: path: | **/build/test-results/testRelease*/TEST-*.xml **/build/test-results/test/TEST-*.xml + **/build/outputs/androidTest-results/**/TEST-*.xml # The very last job to report whether the Workflow passed. # This will act as the Branch Protection gatekeeper diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2d4bf57ca..6fd343474 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -68,16 +68,28 @@ if the author does not have commit access, should "Squash and merge". You can run the following command to run all the unit tests available: ```text -./tests.sh +./checks.sh +``` + +For instrumentation tests, you'd need to have an Android Emulator running on your host machine, or a +device connected with developer mode enabled. Once that's available, you can run the following +command to execute those tests: + +```text +./android_tests.sh ``` ### Releasing -The release steps have been defined in `.ci/release.sh` which it gets triggered within the BuildKite context. +The release steps have been defined in `.ci/release.sh` which it gets triggered within the BuildKite +context. -Releases are triggered manually using GitHub actions, and the GitHub action is the one contacting BuildKite. +Releases are triggered manually using GitHub actions, and the GitHub action is the one contacting +BuildKite. To run a release then -* Navigate to the [GitHub job](https://github.com/elastic/apm-agent-android/actions/workflows/release.yml) + +* Navigate to + the [GitHub job](https://github.com/elastic/apm-agent-android/actions/workflows/release.yml) * Choose Run workflow. * Fill the form and click `Run workflow` and wait for a few minutes to complete diff --git a/android-api/src/main/java/co/elastic/otel/android/api/clock/ClockProvider.kt b/android-api/src/main/java/co/elastic/otel/android/api/clock/ClockProvider.kt index b61f81ada..624814697 100644 --- a/android-api/src/main/java/co/elastic/otel/android/api/clock/ClockProvider.kt +++ b/android-api/src/main/java/co/elastic/otel/android/api/clock/ClockProvider.kt @@ -1,3 +1,21 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ package co.elastic.otel.android.api.clock import io.opentelemetry.sdk.common.Clock diff --git a/android-api/src/main/java/co/elastic/otel/android/api/flusher/LogRecordFlusher.kt b/android-api/src/main/java/co/elastic/otel/android/api/flusher/LogRecordFlusher.kt index d73875b8c..66dff4e8b 100644 --- a/android-api/src/main/java/co/elastic/otel/android/api/flusher/LogRecordFlusher.kt +++ b/android-api/src/main/java/co/elastic/otel/android/api/flusher/LogRecordFlusher.kt @@ -1,3 +1,21 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ package co.elastic.otel.android.api.flusher import io.opentelemetry.sdk.common.CompletableResultCode diff --git a/android-api/src/main/java/co/elastic/otel/android/api/flusher/MetricFlusher.kt b/android-api/src/main/java/co/elastic/otel/android/api/flusher/MetricFlusher.kt index e79ba24d8..c951d2dde 100644 --- a/android-api/src/main/java/co/elastic/otel/android/api/flusher/MetricFlusher.kt +++ b/android-api/src/main/java/co/elastic/otel/android/api/flusher/MetricFlusher.kt @@ -1,3 +1,21 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ package co.elastic.otel.android.api.flusher import io.opentelemetry.sdk.common.CompletableResultCode diff --git a/android-api/src/main/java/co/elastic/otel/android/api/flusher/SpanFlusher.kt b/android-api/src/main/java/co/elastic/otel/android/api/flusher/SpanFlusher.kt index 210efac83..06310aefd 100644 --- a/android-api/src/main/java/co/elastic/otel/android/api/flusher/SpanFlusher.kt +++ b/android-api/src/main/java/co/elastic/otel/android/api/flusher/SpanFlusher.kt @@ -1,3 +1,21 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ package co.elastic.otel.android.api.flusher import io.opentelemetry.sdk.common.CompletableResultCode diff --git a/android-plugin/build.gradle.kts b/android-plugin/build.gradle.kts index 8737bbfe6..703ae32f1 100644 --- a/android-plugin/build.gradle.kts +++ b/android-plugin/build.gradle.kts @@ -8,7 +8,7 @@ dependencies { api(project(":android-common")) implementation(libs.byteBuddy) implementation(libs.byteBuddy.plugin) - compileOnly("com.android.tools.build:gradle:${project.property("androidGradlePlugin_version")}") + compileOnly(libs.android.plugin) } buildConfig { diff --git a/android-plugin/src/main/java/co/elastic/otel/android/plugin/extensions/BytecodeInstrumentation.kt b/android-plugin/src/main/java/co/elastic/otel/android/plugin/extensions/BytecodeInstrumentation.kt index eb2a1daad..f47f8cdad 100644 --- a/android-plugin/src/main/java/co/elastic/otel/android/plugin/extensions/BytecodeInstrumentation.kt +++ b/android-plugin/src/main/java/co/elastic/otel/android/plugin/extensions/BytecodeInstrumentation.kt @@ -1,3 +1,21 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ package co.elastic.otel.android.plugin.extensions import org.gradle.api.provider.ListProperty diff --git a/android-plugin/src/main/java/co/elastic/otel/android/plugin/extensions/ElasticApmExtension.kt b/android-plugin/src/main/java/co/elastic/otel/android/plugin/extensions/ElasticApmExtension.kt index f65f94071..bc9a25f54 100644 --- a/android-plugin/src/main/java/co/elastic/otel/android/plugin/extensions/ElasticApmExtension.kt +++ b/android-plugin/src/main/java/co/elastic/otel/android/plugin/extensions/ElasticApmExtension.kt @@ -1,3 +1,21 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ package co.elastic.otel.android.plugin.extensions import javax.inject.Inject diff --git a/android-plugin/src/main/java/co/elastic/otel/android/plugin/internal/BuildVariantListener.kt b/android-plugin/src/main/java/co/elastic/otel/android/plugin/internal/BuildVariantListener.kt index cd78b163d..2f710ca83 100644 --- a/android-plugin/src/main/java/co/elastic/otel/android/plugin/internal/BuildVariantListener.kt +++ b/android-plugin/src/main/java/co/elastic/otel/android/plugin/internal/BuildVariantListener.kt @@ -1,3 +1,21 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ package co.elastic.otel.android.plugin.internal interface BuildVariantListener { diff --git a/android-plugin/src/main/java/co/elastic/otel/android/plugin/internal/ByteBuddyDependencyAttacher.kt b/android-plugin/src/main/java/co/elastic/otel/android/plugin/internal/ByteBuddyDependencyAttacher.kt index 4ebb15ca2..561874af8 100644 --- a/android-plugin/src/main/java/co/elastic/otel/android/plugin/internal/ByteBuddyDependencyAttacher.kt +++ b/android-plugin/src/main/java/co/elastic/otel/android/plugin/internal/ByteBuddyDependencyAttacher.kt @@ -1,3 +1,21 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ package co.elastic.otel.android.plugin.internal import org.gradle.api.Project diff --git a/android-plugin/src/main/java/co/elastic/otel/android/plugin/internal/InstrumentationPlugin.kt b/android-plugin/src/main/java/co/elastic/otel/android/plugin/internal/InstrumentationPlugin.kt index bd507786f..d34e37172 100644 --- a/android-plugin/src/main/java/co/elastic/otel/android/plugin/internal/InstrumentationPlugin.kt +++ b/android-plugin/src/main/java/co/elastic/otel/android/plugin/internal/InstrumentationPlugin.kt @@ -1,3 +1,21 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ package co.elastic.otel.android.plugin.internal import co.elastic.otel.android.plugin.ElasticAgentPlugin diff --git a/android-sdk/src/main/java/co/elastic/otel/android/ElasticApmAgent.kt b/android-sdk/src/main/java/co/elastic/otel/android/ElasticApmAgent.kt index 65a88f9de..98ac5767b 100644 --- a/android-sdk/src/main/java/co/elastic/otel/android/ElasticApmAgent.kt +++ b/android-sdk/src/main/java/co/elastic/otel/android/ElasticApmAgent.kt @@ -52,6 +52,7 @@ import io.opentelemetry.api.common.Attributes import io.opentelemetry.sdk.common.CompletableResultCode import io.opentelemetry.sdk.logs.export.LogRecordExporter import io.opentelemetry.sdk.metrics.export.MetricExporter +import io.opentelemetry.sdk.resources.Resource import io.opentelemetry.sdk.trace.data.SpanData import io.opentelemetry.sdk.trace.export.SpanExporter @@ -202,6 +203,10 @@ class ElasticApmAgent internal constructor( managedAgentBuilder.setProcessorFactory(value) } + fun setResourceInterceptor(value: Interceptor) = apply { + managedAgentBuilder.setResourceInterceptor(value) + } + fun setHttpSpanInterceptor(value: Interceptor?) = apply { httpSpanInterceptor = value } diff --git a/android-sdk/src/main/java/co/elastic/otel/android/extensions/ElasticOtelAgentExtensions.kt b/android-sdk/src/main/java/co/elastic/otel/android/extensions/ElasticOtelAgentExtensions.kt new file mode 100644 index 000000000..1d7cdb16e --- /dev/null +++ b/android-sdk/src/main/java/co/elastic/otel/android/extensions/ElasticOtelAgentExtensions.kt @@ -0,0 +1,77 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package co.elastic.otel.android.extensions + +import co.elastic.otel.android.api.ElasticOtelAgent +import io.opentelemetry.api.common.Attributes +import io.opentelemetry.api.logs.Severity +import io.opentelemetry.api.trace.Span +import io.opentelemetry.api.trace.SpanKind +import io.opentelemetry.api.trace.StatusCode +import io.opentelemetry.context.Context +import io.opentelemetry.context.Scope +import java.time.Instant + +private const val SCOPE_NAME = "ElasticAgentExtension" + +fun ElasticOtelAgent.log( + body: String, + severity: Severity? = null, + severityText: String? = null, + attributes: Attributes? = null, + context: Context? = null, + observedTimestamp: Instant? = null, + timestamp: Instant? = null +) { + val logger = getOpenTelemetry().logsBridge.get(SCOPE_NAME).logRecordBuilder() + .setBody(body) + severity?.let { logger.setSeverity(it) } + severityText?.let { logger.setSeverityText(it) } + attributes?.let { logger.setAllAttributes(it) } + context?.let { logger.setContext(it) } + observedTimestamp?.let { logger.setObservedTimestamp(it) } + timestamp?.let { logger.setTimestamp(it) } + logger.emit() +} + +fun ElasticOtelAgent.span( + name: String, + attributes: Attributes? = null, + kind: SpanKind? = null, + parentContext: Context? = null, + makeCurrent: Boolean = true, + body: (Span) -> Unit +) { + val builder = getOpenTelemetry().getTracer(SCOPE_NAME).spanBuilder(name) + attributes?.let { builder.setAllAttributes(it) } + kind?.let { builder.setSpanKind(it) } + parentContext?.let { builder.setParent(it) } + + val span = builder.startSpan() + val scope: Scope? = if (makeCurrent) span.makeCurrent() else null + try { + body(span) + } catch (e: Throwable) { + span.setStatus(StatusCode.ERROR) + span.recordException(e) + } finally { + scope?.close() + span.end() + } +} \ No newline at end of file diff --git a/android-sdk/src/main/java/co/elastic/otel/android/internal/api/ManagedElasticOtelAgent.kt b/android-sdk/src/main/java/co/elastic/otel/android/internal/api/ManagedElasticOtelAgent.kt index 5238975e3..6c9512c75 100644 --- a/android-sdk/src/main/java/co/elastic/otel/android/internal/api/ManagedElasticOtelAgent.kt +++ b/android-sdk/src/main/java/co/elastic/otel/android/internal/api/ManagedElasticOtelAgent.kt @@ -44,6 +44,7 @@ import io.opentelemetry.api.common.Attributes import io.opentelemetry.sdk.common.CompletableResultCode import io.opentelemetry.sdk.logs.export.LogRecordExporter import io.opentelemetry.sdk.metrics.export.MetricExporter +import io.opentelemetry.sdk.resources.Resource import io.opentelemetry.sdk.trace.export.SpanExporter import java.util.UUID @@ -185,6 +186,10 @@ class ManagedElasticOtelAgent private constructor( elasticOpenTelemetryBuilder.setDeviceIdProvider(value) } + fun setResourceInterceptor(value: Interceptor) = apply { + elasticOpenTelemetryBuilder.setResourceInterceptor(value) + } + fun addSpanAttributesInterceptor(value: Interceptor) = apply { elasticOpenTelemetryBuilder.addSpanAttributesInterceptor(value) } diff --git a/android-sdk/src/main/java/co/elastic/otel/android/internal/features/diskbuffering/DiskBufferingConfiguration.kt b/android-sdk/src/main/java/co/elastic/otel/android/internal/features/diskbuffering/DiskBufferingConfiguration.kt index dc95d18c1..b8a0f417e 100644 --- a/android-sdk/src/main/java/co/elastic/otel/android/internal/features/diskbuffering/DiskBufferingConfiguration.kt +++ b/android-sdk/src/main/java/co/elastic/otel/android/internal/features/diskbuffering/DiskBufferingConfiguration.kt @@ -20,7 +20,7 @@ package co.elastic.otel.android.internal.features.diskbuffering data class DiskBufferingConfiguration private constructor(val enabled: Boolean) { internal var maxCacheFileSize = 1024 * 1024 - internal var maxCacheSize = 30 * 1024 * 1024 // 30 MB + internal var maxCacheSize = 15 * 1024 * 1024 // 15 MB internal var maxFileAgeForWrite: Long? = null internal var minFileAgeForRead: Long? = null diff --git a/android-sdk/src/main/java/co/elastic/otel/android/internal/features/diskbuffering/DiskBufferingManager.kt b/android-sdk/src/main/java/co/elastic/otel/android/internal/features/diskbuffering/DiskBufferingManager.kt index 8bd766638..f222c4a17 100644 --- a/android-sdk/src/main/java/co/elastic/otel/android/internal/features/diskbuffering/DiskBufferingManager.kt +++ b/android-sdk/src/main/java/co/elastic/otel/android/internal/features/diskbuffering/DiskBufferingManager.kt @@ -57,9 +57,11 @@ internal class DiskBufferingManager private constructor( private var toDiskLogRecordExporter: LogRecordToDiskExporter? = null private var toDiskMetricExporter: MetricToDiskExporter? = null private var signalFromDiskExporter: SignalFromDiskExporter? = null + private val logger = Elog.getLogger() private fun exportFromDisk() { - signalFromDiskExporter?.exportBatchOfEach() + val exported = signalFromDiskExporter?.exportBatchOfEach() + logger.debug("Signals exported from disk: {}", exported) } internal fun close() { @@ -127,7 +129,7 @@ internal class DiskBufferingManager private constructor( enableDiskBuffering(configuration.enabled) startExportSchedule() } catch (e: IOException) { - Elog.getLogger().error("Could not initialize disk buffering", e) + logger.error("Could not initialize disk buffering", e) } finally { openLatch() } @@ -146,6 +148,7 @@ internal class DiskBufferingManager private constructor( } private fun enableDiskBuffering(enabled: Boolean) { + logger.debug("Disk buffering enabled: {}", enabled) if (enabled) { toDiskSpanExporter?.let { spanExporter!!.setDelegate(it) @@ -194,6 +197,8 @@ internal class DiskBufferingManager private constructor( val builder = StorageConfiguration.builder() .setMaxFileSize(diskManager.getMaxCacheFileSize()) .setMaxFolderSize(diskManager.getMaxFolderSize()) + .setMaxFileAgeForWriteMillis(TimeUnit.SECONDS.toMillis(2)) + .setMinFileAgeForReadMillis(TimeUnit.SECONDS.toMillis(4)) .setTemporaryFileProvider( SimpleTemporaryFileProvider( systemTimeProvider, diff --git a/android-sdk/src/main/java/co/elastic/otel/android/internal/features/diskbuffering/tools/DiskManager.kt b/android-sdk/src/main/java/co/elastic/otel/android/internal/features/diskbuffering/tools/DiskManager.kt index dbfa9fa13..42ec45a29 100644 --- a/android-sdk/src/main/java/co/elastic/otel/android/internal/features/diskbuffering/tools/DiskManager.kt +++ b/android-sdk/src/main/java/co/elastic/otel/android/internal/features/diskbuffering/tools/DiskManager.kt @@ -18,17 +18,14 @@ */ package co.elastic.otel.android.internal.features.diskbuffering.tools -import co.elastic.otel.android.common.internal.logging.Elog import co.elastic.otel.android.internal.features.diskbuffering.DiskBufferingConfiguration import co.elastic.otel.android.internal.services.ServiceManager import co.elastic.otel.android.internal.services.appinfo.AppInfoService -import co.elastic.otel.android.internal.services.preferences.PreferencesService import java.io.File import java.io.IOException internal class DiskManager internal constructor( private val appInfoService: AppInfoService, - private val preferencesService: PreferencesService, private val diskBufferingConfiguration: DiskBufferingConfiguration ) { @@ -56,25 +53,7 @@ internal class DiskManager internal constructor( } fun getMaxFolderSize(): Int { - val storedSize = preferencesService.retrieveInt(MAX_FOLDER_SIZE_KEY, -1) - if (storedSize != -1) { - Elog.getLogger().debug("Returning max folder size from preferences: {}", storedSize) - return storedSize - } - val requestedSize = diskBufferingConfiguration.maxCacheSize - val availableCacheSize = - appInfoService.getAvailableCacheSpace(requestedSize.toLong()).toInt() - val calculatedSize = - (availableCacheSize / 3) - diskBufferingConfiguration.maxCacheFileSize - preferencesService.store(MAX_FOLDER_SIZE_KEY, calculatedSize) - - Elog.getLogger().debug( - "Requested cache size: {}, available cache size: {}, folder size: {}", - requestedSize, - availableCacheSize, - calculatedSize - ) - return calculatedSize + return diskBufferingConfiguration.maxCacheSize / 3 } fun getMaxCacheFileSize(): Int = diskBufferingConfiguration.maxCacheFileSize @@ -86,15 +65,12 @@ internal class DiskManager internal constructor( } companion object { - private const val MAX_FOLDER_SIZE_KEY = "max_signal_folder_size" - fun create( serviceManager: ServiceManager, diskBufferingConfiguration: DiskBufferingConfiguration ): DiskManager { return DiskManager( serviceManager.getAppInfoService(), - serviceManager.getPreferencesService(), diskBufferingConfiguration ) } diff --git a/android-sdk/src/main/java/co/elastic/otel/android/internal/opentelemetry/ElasticOpenTelemetry.kt b/android-sdk/src/main/java/co/elastic/otel/android/internal/opentelemetry/ElasticOpenTelemetry.kt index c08b20efc..958775462 100644 --- a/android-sdk/src/main/java/co/elastic/otel/android/internal/opentelemetry/ElasticOpenTelemetry.kt +++ b/android-sdk/src/main/java/co/elastic/otel/android/internal/opentelemetry/ElasticOpenTelemetry.kt @@ -35,6 +35,8 @@ import co.elastic.otel.android.provider.StringProvider import co.elastic.otel.android.session.SessionProvider import io.opentelemetry.api.common.AttributeKey import io.opentelemetry.api.common.Attributes +import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator +import io.opentelemetry.context.propagation.ContextPropagators import io.opentelemetry.sdk.OpenTelemetrySdk import io.opentelemetry.sdk.common.Clock import io.opentelemetry.sdk.logs.SdkLoggerProvider @@ -52,6 +54,7 @@ import io.opentelemetry.semconv.incubating.OsIncubatingAttributes import io.opentelemetry.semconv.incubating.ProcessIncubatingAttributes import java.util.UUID + class ElasticOpenTelemetry private constructor( val sdk: OpenTelemetrySdk, val serviceName: String, @@ -65,6 +68,7 @@ class ElasticOpenTelemetry private constructor( private var serviceBuild: Int? = null private var deploymentEnvironment: String? = null private var deviceIdProvider: StringProvider? = null + private var resourceInterceptor: Interceptor? = null private var spanAttributesInterceptors = mutableListOf>() private var logRecordAttributesInterceptors = mutableListOf>() private var spanExporterInterceptors = mutableListOf>() @@ -95,6 +99,10 @@ class ElasticOpenTelemetry private constructor( deviceIdProvider = value } + fun setResourceInterceptor(value: Interceptor) = apply { + resourceInterceptor = value + } + fun addSpanAttributesInterceptor(value: Interceptor) = apply { spanAttributesInterceptors.add(value) } @@ -145,7 +153,7 @@ class ElasticOpenTelemetry private constructor( } val finalProcessorFactory = processorFactory ?: DefaultProcessorFactory(serviceManager.getBackgroundWorkService()) - val resource = Resource.builder() + var resource = Resource.builder() .put(ServiceAttributes.SERVICE_NAME, serviceName) .put( ServiceAttributes.SERVICE_VERSION, @@ -172,6 +180,7 @@ class ElasticOpenTelemetry private constructor( .put(TelemetryAttributes.TELEMETRY_SDK_VERSION, BuildConfig.APM_AGENT_VERSION) .put(TelemetryAttributes.TELEMETRY_SDK_LANGUAGE, "java") .build() + resource = resourceInterceptor?.intercept(resource) ?: resource val openTelemetryBuilder = OpenTelemetrySdk.builder() val spanExporter = exporterProvider.getSpanExporter()?.let { Interceptor.composite(spanExporterInterceptors).intercept(it) @@ -225,6 +234,7 @@ class ElasticOpenTelemetry private constructor( .build() ) } + openTelemetryBuilder.setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance())) return ElasticOpenTelemetry( openTelemetryBuilder.build(), serviceName, diff --git a/android-sdk/src/main/java/co/elastic/otel/android/internal/opentelemetry/processors/DefaultProcessorFactory.kt b/android-sdk/src/main/java/co/elastic/otel/android/internal/opentelemetry/processors/DefaultProcessorFactory.kt index 9eb053526..7b3d1cf1d 100644 --- a/android-sdk/src/main/java/co/elastic/otel/android/internal/opentelemetry/processors/DefaultProcessorFactory.kt +++ b/android-sdk/src/main/java/co/elastic/otel/android/internal/opentelemetry/processors/DefaultProcessorFactory.kt @@ -35,8 +35,8 @@ internal class DefaultProcessorFactory(private val backgroundWorkService: Backgr ProcessorFactory { companion object { - private val PROCESSING_INTERVAL = Duration.ofSeconds(2) - private val READING_INTERVAL = Duration.ofSeconds(4) + private val PROCESSING_INTERVAL = Duration.ofSeconds(1) + private val READING_INTERVAL = Duration.ofSeconds(2) } override fun createSpanProcessor(exporter: SpanExporter?): SpanProcessor? { diff --git a/android-sdk/src/main/java/co/elastic/otel/android/internal/services/network/query/NetworkApi23QueryManager.kt b/android-sdk/src/main/java/co/elastic/otel/android/internal/services/network/query/NetworkApi23QueryManager.kt index f301d3ccb..c5c0ef8c6 100644 --- a/android-sdk/src/main/java/co/elastic/otel/android/internal/services/network/query/NetworkApi23QueryManager.kt +++ b/android-sdk/src/main/java/co/elastic/otel/android/internal/services/network/query/NetworkApi23QueryManager.kt @@ -18,7 +18,6 @@ */ package co.elastic.otel.android.internal.services.network.query -import android.annotation.TargetApi import android.net.ConnectivityManager import android.net.ConnectivityManager.NetworkCallback import android.net.Network @@ -27,9 +26,10 @@ import android.net.NetworkRequest import android.os.Build import android.telephony.TelephonyManager import androidx.annotation.GuardedBy +import androidx.annotation.RequiresApi import co.elastic.otel.android.internal.services.network.listener.NetworkChangeListener -@TargetApi(Build.VERSION_CODES.M) +@RequiresApi(Build.VERSION_CODES.M) internal class NetworkApi23QueryManager( private val connectivityManager: ConnectivityManager, private val telephonyManager: TelephonyManager diff --git a/android-sdk/src/main/java/co/elastic/otel/android/internal/services/network/query/NetworkApi24QueryManager.kt b/android-sdk/src/main/java/co/elastic/otel/android/internal/services/network/query/NetworkApi24QueryManager.kt index eddb4f1b1..6977d6391 100644 --- a/android-sdk/src/main/java/co/elastic/otel/android/internal/services/network/query/NetworkApi24QueryManager.kt +++ b/android-sdk/src/main/java/co/elastic/otel/android/internal/services/network/query/NetworkApi24QueryManager.kt @@ -18,16 +18,16 @@ */ package co.elastic.otel.android.internal.services.network.query -import android.annotation.TargetApi import android.net.ConnectivityManager import android.net.ConnectivityManager.NetworkCallback import android.net.Network import android.net.NetworkCapabilities import android.os.Build import android.telephony.TelephonyManager +import androidx.annotation.RequiresApi import co.elastic.otel.android.internal.services.network.listener.NetworkChangeListener -@TargetApi(Build.VERSION_CODES.N) +@RequiresApi(Build.VERSION_CODES.N) internal class NetworkApi24QueryManager( private val connectivityManager: ConnectivityManager, private val telephonyManager: TelephonyManager diff --git a/android-sdk/src/test/java/co/elastic/otel/android/functional/AttributesTest.kt b/android-sdk/src/test/java/co/elastic/otel/android/functional/AttributesTest.kt index 71db0434d..a41d13e08 100644 --- a/android-sdk/src/test/java/co/elastic/otel/android/functional/AttributesTest.kt +++ b/android-sdk/src/test/java/co/elastic/otel/android/functional/AttributesTest.kt @@ -27,6 +27,7 @@ import android.net.NetworkInfo import android.os.Build import android.telephony.TelephonyManager import co.elastic.otel.android.exporters.ExporterProvider +import co.elastic.otel.android.interceptor.Interceptor import co.elastic.otel.android.internal.opentelemetry.ElasticOpenTelemetry import co.elastic.otel.android.internal.services.ServiceManager import co.elastic.otel.android.internal.services.network.query.NetworkApi21QueryManager @@ -38,6 +39,7 @@ import co.elastic.otel.android.test.common.ElasticAttributes.getLogRecordDefault import co.elastic.otel.android.test.common.ElasticAttributes.getSpanDefaultAttributes import io.mockk.every import io.mockk.mockk +import io.opentelemetry.api.common.AttributeKey import io.opentelemetry.api.common.Attributes import io.opentelemetry.api.logs.LogRecordBuilder import io.opentelemetry.api.trace.SpanBuilder @@ -160,6 +162,55 @@ internal class AttributesTest : ExporterProvider, ProcessorFactory { assertThat(metricItems.first()).hasResource(expectedResource) } + @Test + fun `Check resources with interceptor`() { + initialize(resourceInterceptor = { + it.merge( + Resource.create( + Attributes.of( + AttributeKey.stringKey("custom.key"), + "custom value" + ) + ) + ) + }) + val expectedResource = Resource.builder() + .put("deployment.environment", "test") + .put("device.id", "device-id") + .put("device.manufacturer", DEVICE_MANUFACTURER) + .put("device.model.identifier", DEVICE_MODEL_NAME) + .put( + "os.description", + "Android ${Build.VERSION.RELEASE}, API level ${Build.VERSION.SDK_INT}, BUILD $OS_BUILD" + ) + .put("os.name", "Android") + .put("os.version", Build.VERSION.RELEASE) + .put("process.runtime.name", "Android Runtime") + .put("process.runtime.version", RUNTIME_VERSION) + .put("service.build", VERSION_CODE) + .put("service.name", "service-name") + .put("service.version", "0.0.0") + .put("telemetry.sdk.language", "java") + .put("telemetry.sdk.name", "android") + .put("telemetry.sdk.version", System.getProperty("agent_version")!!) + .put("custom.key", "custom value") + .build() + + sendSpan() + sendLog() + sendMetricCounter() + + val spanItems = getFinishedSpans() + val logItems = getFinishedLogRecords() + val metricItems = getFinishedMetrics() + assertThat(spanItems).hasSize(1) + assertThat(logItems).hasSize(1) + assertThat(metricItems).hasSize(1) + assertThat(spanItems.first()).hasResource(expectedResource) + assertThat(logItems.first()).hasResource(expectedResource) + assertThat(metricItems.first()).hasResource(expectedResource) + } + @Config(sdk = [21, 23, Config.NEWEST_SDK]) @Test fun `Check resources with not provided service version`() { @@ -397,7 +448,8 @@ internal class AttributesTest : ExporterProvider, ProcessorFactory { serviceName: String = "service-name", serviceVersion: String? = "0.0.0", deploymentEnvironment: String = "test", - clock: Clock = Clock.getDefault() + clock: Clock = Clock.getDefault(), + resourceInterceptor: Interceptor? = null ) { val serviceManager = ServiceManager.create(RuntimeEnvironment.getApplication()) val builder = ElasticOpenTelemetry.Builder() @@ -407,6 +459,7 @@ internal class AttributesTest : ExporterProvider, ProcessorFactory { .setSessionProvider { Session.create("session-id") } .setDeviceIdProvider { "device-id" } serviceVersion?.let { builder.setServiceVersion(it) } + resourceInterceptor?.let { builder.setResourceInterceptor(it) } builder.setExporterProvider(this) builder.setProcessorFactory(this) diff --git a/android-sdk/src/test/java/co/elastic/otel/android/internal/features/persistence/DiskManagerTest.kt b/android-sdk/src/test/java/co/elastic/otel/android/internal/features/persistence/DiskManagerTest.kt index 95728eded..10d4273ee 100644 --- a/android-sdk/src/test/java/co/elastic/otel/android/internal/features/persistence/DiskManagerTest.kt +++ b/android-sdk/src/test/java/co/elastic/otel/android/internal/features/persistence/DiskManagerTest.kt @@ -21,13 +21,8 @@ package co.elastic.otel.android.internal.features.persistence import co.elastic.otel.android.internal.features.diskbuffering.DiskBufferingConfiguration import co.elastic.otel.android.internal.features.diskbuffering.tools.DiskManager import co.elastic.otel.android.internal.services.appinfo.AppInfoService -import co.elastic.otel.android.internal.services.preferences.PreferencesService -import io.mockk.Runs -import io.mockk.clearMocks import io.mockk.every -import io.mockk.just import io.mockk.mockk -import io.mockk.verify import java.io.File import java.util.Objects import org.assertj.core.api.Assertions.assertThat @@ -41,7 +36,6 @@ class DiskManagerTest { @get:Rule var temporaryFolder: TemporaryFolder = TemporaryFolder() private lateinit var appInfoService: AppInfoService - private lateinit var preferencesService: PreferencesService private lateinit var diskBufferingConfiguration: DiskBufferingConfiguration private lateinit var diskManager: DiskManager private lateinit var cacheDir: File @@ -50,10 +44,9 @@ class DiskManagerTest { fun setUp() { cacheDir = temporaryFolder.newFolder("app_cache") appInfoService = mockk() - preferencesService = mockk() diskBufferingConfiguration = DiskBufferingConfiguration.enabled() every { appInfoService.getCacheDir() }.returns(cacheDir) - diskManager = DiskManager(appInfoService, preferencesService, diskBufferingConfiguration) + diskManager = DiskManager(appInfoService, diskBufferingConfiguration) } @Test @@ -95,24 +88,12 @@ class DiskManagerTest { @Test fun getMaxSignalFolderSize() { - val maxCacheSize = (10 * 1024 * 1024).toLong() // 10 MB - val maxCacheFileSize = 1024 * 1024 // 1 MB + val maxCacheSize = (15 * 1024 * 1024).toLong() // 15 MB diskBufferingConfiguration.maxCacheSize = maxCacheSize.toInt() - diskBufferingConfiguration.maxCacheFileSize = maxCacheFileSize - every { appInfoService.getAvailableCacheSpace(maxCacheSize) }.returns(maxCacheSize) - every { preferencesService.retrieveInt(MAX_FOLDER_SIZE_KEY, -1) }.returns(-1) - every { preferencesService.store(MAX_FOLDER_SIZE_KEY, any()) } just Runs - // Expects the size of a single signal type folder minus the size of a cache file, to use as temporary space for reading. - val expected = 2446677 - assertThat(expected.toLong()).isEqualTo(diskManager.getMaxFolderSize().toLong()) - verify { preferencesService.store(MAX_FOLDER_SIZE_KEY, expected) } - - // On a second call, should get the value from the preferences. - clearMocks(appInfoService, preferencesService) - every { preferencesService.retrieveInt(MAX_FOLDER_SIZE_KEY, -1) }.returns(expected) - assertThat(expected.toLong()).isEqualTo(diskManager.getMaxFolderSize().toLong()) - verify { preferencesService.retrieveInt(MAX_FOLDER_SIZE_KEY, -1) } + // Expects the size of a single signal type folder. + val expected = 5242880L + assertThat(expected).isEqualTo(diskManager.getMaxFolderSize().toLong()) } companion object { diff --git a/android-test/buildSrc/build.gradle.kts b/android-test/buildSrc/build.gradle.kts index a4f375907..d67b43a2d 100644 --- a/android-test/buildSrc/build.gradle.kts +++ b/android-test/buildSrc/build.gradle.kts @@ -1,17 +1,9 @@ -import java.util.Properties - plugins { `kotlin-dsl` } -val properties = Properties() -val propertiesFile = File(rootDir, "../../gradle.properties") -propertiesFile.inputStream().use { - properties.load(it) -} - dependencies { implementation(rootLibs.kotlin.plugin) - implementation("com.android.tools.build:gradle:${properties.getProperty("androidGradlePlugin_version")}") + implementation(rootLibs.android.plugin) } diff --git a/android-test/instrumentation/okhttp/src/androidTest/java/co/elastic/otel/android/test/InstrumentationTest.kt b/android-test/instrumentation/okhttp/src/androidTest/java/co/elastic/otel/android/test/InstrumentationTest.kt index 20bb88c4a..b2fd2620d 100644 --- a/android-test/instrumentation/okhttp/src/androidTest/java/co/elastic/otel/android/test/InstrumentationTest.kt +++ b/android-test/instrumentation/okhttp/src/androidTest/java/co/elastic/otel/android/test/InstrumentationTest.kt @@ -46,11 +46,13 @@ class InstrumentationTest { @Test fun verifyOkHttpSyncCallSpan() { - val url = webServer.url("/") + val url = webServer.url("/some/path") executeSyncHttpCall("GET", 200, "{}", url) + val request = webServer.takeRequest() agentRule.flushSpans().join(5, TimeUnit.SECONDS) + assertThat(request.getHeader("traceparent")).isNotNull() assertThat(agentRule.getFinishedSpans()).hasSize(1) assertThat(agentRule.getFinishedSpans().first()).hasName("GET") .hasKind(SpanKind.CLIENT) diff --git a/android_tests.sh b/android_tests.sh new file mode 100755 index 000000000..0a9fa67bb --- /dev/null +++ b/android_tests.sh @@ -0,0 +1 @@ +./gradlew -p "android-test" connectedCheck \ No newline at end of file diff --git a/animalsniffer-signature/build.gradle.kts b/animalsniffer-signature/build.gradle.kts new file mode 100644 index 000000000..022e6b143 --- /dev/null +++ b/animalsniffer-signature/build.gradle.kts @@ -0,0 +1,70 @@ +import ru.vyarus.gradle.plugin.animalsniffer.info.SignatureInfoTask +import ru.vyarus.gradle.plugin.animalsniffer.signature.BuildSignatureTask + +plugins { + id("com.android.library") + id("ru.vyarus.animalsniffer") +} + +android { + namespace = "co.elastic.otel.android.animalsniffer.signature" + compileSdk = (property("elastic.android.compileSdk") as String).toInt() +} + +description = "Build tool to generate the Animal Sniffer Android signature" + +val signatureJar = + configurations.create("signatureJar") { + isCanBeConsumed = false + isCanBeResolved = false + } +val signatureJarClasspath = + configurations.create("signatureJarClasspath") { + isCanBeConsumed = false + isCanBeResolved = true + extendsFrom(signatureJar) + } +val generatedSignature = + configurations.create("generatedSignature") { + isCanBeConsumed = true + isCanBeResolved = false + } +configurations.add(signatureJar) +configurations.add(signatureJarClasspath) +configurations.add(generatedSignature) + +dependencies { + signature("com.toasttab.android:gummy-bears-api-${project.property("elastic.android.minSdk")}:0.9.0@signature") + signatureJar(libs.coreLib) +} + +val signatureSimpleName = "android.signature" +val signatureBuilderTask = + tasks.register("buildSignature", BuildSignatureTask::class.java) { + files(signatureJarClasspath) // All the jar files here will be added to the signature file. + signatures(configurations.signature) // We'll extend from the existing signatures added to this config. + outputName = signatureSimpleName // Name for the generated signature file. + } + +// Exposing the "generatedSignature" consumable config to be used in other subprojects +artifacts { + add( + "generatedSignature", + project.provider { + File( + signatureBuilderTask + .get() + .outputs.files.singleFile, + signatureSimpleName, + ) + }, + ) { + builtBy(signatureBuilderTask) + } +} + +// Utility task to show what's in the signature file +tasks.register("printSignature", SignatureInfoTask::class.java) { + signature = signatureBuilderTask.get().outputFiles + depth = 1 +} \ No newline at end of file diff --git a/animalsniffer-signature/metadata/notice.properties b/animalsniffer-signature/metadata/notice.properties new file mode 100644 index 000000000..c4167d836 --- /dev/null +++ b/animalsniffer-signature/metadata/notice.properties @@ -0,0 +1 @@ +dependencies.hash=D41D8CD98F00B204E9800998ECF8427E \ No newline at end of file diff --git a/animalsniffer-signature/src/main/resources/META-INF/NOTICE b/animalsniffer-signature/src/main/resources/META-INF/NOTICE new file mode 100644 index 000000000..cfc4e1d7c --- /dev/null +++ b/animalsniffer-signature/src/main/resources/META-INF/NOTICE @@ -0,0 +1,2 @@ +Elastic APM Android Agent +Copyright 2018-2022 Elasticsearch B.V. \ No newline at end of file diff --git a/build-tools/build.gradle.kts b/build-tools/build.gradle.kts index 0fd1492af..15cbd127e 100644 --- a/build-tools/build.gradle.kts +++ b/build-tools/build.gradle.kts @@ -1,16 +1,8 @@ -import java.util.Properties - plugins { id("java-gradle-plugin") `kotlin-dsl` } -val properties = Properties() -val propertiesFile = File(rootDir, "../gradle.properties") -propertiesFile.inputStream().use { - properties.load(it) -} - dependencies { implementation(rootLibs.apache.commons.text) implementation(rootLibs.commons.io) @@ -21,7 +13,8 @@ dependencies { implementation(rootLibs.gradle.shadow.plugin) implementation(rootLibs.kotlin.plugin) implementation(rootLibs.buildconfig.plugin) - implementation("com.android.tools.build:gradle:${properties.getProperty("androidGradlePlugin_version")}") + implementation(rootLibs.animalsniffer.plugin) + implementation(rootLibs.android.plugin) testImplementation(rootLibs.junit4) } diff --git a/build-tools/src/main/java/co/elastic/otel/android/compilation/tools/sourceheader/subplugins/AndroidSourceHeaderPlugin.java b/build-tools/src/main/java/co/elastic/otel/android/compilation/tools/sourceheader/subplugins/AndroidSourceHeaderPlugin.java index 25a734dbb..374c1938c 100644 --- a/build-tools/src/main/java/co/elastic/otel/android/compilation/tools/sourceheader/subplugins/AndroidSourceHeaderPlugin.java +++ b/build-tools/src/main/java/co/elastic/otel/android/compilation/tools/sourceheader/subplugins/AndroidSourceHeaderPlugin.java @@ -7,11 +7,6 @@ public class AndroidSourceHeaderPlugin extends BaseSourceHeaderPlugin { @Override public void apply(Project project) { super.apply(project); - spotlessExtension.java(javaExtension -> javaExtension.target("src/*/java/**/*.java")); - spotlessExtension.kotlin(kotlinExtension -> { - kotlinExtension.licenseHeader(getLicenseHeader()); - kotlinExtension.target("src/*/java/**/*.kt"); - }); project.afterEvaluate(self -> self.getTasks().getByName("preBuild", task -> task.dependsOn(getSpotlessApply(self)))); } } diff --git a/build-tools/src/main/java/co/elastic/otel/android/compilation/tools/sourceheader/subplugins/BaseSourceHeaderPlugin.java b/build-tools/src/main/java/co/elastic/otel/android/compilation/tools/sourceheader/subplugins/BaseSourceHeaderPlugin.java index 530f41da7..980a94d05 100644 --- a/build-tools/src/main/java/co/elastic/otel/android/compilation/tools/sourceheader/subplugins/BaseSourceHeaderPlugin.java +++ b/build-tools/src/main/java/co/elastic/otel/android/compilation/tools/sourceheader/subplugins/BaseSourceHeaderPlugin.java @@ -16,7 +16,14 @@ public void apply(Project project) { project.getPluginManager().apply(SpotlessPlugin.class); spotlessExtension = project.getExtensions().getByType(SpotlessExtension.class); - spotlessExtension.java(javaExtension -> javaExtension.licenseHeader(getLicenseHeader())); + spotlessExtension.java(javaExtension -> { + javaExtension.licenseHeader(getLicenseHeader()); + javaExtension.target("src/*/java/**/*.java"); + }); + spotlessExtension.kotlin(kotlinExtension -> { + kotlinExtension.licenseHeader(getLicenseHeader()); + kotlinExtension.target("src/*/java/**/*.kt"); + }); } protected String getLicenseHeader() { diff --git a/build-tools/src/main/kotlin/elastic.android-library.gradle.kts b/build-tools/src/main/kotlin/elastic.android-library.gradle.kts index b7529a91e..4e68d20cb 100644 --- a/build-tools/src/main/kotlin/elastic.android-library.gradle.kts +++ b/build-tools/src/main/kotlin/elastic.android-library.gradle.kts @@ -1,6 +1,7 @@ plugins { id("com.android.library") id("org.jetbrains.kotlin.android") + id("elastic.animalsniffer-android") } android { diff --git a/build-tools/src/main/kotlin/elastic.animalsniffer-android.gradle.kts b/build-tools/src/main/kotlin/elastic.animalsniffer-android.gradle.kts new file mode 100644 index 000000000..9585825f3 --- /dev/null +++ b/build-tools/src/main/kotlin/elastic.animalsniffer-android.gradle.kts @@ -0,0 +1,34 @@ +import java.util.Locale +import ru.vyarus.gradle.plugin.animalsniffer.AnimalSniffer + +plugins { + id("com.android.library") + id("ru.vyarus.animalsniffer") +} + +dependencies { + signature(project(path = ":animalsniffer-signature", configuration = "generatedSignature")) +} + +val capitalizedVariantNames = mutableListOf() +androidComponents.onVariants { variant -> + val capitalizedName = + variant.name.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() } + capitalizedVariantNames.add(capitalizedName) +} + +afterEvaluate { + capitalizedVariantNames.forEach { capitalizedName -> + tasks.named("pre${capitalizedName}Build").configure { + finalizedBy("animalsniffer$capitalizedName") + } + } +} + +// Any class, method or field annotated with this annotation will be ignored by animal sniffer. +animalsniffer.annotation = "androidx.annotation.RequiresApi" + +tasks.withType { + // always having declared output makes this task properly participate in tasks up-to-date checks + reports.text.required.set(true) +} \ No newline at end of file diff --git a/checks.sh b/checks.sh new file mode 100755 index 000000000..26d102638 --- /dev/null +++ b/checks.sh @@ -0,0 +1 @@ +./gradlew check \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index c76a2b208..34fcd2cd9 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,8 +1,7 @@ #Mon Jul 29 12:17:40 UTC 2024 -androidGradlePlugin_version=8.7.3 -description=APM for Android applications with the Elastic stack +description=OpenTelemetry Android agent for the Elastic stack org.gradle.jvmargs=-XX\:MaxMetaspaceSize\=2G -version=0.21.0 +version=1.0.0 android.useAndroidX=true elastic.android.minSdk=21 elastic.android.compileSdk=35 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6dfe3f4ae..d912da7e4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -34,6 +34,7 @@ awaitility = "org.awaitility:awaitility-kotlin:4.2.2" wireMock = "org.wiremock:wiremock:3.10.0" #Compilation tools +android-plugin = { module = "com.android.tools.build:gradle", version.ref = "android" } apache-commons-text = "org.apache.commons:commons-text:1.10.0" commons-io = "commons-io:commons-io:2.13.0" spotless-plugin = "com.diffplug.spotless:spotless-plugin-gradle:7.0.2" @@ -46,6 +47,7 @@ kotlin-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version. autoService-annotations = { module = "com.google.auto.service:auto-service-annotations", version.ref = "autoService" } autoService-compiler = { module = "com.google.auto.service:auto-service", version.ref = "autoService" } 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" [bundles] mocking = ["mockk"] 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 9fd93c472..3f077a7ce 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 @@ -1,3 +1,21 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ package co.elastic.otel.android.crash import co.elastic.otel.android.instrumentation.generated.BuildConfig 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 4997ed7cb..74793f748 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 @@ -1,3 +1,21 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ package co.elastic.otel.android.launchtime import co.elastic.otel.android.instrumentation.generated.BuildConfig 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 aaec5175f..209855e15 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 @@ -1,3 +1,21 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ package co.elastic.otel.android.okhttp import co.elastic.otel.android.instrumentation.generated.BuildConfig diff --git a/sample-app/app/build.gradle b/sample-app/app/build.gradle deleted file mode 100644 index 27f48d6d3..000000000 --- a/sample-app/app/build.gradle +++ /dev/null @@ -1,62 +0,0 @@ -plugins { - id 'com.android.application' - id 'org.jetbrains.kotlin.android' - id 'co.elastic.apm.android' -} - -android { - namespace 'co.elastic.apm.android.sample' - compileSdk 34 - - defaultConfig { - applicationId "co.elastic.apm.android.sample" - minSdk 26 - targetSdk 34 - versionCode 1 - versionName "1.0" - - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - } - - buildTypes { - release { - minifyEnabled true - signingConfig signingConfigs.debug - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - } - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - coreLibraryDesugaringEnabled true - } - kotlinOptions { - jvmTarget = '1.8' - } - buildFeatures { - viewBinding true - } -} - -elasticApm { - serviceName = "weather-sample-app" - serverUrl = "http://10.0.2.2:8200" // Your Elastic APM server endpoint. -// secretToken = "my-apm-secret-token" // Uncomment and set it if this is your preferred auth method. -} - -dependencies { - def lifecycle_version = "2.4.0" - def retrofit_version = "2.11.0" - coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4' - implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version" - implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" - implementation 'androidx.core:core-ktx:1.7.0' - implementation 'androidx.appcompat:appcompat:1.5.1' - implementation 'com.google.android.material:material:1.7.0' - implementation 'androidx.constraintlayout:constraintlayout:2.1.4' - implementation 'androidx.navigation:navigation-fragment-ktx:2.4.1' - implementation 'androidx.navigation:navigation-ui-ktx:2.4.1' - implementation "com.squareup.retrofit2:retrofit:$retrofit_version" - implementation "com.squareup.retrofit2:converter-gson:$retrofit_version" - testImplementation 'junit:junit:4.13.2' -} \ No newline at end of file diff --git a/sample-app/app/build.gradle.kts b/sample-app/app/build.gradle.kts new file mode 100644 index 000000000..1fcfec292 --- /dev/null +++ b/sample-app/app/build.gradle.kts @@ -0,0 +1,47 @@ +plugins { + id("com.android.application") + id("org.jetbrains.kotlin.android") + id("co.elastic.otel.android.agent") + id("co.elastic.otel.android.instrumentation-okhttp") + id("co.elastic.otel.android.instrumentation-launchtime") + id("co.elastic.otel.android.instrumentation-crash") +} + +android { + namespace = "co.elastic.otel.android.sample" + compileSdk = 35 + + defaultConfig { + applicationId = "co.elastic.otel.android.sample" + minSdk = 26 + versionCode = 1 + versionName = "1.0" + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + isMinifyEnabled = true + signingConfig = signingConfigs["debug"] + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } + buildFeatures { + viewBinding = true + } +} + +dependencies { + val retrofitVersion = "2.11.0" + implementation("androidx.navigation:navigation-fragment-ktx:2.8.7") + implementation("androidx.navigation:navigation-ui-ktx:2.8.7") + implementation("com.squareup.retrofit2:retrofit:$retrofitVersion") + implementation("com.squareup.retrofit2:converter-gson:$retrofitVersion") +} \ No newline at end of file diff --git a/sample-app/app/src/main/AndroidManifest.xml b/sample-app/app/src/main/AndroidManifest.xml index 6ade646e0..abf8fe596 100644 --- a/sample-app/app/src/main/AndroidManifest.xml +++ b/sample-app/app/src/main/AndroidManifest.xml @@ -1,6 +1,5 @@ - + @@ -14,10 +13,9 @@ android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.SampleWeatherApp" - android:usesCleartextTraffic="true" - tools:targetApi="31"> + android:usesCleartextTraffic="true"> diff --git a/sample-app/app/src/main/java/co/elastic/apm/android/sample/MainActivity.kt b/sample-app/app/src/main/java/co/elastic/apm/android/sample/MainActivity.kt deleted file mode 100644 index e29a90b70..000000000 --- a/sample-app/app/src/main/java/co/elastic/apm/android/sample/MainActivity.kt +++ /dev/null @@ -1,38 +0,0 @@ -package co.elastic.apm.android.sample - -import android.os.Bundle -import androidx.appcompat.app.AppCompatActivity -import androidx.navigation.findNavController -import androidx.navigation.ui.AppBarConfiguration -import androidx.navigation.ui.setupActionBarWithNavController -import co.elastic.apm.android.sample.databinding.ActivityMainBinding -import com.google.android.material.snackbar.Snackbar - -class MainActivity : AppCompatActivity() { - - private lateinit var appBarConfiguration: AppBarConfiguration - private lateinit var binding: ActivityMainBinding - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - binding = ActivityMainBinding.inflate(layoutInflater) - setContentView(binding.root) - - setSupportActionBar(binding.toolbar) - - val navController = findNavController(R.id.nav_host_fragment_content_main) - appBarConfiguration = AppBarConfiguration(navController.graph) - setupActionBarWithNavController(navController, appBarConfiguration) - - binding.fab.setOnClickListener { view -> - Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG) - .setAction("Action", null).show() - } - } - - override fun onSupportNavigateUp(): Boolean { - val navController = findNavController(R.id.nav_host_fragment_content_main) - return navController.navigateUp() || super.onSupportNavigateUp() - } -} \ No newline at end of file diff --git a/sample-app/app/src/main/java/co/elastic/apm/android/sample/MyApp.kt b/sample-app/app/src/main/java/co/elastic/apm/android/sample/MyApp.kt deleted file mode 100644 index 1068ec27c..000000000 --- a/sample-app/app/src/main/java/co/elastic/apm/android/sample/MyApp.kt +++ /dev/null @@ -1,24 +0,0 @@ -package co.elastic.apm.android.sample - -import android.app.Application -import co.elastic.apm.android.sdk.ElasticApmAgent -import co.elastic.apm.android.sdk.ElasticApmConfiguration -import co.elastic.apm.android.sdk.configuration.logging.LogLevel -import co.elastic.apm.android.sdk.configuration.logging.LoggingPolicy -import co.elastic.apm.android.sdk.features.persistence.PersistenceConfiguration - -class MyApp : Application() { - - override fun onCreate() { - super.onCreate() - ElasticApmAgent.initialize( - this, - ElasticApmConfiguration.builder() - .setPersistenceConfiguration( - PersistenceConfiguration.builder().setEnabled(true).build() - ) - .setLibraryLoggingPolicy(LoggingPolicy.enabled(LogLevel.TRACE)) - .build() - ) - } -} \ 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 new file mode 100644 index 000000000..88c816b3a --- /dev/null +++ b/sample-app/app/src/main/java/co/elastic/otel/android/sample/MyApp.kt @@ -0,0 +1,22 @@ +package co.elastic.otel.android.sample + +import android.app.Application +import co.elastic.otel.android.ElasticApmAgent +import co.elastic.otel.android.api.ElasticOtelAgent +import co.elastic.otel.android.extensions.log + +class MyApp : Application() { + companion object { + internal lateinit var agent: ElasticOtelAgent + } + + override fun onCreate() { + super.onCreate() + agent = ElasticApmAgent.builder(this) + .setUrl("http://10.0.2.2:8200") + .setServiceName("weather-sample-app") + .build() + + agent.log("App created") + } +} \ No newline at end of file diff --git a/sample-app/app/src/main/java/co/elastic/apm/android/sample/network/CityWeatherService.kt b/sample-app/app/src/main/java/co/elastic/otel/android/sample/network/CityWeatherService.kt similarity index 63% rename from sample-app/app/src/main/java/co/elastic/apm/android/sample/network/CityWeatherService.kt rename to sample-app/app/src/main/java/co/elastic/otel/android/sample/network/CityWeatherService.kt index d7a34e8e1..5cb2ed919 100644 --- a/sample-app/app/src/main/java/co/elastic/apm/android/sample/network/CityWeatherService.kt +++ b/sample-app/app/src/main/java/co/elastic/otel/android/sample/network/CityWeatherService.kt @@ -1,6 +1,6 @@ -package co.elastic.apm.android.sample.network +package co.elastic.otel.android.sample.network -import co.elastic.apm.android.sample.network.data.ForecastResponse +import co.elastic.otel.android.sample.network.data.ForecastResponse import retrofit2.http.GET import retrofit2.http.Query diff --git a/sample-app/app/src/main/java/co/elastic/apm/android/sample/network/WeatherRestManager.kt b/sample-app/app/src/main/java/co/elastic/otel/android/sample/network/WeatherRestManager.kt similarity index 82% rename from sample-app/app/src/main/java/co/elastic/apm/android/sample/network/WeatherRestManager.kt rename to sample-app/app/src/main/java/co/elastic/otel/android/sample/network/WeatherRestManager.kt index c67b4e6c4..2b42c0052 100644 --- a/sample-app/app/src/main/java/co/elastic/apm/android/sample/network/WeatherRestManager.kt +++ b/sample-app/app/src/main/java/co/elastic/otel/android/sample/network/WeatherRestManager.kt @@ -1,6 +1,6 @@ -package co.elastic.apm.android.sample.network +package co.elastic.otel.android.sample.network -import co.elastic.apm.android.sample.network.data.ForecastResponse +import co.elastic.otel.android.sample.network.data.ForecastResponse import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory diff --git a/sample-app/app/src/main/java/co/elastic/apm/android/sample/network/data/CurrentWeatherResponse.kt b/sample-app/app/src/main/java/co/elastic/otel/android/sample/network/data/CurrentWeatherResponse.kt similarity index 53% rename from sample-app/app/src/main/java/co/elastic/apm/android/sample/network/data/CurrentWeatherResponse.kt rename to sample-app/app/src/main/java/co/elastic/otel/android/sample/network/data/CurrentWeatherResponse.kt index be3d5f433..2b17452b3 100644 --- a/sample-app/app/src/main/java/co/elastic/apm/android/sample/network/data/CurrentWeatherResponse.kt +++ b/sample-app/app/src/main/java/co/elastic/otel/android/sample/network/data/CurrentWeatherResponse.kt @@ -1,3 +1,3 @@ -package co.elastic.apm.android.sample.network.data +package co.elastic.otel.android.sample.network.data data class CurrentWeatherResponse(val temperature: Double) \ No newline at end of file diff --git a/sample-app/app/src/main/java/co/elastic/apm/android/sample/network/data/ForecastResponse.kt b/sample-app/app/src/main/java/co/elastic/otel/android/sample/network/data/ForecastResponse.kt similarity index 76% rename from sample-app/app/src/main/java/co/elastic/apm/android/sample/network/data/ForecastResponse.kt rename to sample-app/app/src/main/java/co/elastic/otel/android/sample/network/data/ForecastResponse.kt index 83ae69c3b..3d34094e0 100644 --- a/sample-app/app/src/main/java/co/elastic/apm/android/sample/network/data/ForecastResponse.kt +++ b/sample-app/app/src/main/java/co/elastic/otel/android/sample/network/data/ForecastResponse.kt @@ -1,4 +1,4 @@ -package co.elastic.apm.android.sample.network.data +package co.elastic.otel.android.sample.network.data import com.google.gson.annotations.SerializedName diff --git a/sample-app/app/src/main/java/co/elastic/apm/android/sample/FirstFragment.kt b/sample-app/app/src/main/java/co/elastic/otel/android/sample/ui/FirstFragment.kt similarity index 90% rename from sample-app/app/src/main/java/co/elastic/apm/android/sample/FirstFragment.kt rename to sample-app/app/src/main/java/co/elastic/otel/android/sample/ui/FirstFragment.kt index a9e30b97c..d25b97a69 100644 --- a/sample-app/app/src/main/java/co/elastic/apm/android/sample/FirstFragment.kt +++ b/sample-app/app/src/main/java/co/elastic/otel/android/sample/ui/FirstFragment.kt @@ -1,4 +1,4 @@ -package co.elastic.apm.android.sample +package co.elastic.otel.android.sample.ui import android.os.Bundle import android.view.LayoutInflater @@ -8,7 +8,8 @@ import android.widget.ArrayAdapter import androidx.core.os.bundleOf import androidx.fragment.app.Fragment import androidx.navigation.fragment.findNavController -import co.elastic.apm.android.sample.databinding.FragmentFirstBinding +import co.elastic.otel.android.sample.R +import co.elastic.otel.android.sample.databinding.FragmentFirstBinding class FirstFragment : Fragment() { 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 new file mode 100644 index 000000000..ad02607b5 --- /dev/null +++ b/sample-app/app/src/main/java/co/elastic/otel/android/sample/ui/MainActivity.kt @@ -0,0 +1,41 @@ +package co.elastic.otel.android.sample.ui + +import android.os.Bundle +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.R +import co.elastic.otel.android.sample.databinding.ActivityMainBinding + +class MainActivity : AppCompatActivity() { + + private lateinit var appBarConfiguration: AppBarConfiguration + private lateinit var binding: ActivityMainBinding + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + agent.span("Main Activity creation") { + binding = ActivityMainBinding.inflate(layoutInflater) + setContentView(binding.root) + + setSupportActionBar(binding.toolbar) + + val navController = findNavController(R.id.nav_host_fragment_content_main) + appBarConfiguration = AppBarConfiguration(navController.graph) + setupActionBarWithNavController(navController, appBarConfiguration) + + binding.fab.setOnClickListener { view -> + agent.log("Button click") + } + } + } + + override fun onSupportNavigateUp(): Boolean { + val navController = findNavController(R.id.nav_host_fragment_content_main) + return navController.navigateUp() || super.onSupportNavigateUp() + } +} \ No newline at end of file diff --git a/sample-app/app/src/main/java/co/elastic/apm/android/sample/SecondFragment.kt b/sample-app/app/src/main/java/co/elastic/otel/android/sample/ui/SecondFragment.kt similarity index 88% rename from sample-app/app/src/main/java/co/elastic/apm/android/sample/SecondFragment.kt rename to sample-app/app/src/main/java/co/elastic/otel/android/sample/ui/SecondFragment.kt index c6c8317b7..f73640873 100644 --- a/sample-app/app/src/main/java/co/elastic/apm/android/sample/SecondFragment.kt +++ b/sample-app/app/src/main/java/co/elastic/otel/android/sample/ui/SecondFragment.kt @@ -1,4 +1,4 @@ -package co.elastic.apm.android.sample +package co.elastic.otel.android.sample.ui import android.os.Bundle import android.text.Html @@ -10,9 +10,10 @@ import android.widget.Toast import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController -import co.elastic.apm.android.sample.databinding.FragmentSecondBinding -import co.elastic.apm.android.sample.network.WeatherRestManager -import co.elastic.apm.android.sample.network.data.ForecastResponse +import co.elastic.otel.android.sample.network.WeatherRestManager +import co.elastic.otel.android.sample.network.data.ForecastResponse +import co.elastic.otel.android.sample.R +import co.elastic.otel.android.sample.databinding.FragmentSecondBinding import kotlinx.coroutines.launch class SecondFragment : Fragment() { diff --git a/sample-app/app/src/main/res/menu/menu_main.xml b/sample-app/app/src/main/res/menu/menu_main.xml index 08ed19f33..8361bfe00 100644 --- a/sample-app/app/src/main/res/menu/menu_main.xml +++ b/sample-app/app/src/main/res/menu/menu_main.xml @@ -1,7 +1,7 @@ + tools:context=".ui.MainActivity"> @@ -17,7 +17,7 @@ diff --git a/sample-app/backend/build.gradle b/sample-app/backend/build.gradle deleted file mode 100644 index fd306f4ec..000000000 --- a/sample-app/backend/build.gradle +++ /dev/null @@ -1,20 +0,0 @@ -plugins { - id 'java' - id 'org.springframework.boot' version '2.7.6' - id 'io.spring.dependency-management' version '1.1.0' -} - -sourceCompatibility = JavaVersion.VERSION_11 -targetCompatibility = JavaVersion.VERSION_11 - -def elasticApmVersion = '1.35.0' - -dependencies { - implementation 'org.springframework.boot:spring-boot-starter-web' - implementation "co.elastic.apm:apm-agent-attach:$elasticApmVersion" - testImplementation 'org.springframework.boot:spring-boot-starter-test' -} - -tasks.named('test') { - useJUnitPlatform() -} diff --git a/sample-app/backend/build.gradle.kts b/sample-app/backend/build.gradle.kts new file mode 100644 index 000000000..ba0f1e487 --- /dev/null +++ b/sample-app/backend/build.gradle.kts @@ -0,0 +1,22 @@ +plugins { + id("java") + id("org.springframework.boot") version "2.7.6" + id("io.spring.dependency-management") version "1.1.0" +} + +java { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 +} + +val elasticApmVersion = "1.35.0" + +dependencies { + implementation("org.springframework.boot:spring-boot-starter-web") + implementation("co.elastic.apm:apm-agent-attach:$elasticApmVersion") + testImplementation("org.springframework.boot:spring-boot-starter-test") +} + +tasks.withType(Test::class).configureEach { + useJUnitPlatform() +} diff --git a/sample-app/build.gradle b/sample-app/build.gradle deleted file mode 100644 index b05eddc4d..000000000 --- a/sample-app/build.gradle +++ /dev/null @@ -1,6 +0,0 @@ -// Top-level build file where you can add configuration options common to all sub-projects/modules. -plugins { - id 'com.android.application' version '8.2.0' apply false - id 'com.android.library' version '8.2.0' apply false - id 'org.jetbrains.kotlin.android' version '1.8.22' apply false -} \ No newline at end of file diff --git a/sample-app/build.gradle.kts b/sample-app/build.gradle.kts new file mode 100644 index 000000000..c9707b92f --- /dev/null +++ b/sample-app/build.gradle.kts @@ -0,0 +1,34 @@ +import java.util.Properties + +plugins { + alias(rootLibs.plugins.androidApp) apply false + alias(rootLibs.plugins.androidLib) apply false + alias(rootLibs.plugins.kotlin.android) apply false +} + +val agentProperties = Properties() +val propertiesFile = File(rootDir, "../gradle.properties") +propertiesFile.inputStream().use { + agentProperties.load(it) +} + +val agentVersion = agentProperties["version"] +subprojects { + if (name == "app") { + configurations.all { + File(rootDir, "../instrumentation").listFiles().forEach { + val dirName = it.name + if (dirName != "api") { + resolutionStrategy.dependencySubstitution { + substitute(module("co.elastic.otel.android.instrumentation:${dirName}-library")) + .using(module("co.elastic.otel.android.${dirName}:library:$agentVersion")) + } + resolutionStrategy.dependencySubstitution { + substitute(module("co.elastic.otel.android.instrumentation:${dirName}-bytebuddy")) + .using(module("co.elastic.otel.android.${dirName}:bytebuddy:$agentVersion")) + } + } + } + } + } +} diff --git a/sample-app/gradle/wrapper/gradle-wrapper.jar b/sample-app/gradle/wrapper/gradle-wrapper.jar index 41d9927a4..2c3521197 100644 Binary files a/sample-app/gradle/wrapper/gradle-wrapper.jar and b/sample-app/gradle/wrapper/gradle-wrapper.jar differ diff --git a/sample-app/gradle/wrapper/gradle-wrapper.properties b/sample-app/gradle/wrapper/gradle-wrapper.properties index 15de90249..e18bc253b 100644 --- a/sample-app/gradle/wrapper/gradle-wrapper.properties +++ b/sample-app/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-bin.zip +networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/sample-app/gradlew b/sample-app/gradlew index 1b6c78733..f5feea6d6 100755 --- a/sample-app/gradlew +++ b/sample-app/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -80,13 +82,12 @@ do esac done -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -APP_NAME="Gradle" +# This is normally unused +# shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -133,22 +134,29 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -193,11 +201,15 @@ if "$cygwin" || "$msys" ; then done fi -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ @@ -205,6 +217,12 @@ set -- \ org.gradle.wrapper.GradleWrapperMain \ "$@" +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + # Use "xargs" to parse quoted args. # # With -n1 it outputs one arg per line, with the quotes and backslashes removed. diff --git a/sample-app/gradlew.bat b/sample-app/gradlew.bat index 107acd32c..9d21a2183 100644 --- a/sample-app/gradlew.bat +++ b/sample-app/gradlew.bat @@ -13,8 +13,10 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +27,8 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,13 +43,13 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -56,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -75,13 +78,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/sample-app/settings.gradle b/sample-app/settings.gradle.kts similarity index 68% rename from sample-app/settings.gradle rename to sample-app/settings.gradle.kts index da357fe02..60c36525b 100644 --- a/sample-app/settings.gradle +++ b/sample-app/settings.gradle.kts @@ -8,6 +8,11 @@ pluginManagement { } } dependencyResolutionManagement { + versionCatalogs { + create("rootLibs") { + from(files("../gradle/libs.versions.toml")) + } + } repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { mavenCentral() @@ -15,6 +20,6 @@ dependencyResolutionManagement { } } rootProject.name = "Android APM Sample app" -includeBuild '..' -include ':app' -include ':backend' +includeBuild("..") +include(":app") +include(":backend") diff --git a/settings.gradle.kts b/settings.gradle.kts index af6fcda24..d7f1c27d0 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -25,6 +25,7 @@ include(":android-api") include(":android-sdk") include(":android-plugin") include(":android-common") +include(":animalsniffer-signature") includeFromDir("instrumentation") includeFromDir("test-tools", 2) diff --git a/tests.sh b/tests.sh deleted file mode 100755 index 2196670f7..000000000 --- a/tests.sh +++ /dev/null @@ -1,4 +0,0 @@ -./gradlew publishToMavenLocal -./gradlew test -./gradlew -p "android-test" test -./gradlew -p "build-tools" test