diff --git a/.ci/snapshoty.yml b/.ci/snapshoty.yml index 3a1d0e298..e756277e8 100644 --- a/.ci/snapshoty.yml +++ b/.ci/snapshoty.yml @@ -34,10 +34,6 @@ artifacts: output_pattern: '{project}/{github_branch_name}/elastic-apm-android-common-{app_version}-{github_sha_short}.jar' # List of metadata processors to use. metadata: *metadata - - path: './android-instrumentation/build/outputs/aar' - files_pattern: 'android-instrumentation-(?Pdebug|release)\.aar' - output_pattern: '{project}/{github_branch_name}/elastic-apm-android-instrumentation-{version}-{github_sha_short}.aar' - metadata: *metadata - path: './android-plugin/build/libs' files_pattern: 'android-plugin-(?P\d+\.\d+\.\d+)\.jar' output_pattern: '{project}/{github_branch_name}/elastic-apm-android-plugin-{app_version}-{github_sha_short}.jar' diff --git a/NOTICE b/NOTICE index 5d3750b91..b2ad1a5a4 100644 --- a/NOTICE +++ b/NOTICE @@ -5,15 +5,14 @@ Copyright 2018-2022 Elasticsearch B.V. This product includes software licensed under the 'Apache License Version 2.0' license from the following sources: - - Android Lifecycle Process (https://developer.android.com/jetpack/androidx/releases/lifecycle#2.5.1) + - Android Lifecycle Process (https://developer.android.com/jetpack/androidx/releases/lifecycle#2.6.1) - Android Support Library Annotations (https://developer.android.com/jetpack/androidx/releases/annotation#1.4.0) - - Android Support Library fragment (https://developer.android.com/jetpack/androidx/releases/fragment#1.5.3) - Byte Buddy (without dependencies) - Byte Buddy Gradle plugin - com.github.instacart.truetime-android:library:3.5 - - Kotlin Stdlib Jdk8 (https://kotlinlang.org/) - - OkHttp + - Kotlin Stdlib (https://kotlinlang.org/) - okhttp (https://square.github.io/okhttp/) + - OpenTelemetry Android (https://github.com/open-telemetry/opentelemetry-android) - OpenTelemetry Java (https://github.com/open-telemetry/opentelemetry-java) - OpenTelemetry Java Contrib (https://github.com/open-telemetry/opentelemetry-java-contrib) - OpenTelemetry Semantic Conventions Java (https://github.com/open-telemetry/semantic-conventions-java) diff --git a/README.md b/README.md index d2517d6f9..35162b6ea 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,12 @@ # apm-agent-android +> [!NOTE] +> This agent is based on +> the [OpenTelemetry Android lib](https://github.com/open-telemetry/opentelemetry-android) which is +> not stable yet. Elastic is proactively making contributions to this OpenTelemetry initiative and +> we will release v1.x of the Elastic Android APM agent after a stable release of the OTel Android +> SDK/API becomes available. + Elastic APM Android Agent See the [documentation](https://www.elastic.co/guide/en/apm/agent/android/current/index.html) for diff --git a/android-common/build.gradle b/android-common/build.gradle index 5135289b5..2dc6afeaa 100644 --- a/android-common/build.gradle +++ b/android-common/build.gradle @@ -10,7 +10,6 @@ licensesConfig { } dependencies { - api "com.squareup.okhttp3:okhttp:$okhttp_version" - api "org.slf4j:slf4j-api:2.0.0" - implementation "androidx.annotation:annotation:$androidAnnotations_version" + api libs.slf4j.api + implementation libs.androidx.annotations } \ No newline at end of file diff --git a/android-common/metadata/notice.properties b/android-common/metadata/notice.properties index e23aed6db..f238d1194 100644 --- a/android-common/metadata/notice.properties +++ b/android-common/metadata/notice.properties @@ -1 +1 @@ -dependencies.hash=C4E938CB5D2FBDF87B030546B2E27163 \ No newline at end of file +dependencies.hash=7975FB0685D05968563B5868A424E0DC \ No newline at end of file diff --git a/android-common/src/main/java/co/elastic/apm/android/common/ApmInfo.java b/android-common/src/main/java/co/elastic/apm/android/common/ApmInfo.java index 263753355..4be719ee0 100644 --- a/android-common/src/main/java/co/elastic/apm/android/common/ApmInfo.java +++ b/android-common/src/main/java/co/elastic/apm/android/common/ApmInfo.java @@ -25,6 +25,5 @@ public class ApmInfo { public static String KEY_SERVER_SECRET_TOKEN = "server.secret_token"; public static String KEY_SERVER_API_KEY = "server.api_key"; public static String KEY_SERVICE_ENVIRONMENT = "service.deployment_environment"; - public static String KEY_SCOPE_OKHTTP_VERSION = "scope.okhttp.version"; public static String ASSET_FILE_NAME = "co_elastic_apm_android.properties"; } diff --git a/android-common/src/main/java/co/elastic/apm/android/common/okhttp/eventlistener/CompositeEventListener.java b/android-common/src/main/java/co/elastic/apm/android/common/okhttp/eventlistener/CompositeEventListener.java deleted file mode 100644 index 593cc0186..000000000 --- a/android-common/src/main/java/co/elastic/apm/android/common/okhttp/eventlistener/CompositeEventListener.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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.apm.android.common.okhttp.eventlistener; - -import androidx.annotation.NonNull; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.List; - -import co.elastic.apm.android.common.MethodCaller; -import okhttp3.EventListener; - -public class CompositeEventListener extends EventListener implements MethodCaller { - private final List listeners; - - public CompositeEventListener(List listeners) { - this.listeners = listeners; - } - - @Override - public void doCall(@NonNull Method method, @NonNull Object[] params) { - for (EventListener listener : listeners) { - try { - method.invoke(listener, params); - } catch (IllegalAccessException | InvocationTargetException e) { - throw new RuntimeException(e); - } - } - } - - public static String getGeneratedName() { - return CompositeEventListener.class.getPackage().getName() + ".Generated_" + CompositeEventListener.class.getSimpleName(); - } -} \ No newline at end of file diff --git a/android-common/src/main/java/co/elastic/apm/android/common/okhttp/eventlistener/CompositeEventListenerFactory.java b/android-common/src/main/java/co/elastic/apm/android/common/okhttp/eventlistener/CompositeEventListenerFactory.java deleted file mode 100644 index 7f2d4e86f..000000000 --- a/android-common/src/main/java/co/elastic/apm/android/common/okhttp/eventlistener/CompositeEventListenerFactory.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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.apm.android.common.okhttp.eventlistener; - -import androidx.annotation.NonNull; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import co.elastic.apm.android.common.internal.logging.Elog; -import okhttp3.Call; -import okhttp3.EventListener; - -public class CompositeEventListenerFactory implements EventListener.Factory { - private final List factories; - - public CompositeEventListenerFactory(EventListener.Factory... factories) { - this.factories = Arrays.asList(factories); - } - - @NonNull - @Override - public EventListener create(@NonNull Call call) { - Elog.getLogger().debug("Creating OkHttp event listener"); - List listeners = new ArrayList<>(); - - for (EventListener.Factory factory : factories) { - listeners.add(factory.create(call)); - } - - return new CompositeEventListener(listeners); - } -} diff --git a/android-common/src/main/resources/META-INF/NOTICE b/android-common/src/main/resources/META-INF/NOTICE index 51d294c64..0f7068ad7 100644 --- a/android-common/src/main/resources/META-INF/NOTICE +++ b/android-common/src/main/resources/META-INF/NOTICE @@ -6,7 +6,6 @@ Copyright 2018-2022 Elasticsearch B.V. This product includes software licensed under the 'Apache License Version 2.0' license from the following sources: - Android Support Library Annotations (https://developer.android.com/jetpack/androidx/releases/annotation#1.4.0) - - OkHttp ------------------------------------------------------------------------------- diff --git a/android-instrumentation/build.gradle b/android-instrumentation/build.gradle deleted file mode 100644 index 2ee83fdb3..000000000 --- a/android-instrumentation/build.gradle +++ /dev/null @@ -1,39 +0,0 @@ -plugins { - id 'com.android.library' -} - -android { - namespace = "co.elastic.apm.android.instrumentation" - compileSdk androidCompileSdk - - defaultConfig { - minSdk androidMinSdk - targetSdk androidCompileSdk - - testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" - consumerProguardFiles "consumer-rules.pro" - } - - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - } - compileOptions { - sourceCompatibility jvmCompatibility - targetCompatibility jvmCompatibility - } -} - -licensesConfig { - manualMappingFile = rootProject.file("manual_licenses_map.txt") -} - -dependencies { - implementation project(':android-sdk') - implementation project(':android-sdk-ktx') - implementation project(':android-common') - implementation "androidx.fragment:fragment:1.5.3" - implementation "net.bytebuddy:byte-buddy:$bytebuddy_version" -} \ No newline at end of file diff --git a/android-instrumentation/metadata/notice.properties b/android-instrumentation/metadata/notice.properties deleted file mode 100644 index 93d6c4bd0..000000000 --- a/android-instrumentation/metadata/notice.properties +++ /dev/null @@ -1 +0,0 @@ -dependencies.hash=ECC7B12A979C5F340C393E6F6E6B8044 \ No newline at end of file diff --git a/android-instrumentation/src/main/AndroidManifest.xml b/android-instrumentation/src/main/AndroidManifest.xml deleted file mode 100644 index 568741e54..000000000 --- a/android-instrumentation/src/main/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/android-instrumentation/src/main/java/co/elastic/apm/android/instrumentation/okhttp/client/OkHttpClientAdvice.java b/android-instrumentation/src/main/java/co/elastic/apm/android/instrumentation/okhttp/client/OkHttpClientAdvice.java deleted file mode 100644 index cf6628b10..000000000 --- a/android-instrumentation/src/main/java/co/elastic/apm/android/instrumentation/okhttp/client/OkHttpClientAdvice.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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.apm.android.instrumentation.okhttp.client; - -import net.bytebuddy.asm.Advice; - -import co.elastic.apm.android.common.internal.logging.Elog; -import co.elastic.apm.android.common.okhttp.eventlistener.CompositeEventListenerFactory; -import co.elastic.apm.android.sdk.traces.http.impl.okhttp.OkHttpContextStore; -import co.elastic.apm.android.sdk.traces.http.impl.okhttp.OtelOkHttpEventListener; -import co.elastic.apm.android.sdk.traces.http.impl.okhttp.OtelOkHttpInterceptor; -import okhttp3.OkHttpClient; - -public class OkHttpClientAdvice { - - @SuppressWarnings("KotlinInternalInJava") - @Advice.OnMethodEnter - public static void enter(@Advice.Argument(0) OkHttpClient.Builder builder) { - Elog.getLogger().debug("Instrumenting OkHttpClient"); - OkHttpContextStore contextStore = new OkHttpContextStore(); - OtelOkHttpEventListener.Factory otelFactory = new OtelOkHttpEventListener.Factory(contextStore); - builder.eventListenerFactory(new CompositeEventListenerFactory(otelFactory, builder.getEventListenerFactory$okhttp())); - builder.interceptors().add(0, new OtelOkHttpInterceptor(contextStore)); - } -} \ No newline at end of file diff --git a/android-instrumentation/src/main/java/co/elastic/apm/android/instrumentation/okhttp/client/OkHttpClientPlugin.java b/android-instrumentation/src/main/java/co/elastic/apm/android/instrumentation/okhttp/client/OkHttpClientPlugin.java deleted file mode 100644 index 05f23ef81..000000000 --- a/android-instrumentation/src/main/java/co/elastic/apm/android/instrumentation/okhttp/client/OkHttpClientPlugin.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * 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.apm.android.instrumentation.okhttp.client; - -import net.bytebuddy.asm.Advice; -import net.bytebuddy.build.Plugin; -import net.bytebuddy.description.type.TypeDescription; -import net.bytebuddy.dynamic.ClassFileLocator; -import net.bytebuddy.dynamic.DynamicType; -import net.bytebuddy.matcher.ElementMatchers; - -import java.io.IOException; - -import okhttp3.OkHttpClient; - -public class OkHttpClientPlugin implements Plugin { - - @Override - public DynamicType.Builder apply(DynamicType.Builder builder, - TypeDescription typeDescription, - ClassFileLocator classFileLocator) { - return builder.visit(Advice.to(OkHttpClientAdvice.class) - .on(ElementMatchers.isConstructor() - .and(ElementMatchers.takesArguments(OkHttpClient.Builder.class)) - )); - } - - @Override - public void close() throws IOException { - // No operation. - } - - @Override - public boolean matches(TypeDescription target) { - return target.getTypeName().equals("okhttp3.OkHttpClient"); - } -} diff --git a/android-instrumentation/src/main/java/co/elastic/apm/android/instrumentation/okhttp/eventlistener/CompositeEventListenerFactoryPlugin.java b/android-instrumentation/src/main/java/co/elastic/apm/android/instrumentation/okhttp/eventlistener/CompositeEventListenerFactoryPlugin.java deleted file mode 100644 index d656f9b95..000000000 --- a/android-instrumentation/src/main/java/co/elastic/apm/android/instrumentation/okhttp/eventlistener/CompositeEventListenerFactoryPlugin.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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.apm.android.instrumentation.okhttp.eventlistener; - -import net.bytebuddy.build.Plugin; -import net.bytebuddy.description.type.TypeDescription; -import net.bytebuddy.dynamic.ClassFileLocator; -import net.bytebuddy.dynamic.DynamicType; - -import java.io.IOException; - -import co.elastic.apm.android.common.internal.logging.Elog; - -public class CompositeEventListenerFactoryPlugin implements Plugin { - - @Override - public DynamicType.Builder apply(DynamicType.Builder builder, TypeDescription typeDescription, ClassFileLocator classFileLocator) { - Elog.getLogger().debug("Remapping to generated CompositeEventListener"); - return builder.visit(new CompositeEventListenerRemapper()); - } - - @Override - public void close() throws IOException { - // No operation. - } - - @Override - public boolean matches(TypeDescription target) { - return target.getTypeName().equals("co.elastic.apm.android.common.okhttp.eventlistener.CompositeEventListenerFactory"); - } -} diff --git a/android-instrumentation/src/main/java/co/elastic/apm/android/instrumentation/okhttp/eventlistener/CompositeEventListenerRemapper.java b/android-instrumentation/src/main/java/co/elastic/apm/android/instrumentation/okhttp/eventlistener/CompositeEventListenerRemapper.java deleted file mode 100644 index 4f253388d..000000000 --- a/android-instrumentation/src/main/java/co/elastic/apm/android/instrumentation/okhttp/eventlistener/CompositeEventListenerRemapper.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * 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.apm.android.instrumentation.okhttp.eventlistener; - -import net.bytebuddy.asm.AsmVisitorWrapper; -import net.bytebuddy.description.field.FieldDescription; -import net.bytebuddy.description.field.FieldList; -import net.bytebuddy.description.method.MethodList; -import net.bytebuddy.description.type.TypeDescription; -import net.bytebuddy.implementation.Implementation; -import net.bytebuddy.jar.asm.ClassVisitor; -import net.bytebuddy.jar.asm.commons.ClassRemapper; -import net.bytebuddy.jar.asm.commons.Remapper; -import net.bytebuddy.pool.TypePool; - -import co.elastic.apm.android.common.okhttp.eventlistener.CompositeEventListener; - -public class CompositeEventListenerRemapper extends AsmVisitorWrapper.AbstractBase { - - @Override - public ClassVisitor wrap(TypeDescription typeDescription, - ClassVisitor classVisitor, - Implementation.Context context, - TypePool typePool, - FieldList fields, - MethodList methods, - int writerFlags, - int readerFlags) { - return new ClassRemapper(classVisitor, new GeneratedRemapper()); - } - - static class GeneratedRemapper extends Remapper { - private final String from; - private final String to; - - GeneratedRemapper() { - from = CompositeEventListener.class.getName().replaceAll("\\.", "/"); - to = CompositeEventListener.getGeneratedName().replaceAll("\\.", "/"); - } - - @Override - public String map(String internalName) { - if (internalName.equals(from)) { - return to; - } - return super.map(internalName); - } - } -} diff --git a/android-instrumentation/src/main/java/co/elastic/apm/android/instrumentation/ui/activities/ActivityLifecycleMethodAdvice.java b/android-instrumentation/src/main/java/co/elastic/apm/android/instrumentation/ui/activities/ActivityLifecycleMethodAdvice.java deleted file mode 100644 index 50137e98b..000000000 --- a/android-instrumentation/src/main/java/co/elastic/apm/android/instrumentation/ui/activities/ActivityLifecycleMethodAdvice.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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.apm.android.instrumentation.ui.activities; - -import net.bytebuddy.asm.Advice; - -import co.elastic.apm.android.instrumentation.ui.common.IsLastLifecycleMethod; -import co.elastic.apm.android.sdk.internal.instrumentation.LifecycleMultiMethodSpan; -import co.elastic.apm.android.sdk.traces.ElasticTracers; - -public class ActivityLifecycleMethodAdvice { - - @Advice.OnMethodEnter - public static void onMethodEnter( - @Advice.Origin("#t") String ownerName, - @Advice.Origin("#m") String methodName, - @Advice.Local("elasticSpanWithScope") LifecycleMultiMethodSpan.SpanWithScope spanWithScope) { - spanWithScope = LifecycleMultiMethodSpan.onMethodEnter(ownerName, methodName, ElasticTracers.androidActivity()); - } - - @Advice.OnMethodExit(onThrowable = Throwable.class) - public static void onMethodExit( - @Advice.Origin("#t") String ownerName, - @Advice.Local("elasticSpanWithScope") LifecycleMultiMethodSpan.SpanWithScope spanWithScope, - @IsLastLifecycleMethod boolean isLastMethod, - @Advice.Thrown Throwable thrown) { - LifecycleMultiMethodSpan.onMethodExit(ownerName, spanWithScope, thrown, isLastMethod); - } -} diff --git a/android-instrumentation/src/main/java/co/elastic/apm/android/instrumentation/ui/activities/ActivityLifecyclePlugin.java b/android-instrumentation/src/main/java/co/elastic/apm/android/instrumentation/ui/activities/ActivityLifecyclePlugin.java deleted file mode 100644 index 4c676771a..000000000 --- a/android-instrumentation/src/main/java/co/elastic/apm/android/instrumentation/ui/activities/ActivityLifecyclePlugin.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * 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.apm.android.instrumentation.ui.activities; - -import android.app.Activity; -import android.os.Bundle; - -import androidx.annotation.NonNull; - -import net.bytebuddy.build.AndroidDescriptor; -import net.bytebuddy.description.type.TypeDescription; - -import java.util.ArrayList; -import java.util.List; - -import co.elastic.apm.android.instrumentation.ui.common.BaseLifecycleMethodsPlugin; - -/** - * Instruments the following {@link Activity} methods: - * - {@link Activity#onCreate(android.os.Bundle)} - * - {@link Activity#onStart()} - * - {@link Activity#onResume()} - * To create a root span covering them all, as well as method-specific spans for each. - */ -public class ActivityLifecyclePlugin extends BaseLifecycleMethodsPlugin { - private final AndroidDescriptor androidDescriptor; - - public ActivityLifecyclePlugin(AndroidDescriptor androidDescriptor) { - this.androidDescriptor = androidDescriptor; - } - - @Override - public boolean matches(TypeDescription target) { - if (androidDescriptor.getTypeScope(target) == AndroidDescriptor.TypeScope.EXTERNAL) { - return false; - } - return !target.getSimpleName().startsWith("Hilt_") && target.isAssignableTo(Activity.class); - } - - @NonNull - @Override - protected Class getAdviceClass() { - return ActivityLifecycleMethodAdvice.class; - } - - @Override - protected List provideOrderedTargetMethods() { - List methods = new ArrayList<>(); - methods.add(MethodIdentity.create("onCreate", void.class, Bundle.class)); - methods.add(MethodIdentity.create("onStart", void.class)); - methods.add(MethodIdentity.create("onResume", void.class)); - return methods; - } -} diff --git a/android-instrumentation/src/main/java/co/elastic/apm/android/instrumentation/ui/common/BaseLifecycleMethodsPlugin.java b/android-instrumentation/src/main/java/co/elastic/apm/android/instrumentation/ui/common/BaseLifecycleMethodsPlugin.java deleted file mode 100644 index 872b57f77..000000000 --- a/android-instrumentation/src/main/java/co/elastic/apm/android/instrumentation/ui/common/BaseLifecycleMethodsPlugin.java +++ /dev/null @@ -1,182 +0,0 @@ -/* - * 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.apm.android.instrumentation.ui.common; - -import static net.bytebuddy.matcher.ElementMatchers.named; -import static net.bytebuddy.matcher.ElementMatchers.returns; -import static net.bytebuddy.matcher.ElementMatchers.takesArguments; - -import androidx.annotation.NonNull; - -import net.bytebuddy.asm.Advice; -import net.bytebuddy.build.Plugin; -import net.bytebuddy.description.method.MethodDescription; -import net.bytebuddy.description.method.ParameterDescription; -import net.bytebuddy.description.type.TypeDescription; -import net.bytebuddy.dynamic.ClassFileLocator; -import net.bytebuddy.dynamic.DynamicType; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -public abstract class BaseLifecycleMethodsPlugin implements Plugin { - private List cachedTargetMethods = null; - - @Override - public DynamicType.Builder apply(DynamicType.Builder builder, - TypeDescription typeDescription, - ClassFileLocator classFileLocator) { - AvailableMethods availableMethods = getAvailableLifecycleMethods(typeDescription); - if (availableMethods == null) { - // No Operation. - return builder; - } - - DynamicType.Builder newBuilder = builder.visit(Advice.withCustomMapping().bind(IsLastLifecycleMethod.class, true).to(getAdviceClass()).on(getMethodMatcher(availableMethods.lastMethod))); - - if (!availableMethods.otherMethods.isEmpty()) { - newBuilder = newBuilder.visit(Advice.withCustomMapping().bind(IsLastLifecycleMethod.class, false).to(getAdviceClass()).on(getMultipleMethodsMatcher(availableMethods.otherMethods))); - } - - return newBuilder; - } - - @NonNull - protected abstract Class getAdviceClass(); - - /** - * Must return the list of lifecycle target methods in the order they are supposed to be - * called. - */ - protected abstract List provideOrderedTargetMethods(); - - @Override - public void close() { - cachedTargetMethods = null; - } - - private Junction getMultipleMethodsMatcher(List methods) { - Junction elementMatcher = null; - for (MethodIdentity method : methods) { - if (elementMatcher == null) { - elementMatcher = getMethodMatcher(method); - } else { - elementMatcher = elementMatcher.or(getMethodMatcher(method)); - } - } - return elementMatcher; - } - - @NonNull - private Junction getMethodMatcher(MethodIdentity method) { - return named(method.name).and(takesArguments(method.argumentTypes)).and(returns(method.returnType)); - } - - private AvailableMethods getAvailableLifecycleMethods(TypeDescription typeDescription) { - int foundMethodsCount = 0; - List foundMethods = new ArrayList<>(); - List targetMethods = getTargetMethods(); - int maxMethods = targetMethods.size(); - for (MethodDescription.InDefinedShape declaredMethod : typeDescription.getDeclaredMethods()) { - MethodIdentity methodIdentity = convert(declaredMethod); - if (targetMethods.contains(methodIdentity)) { - foundMethods.add(methodIdentity); - foundMethodsCount++; - } - if (foundMethodsCount == maxMethods) { - break; - } - } - - if (foundMethods.isEmpty()) { - // No lifecycle methods defined in the target class. - return null; - } - - AvailableMethods availableMethods = new AvailableMethods(); - - // Looping backwards to get the last lifecycle methods first. - for (int i = targetMethods.size() - 1; i >= 0; i--) { - MethodIdentity method = targetMethods.get(i); - if (foundMethods.contains(method)) { - if (availableMethods.lastMethod == null) { - availableMethods.lastMethod = method; - } else { - availableMethods.otherMethods.add(method); - } - } - } - - return availableMethods; - } - - private MethodIdentity convert(MethodDescription.InDefinedShape methodDescription) { - List arguments = new ArrayList<>(); - for (ParameterDescription.InDefinedShape parameter : methodDescription.getParameters()) { - arguments.add(parameter.getType().asErasure()); - } - return new MethodIdentity(methodDescription.getName(), methodDescription.getReturnType().asErasure(), arguments); - } - - private List getTargetMethods() { - if (cachedTargetMethods == null) { - cachedTargetMethods = provideOrderedTargetMethods(); - } - return cachedTargetMethods; - } - - private static class AvailableMethods { - private MethodIdentity lastMethod; - private final List otherMethods = new ArrayList<>(); - } - - protected static class MethodIdentity { - public final String name; - public final TypeDescription returnType; - public final List argumentTypes; - - public static MethodIdentity create(String name, Class returnType, Class... argumentTypes) { - List arguments = new ArrayList<>(); - for (Class argumentType : argumentTypes) { - arguments.add(TypeDescription.ForLoadedType.of(argumentType)); - } - return new MethodIdentity(name, TypeDescription.ForLoadedType.of(returnType), arguments); - } - - private MethodIdentity(String name, TypeDescription returnType, List argumentTypes) { - this.name = name; - this.returnType = returnType; - this.argumentTypes = argumentTypes; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - MethodIdentity that = (MethodIdentity) o; - return Objects.equals(name, that.name) && Objects.equals(returnType, that.returnType) && Objects.equals(argumentTypes, that.argumentTypes); - } - - @Override - public int hashCode() { - return Objects.hash(name, returnType, argumentTypes); - } - } -} diff --git a/android-instrumentation/src/main/java/co/elastic/apm/android/instrumentation/ui/common/IsLastLifecycleMethod.java b/android-instrumentation/src/main/java/co/elastic/apm/android/instrumentation/ui/common/IsLastLifecycleMethod.java deleted file mode 100644 index c6066c6d6..000000000 --- a/android-instrumentation/src/main/java/co/elastic/apm/android/instrumentation/ui/common/IsLastLifecycleMethod.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * 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.apm.android.instrumentation.ui.common; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -@Retention(RetentionPolicy.RUNTIME) -public @interface IsLastLifecycleMethod { -} diff --git a/android-instrumentation/src/main/java/co/elastic/apm/android/instrumentation/ui/fragments/FragmentLifecycleMethodAdvice.java b/android-instrumentation/src/main/java/co/elastic/apm/android/instrumentation/ui/fragments/FragmentLifecycleMethodAdvice.java deleted file mode 100644 index 45784ca9d..000000000 --- a/android-instrumentation/src/main/java/co/elastic/apm/android/instrumentation/ui/fragments/FragmentLifecycleMethodAdvice.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * 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.apm.android.instrumentation.ui.fragments; - -import net.bytebuddy.asm.Advice; -import net.bytebuddy.implementation.bytecode.assign.Assigner; - -import java.util.Objects; - -import co.elastic.apm.android.instrumentation.ui.common.IsLastLifecycleMethod; -import co.elastic.apm.android.sdk.internal.instrumentation.LifecycleMultiMethodSpan; -import co.elastic.apm.android.sdk.traces.ElasticTracers; - -public class FragmentLifecycleMethodAdvice { - - @Advice.OnMethodEnter - public static void onMethodEnter( - @Advice.Origin("#t") String ownerName, - @Advice.Origin("#m") String methodName, - @Advice.Local("elasticSpanWithScope") LifecycleMultiMethodSpan.SpanWithScope spanWithScope) { - spanWithScope = LifecycleMultiMethodSpan.onMethodEnter(ownerName, methodName, ElasticTracers.androidFragment()); - } - - @Advice.OnMethodExit(onThrowable = Throwable.class) - public static void onMethodExit( - @Advice.Origin("#t") String ownerName, - @Advice.Origin("#r") String returnType, - @Advice.Return(typing = Assigner.Typing.DYNAMIC) Object returned, - @Advice.Local("elasticSpanWithScope") LifecycleMultiMethodSpan.SpanWithScope spanWithScope, - @IsLastLifecycleMethod boolean isLastMethod, - @Advice.Thrown Throwable thrown) { - boolean endRoot = false; - if (!Objects.equals(returnType, "void")) { - endRoot = returned == null; - } - LifecycleMultiMethodSpan.onMethodExit(ownerName, spanWithScope, thrown, endRoot || isLastMethod); - } -} diff --git a/android-instrumentation/src/main/java/co/elastic/apm/android/instrumentation/ui/fragments/FragmentLifecyclePlugin.java b/android-instrumentation/src/main/java/co/elastic/apm/android/instrumentation/ui/fragments/FragmentLifecyclePlugin.java deleted file mode 100644 index aee30e9f1..000000000 --- a/android-instrumentation/src/main/java/co/elastic/apm/android/instrumentation/ui/fragments/FragmentLifecyclePlugin.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * 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.apm.android.instrumentation.ui.fragments; - -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import androidx.annotation.NonNull; -import androidx.fragment.app.Fragment; - -import net.bytebuddy.build.AndroidDescriptor; -import net.bytebuddy.description.type.TypeDescription; - -import java.util.ArrayList; -import java.util.List; - -import co.elastic.apm.android.instrumentation.ui.common.BaseLifecycleMethodsPlugin; - -/** - * Instruments the following {@link Fragment} methods: - * - {@link Fragment#onCreate(android.os.Bundle)} - * - {@link Fragment#onCreateView(LayoutInflater, ViewGroup, Bundle)} - * - {@link Fragment#onViewCreated(View, Bundle)} - * To create a root span covering them all, as well as method-specific spans for each. - */ -public class FragmentLifecyclePlugin extends BaseLifecycleMethodsPlugin { - private final AndroidDescriptor androidDescriptor; - - public FragmentLifecyclePlugin(AndroidDescriptor androidDescriptor) { - this.androidDescriptor = androidDescriptor; - } - - @NonNull - @Override - protected Class getAdviceClass() { - return FragmentLifecycleMethodAdvice.class; - } - - @Override - protected List provideOrderedTargetMethods() { - List methods = new ArrayList<>(); - methods.add(MethodIdentity.create("onCreate", void.class, Bundle.class)); - methods.add(MethodIdentity.create("onCreateView", View.class, LayoutInflater.class, ViewGroup.class, Bundle.class)); - methods.add(MethodIdentity.create("onViewCreated", void.class, View.class, Bundle.class)); - return methods; - } - - @Override - public boolean matches(TypeDescription target) { - if (androidDescriptor.getTypeScope(target) == AndroidDescriptor.TypeScope.EXTERNAL) { - return false; - } - return !target.getSimpleName().startsWith("Hilt_") && target.isAssignableTo(Fragment.class); - } -} diff --git a/android-instrumentation/src/main/resources/META-INF/NOTICE b/android-instrumentation/src/main/resources/META-INF/NOTICE deleted file mode 100644 index 801f4faf6..000000000 --- a/android-instrumentation/src/main/resources/META-INF/NOTICE +++ /dev/null @@ -1,233 +0,0 @@ -Elastic APM Android Agent -Copyright 2018-2022 Elasticsearch B.V. - -############################################################################### - -This product includes software licensed under the 'Apache License Version 2.0' license from the following sources: - - - Android Support Library fragment (https://developer.android.com/jetpack/androidx/releases/fragment#1.5.3) - - Byte Buddy (without dependencies) - -############################################################################### - -Byte Buddy (without dependencies) NOTICE - -Copyright 2014 - Present Rafael Winterhalter - -Licensed 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. - - -############################################################################### - - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed 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. \ No newline at end of file diff --git a/android-instrumentation/src/main/resources/META-INF/net.bytebuddy/build.plugins b/android-instrumentation/src/main/resources/META-INF/net.bytebuddy/build.plugins deleted file mode 100644 index 7475598ee..000000000 --- a/android-instrumentation/src/main/resources/META-INF/net.bytebuddy/build.plugins +++ /dev/null @@ -1,4 +0,0 @@ -co.elastic.apm.android.instrumentation.okhttp.client.OkHttpClientPlugin -co.elastic.apm.android.instrumentation.ui.activities.ActivityLifecyclePlugin -co.elastic.apm.android.instrumentation.okhttp.eventlistener.CompositeEventListenerFactoryPlugin -co.elastic.apm.android.instrumentation.ui.fragments.FragmentLifecyclePlugin \ No newline at end of file diff --git a/android-plugin/build.gradle b/android-plugin/build.gradle index 9641b3793..15bac3c78 100644 --- a/android-plugin/build.gradle +++ b/android-plugin/build.gradle @@ -7,8 +7,8 @@ targetCompatibility = jvmCompatibility sourceCompatibility = jvmCompatibility dependencies { - implementation "net.bytebuddy:byte-buddy-gradle-plugin:$bytebuddy_version" - implementation "net.bytebuddy:byte-buddy:$bytebuddy_version" + implementation libs.byteBuddy + implementation libs.byteBuddy.plugin implementation project(':android-common') compileOnly "com.android.tools.build:gradle:$androidGradlePlugin_version" } @@ -17,7 +17,8 @@ buildConfig { packageName("${group}.generated") buildConfigField("String", "SDK_DEPENDENCY_URI", "\"$group:android-sdk:$version\"") buildConfigField("String", "SDK_KTX_DEPENDENCY_URI", "\"$group:android-sdk-ktx:$version\"") - buildConfigField("String", "INSTRUMENTATION_DEPENDENCY_URI", "\"$group:android-instrumentation:$version\"") + buildConfigField("String", "OTEL_OKHTTP_LIBRARY_URI", "\"${libs.opentelemetry.android.okhttpLib.get()}\"") + buildConfigField("String", "OTEL_OKHTTP_AGENT_URI", "\"${libs.opentelemetry.android.okhttpAgent.get()}\"") } licensesConfig { diff --git a/android-plugin/metadata/notice.properties b/android-plugin/metadata/notice.properties index 8f5b26d23..db25a0e33 100644 --- a/android-plugin/metadata/notice.properties +++ b/android-plugin/metadata/notice.properties @@ -1 +1 @@ -dependencies.hash=6DA47598A1BBA807C71A7E800B9F2509 \ No newline at end of file +dependencies.hash=09D717D772515279BACBDAB508BC19DE \ No newline at end of file diff --git a/android-plugin/src/main/java/co/elastic/apm/android/plugin/ApmAndroidAgentPlugin.java b/android-plugin/src/main/java/co/elastic/apm/android/plugin/ApmAndroidAgentPlugin.java index deb3f7bfe..5c89ec723 100644 --- a/android-plugin/src/main/java/co/elastic/apm/android/plugin/ApmAndroidAgentPlugin.java +++ b/android-plugin/src/main/java/co/elastic/apm/android/plugin/ApmAndroidAgentPlugin.java @@ -18,11 +18,9 @@ */ package co.elastic.apm.android.plugin; -import com.android.build.api.artifact.ScopedArtifact; import com.android.build.api.instrumentation.InstrumentationScope; import com.android.build.api.variant.ApplicationAndroidComponentsExtension; import com.android.build.api.variant.ApplicationVariant; -import com.android.build.api.variant.ScopedArtifacts; import com.android.build.api.variant.SourceDirectories; import com.android.build.gradle.BaseExtension; @@ -30,13 +28,7 @@ import org.gradle.api.Plugin; import org.gradle.api.Project; -import org.gradle.api.artifacts.Configuration; -import org.gradle.api.artifacts.ModuleVersionIdentifier; -import org.gradle.api.artifacts.ResolveException; -import org.gradle.api.artifacts.ResolvedArtifact; -import org.gradle.api.artifacts.ResolvedConfiguration; import org.gradle.api.plugins.ExtensionContainer; -import org.gradle.api.provider.Provider; import org.gradle.api.tasks.TaskProvider; import co.elastic.apm.android.common.internal.logging.Elog; @@ -44,8 +36,6 @@ import co.elastic.apm.android.plugin.instrumentation.ElasticLocalInstrumentationFactory; import co.elastic.apm.android.plugin.logging.GradleLoggerFactory; import co.elastic.apm.android.plugin.tasks.ApmInfoGeneratorTask; -import co.elastic.apm.android.plugin.tasks.OkHttpEventlistenerGenerator; -import co.elastic.apm.android.plugin.tasks.tools.ClasspathProvider; import co.elastic.apm.generated.BuildConfig; import kotlin.Unit; @@ -54,14 +44,12 @@ class ApmAndroidAgentPlugin implements Plugin { private Project project; private BaseExtension androidExtension; private ElasticApmExtension defaultExtension; - private ClasspathProvider classpathProvider; @Override public void apply(Project project) { this.project = project; Elog.init(new GradleLoggerFactory()); androidExtension = project.getExtensions().getByType(BaseExtension.class); - classpathProvider = new ClasspathProvider(); initializeElasticExtension(project); addBytebuddyPlugin(); addSdkDependency(); @@ -91,7 +79,8 @@ private boolean kotlinPluginFound() { } private void addInstrumentationDependency() { - project.getDependencies().add("byteBuddy", BuildConfig.INSTRUMENTATION_DEPENDENCY_URI); + project.getDependencies().add("implementation", BuildConfig.OTEL_OKHTTP_LIBRARY_URI); + project.getDependencies().add("byteBuddy", BuildConfig.OTEL_OKHTTP_AGENT_URI); } private void addTasks() { @@ -102,7 +91,6 @@ private void addTasks() { } private void enhanceVariant(ApplicationVariant applicationVariant) { - addOkhttpEventListenerGenerator(applicationVariant); addLocalRemapping(applicationVariant); addApmInfoGenerator(applicationVariant); } @@ -111,19 +99,6 @@ private void addLocalRemapping(ApplicationVariant applicationVariant) { applicationVariant.getInstrumentation().transformClassesWith(ElasticLocalInstrumentationFactory.class, InstrumentationScope.PROJECT, none -> Unit.INSTANCE); } - private void addOkhttpEventListenerGenerator(ApplicationVariant applicationVariant) { - TaskProvider taskProvider = - project.getTasks().register(applicationVariant.getName() + "GenerateOkhttpEventListener", OkHttpEventlistenerGenerator.class); - taskProvider.configure(task -> { - task.getOutputDir().set(project.getLayout().getBuildDirectory().dir(task.getName())); - task.getAppRuntimeClasspath().from(classpathProvider.getRuntimeClasspath(applicationVariant)); - task.getJvmTargetVersion().set(androidExtension.getCompileOptions().getTargetCompatibility().toString()); - }); - applicationVariant.getArtifacts().forScope(ScopedArtifacts.Scope.PROJECT) - .use(taskProvider) - .toAppend(ScopedArtifact.CLASSES.INSTANCE, OkHttpEventlistenerGenerator::getOutputDir); - } - private void addApmInfoGenerator(ApplicationVariant variant) { String variantName = variant.getName(); TaskProvider taskProvider = project.getTasks().register(variantName + "GenerateApmInfo", ApmInfoGeneratorTask.class); @@ -135,7 +110,6 @@ private void addApmInfoGenerator(ApplicationVariant variant) { apmInfoGenerator.getApiKey().set(defaultExtension.getApiKey()); apmInfoGenerator.getVariantName().set(variantName); apmInfoGenerator.getOutputDir().set(project.getLayout().getBuildDirectory().dir(apmInfoGenerator.getName())); - apmInfoGenerator.getOkHttpVersion().set(getOkhttpVersion(project, classpathProvider.getRuntimeConfiguration(variant))); }); SourceDirectories.Layered assets = variant.getSources().getAssets(); if (assets != null) { @@ -144,20 +118,4 @@ private void addApmInfoGenerator(ApplicationVariant variant) { Elog.getLogger().warn("Could not attach ApmInfoGeneratorTask"); } } - - private static Provider getOkhttpVersion(Project project, Configuration runtimeConfiguration) { - return project.provider(() -> { - ResolvedConfiguration resolvedConfiguration = runtimeConfiguration.getResolvedConfiguration(); - try { - for (ResolvedArtifact artifact : resolvedConfiguration.getResolvedArtifacts()) { - ModuleVersionIdentifier identifier = artifact.getModuleVersion().getId(); - if (identifier.getGroup().equals("com.squareup.okhttp3") && identifier.getName().equals("okhttp")) { - return identifier.getVersion(); - } - } - } catch (ResolveException ignored) { - } - return null; - }); - } } \ No newline at end of file diff --git a/android-plugin/src/main/java/co/elastic/apm/android/plugin/tasks/ApmInfoGeneratorTask.java b/android-plugin/src/main/java/co/elastic/apm/android/plugin/tasks/ApmInfoGeneratorTask.java index 453ccf265..7c84d25cb 100644 --- a/android-plugin/src/main/java/co/elastic/apm/android/plugin/tasks/ApmInfoGeneratorTask.java +++ b/android-plugin/src/main/java/co/elastic/apm/android/plugin/tasks/ApmInfoGeneratorTask.java @@ -60,10 +60,6 @@ public abstract class ApmInfoGeneratorTask extends DefaultTask { @Input public abstract Property getApiKey(); - @Optional - @Input - public abstract Property getOkHttpVersion(); - @OutputDirectory public abstract DirectoryProperty getOutputDir(); @@ -94,10 +90,6 @@ public void execute() { if (apiKey != null) { properties.put(ApmInfo.KEY_SERVER_API_KEY, apiKey); } - String okhttpVersion = getOkHttpVersion().getOrNull(); - if (okhttpVersion != null) { - properties.put(ApmInfo.KEY_SCOPE_OKHTTP_VERSION, okhttpVersion); - } try (OutputStream outputStream = new FileOutputStream(propertiesFile)) { properties.store(outputStream, null); diff --git a/android-plugin/src/main/java/co/elastic/apm/android/plugin/tasks/OkHttpEventlistenerGenerator.java b/android-plugin/src/main/java/co/elastic/apm/android/plugin/tasks/OkHttpEventlistenerGenerator.java deleted file mode 100644 index 2342e201c..000000000 --- a/android-plugin/src/main/java/co/elastic/apm/android/plugin/tasks/OkHttpEventlistenerGenerator.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * 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.apm.android.plugin.tasks; - -import static net.bytebuddy.matcher.ElementMatchers.not; - -import net.bytebuddy.ByteBuddy; -import net.bytebuddy.ClassFileVersion; -import net.bytebuddy.asm.Advice; -import net.bytebuddy.description.type.TypeDescription; -import net.bytebuddy.dynamic.ClassFileLocator; -import net.bytebuddy.matcher.ElementMatchers; -import net.bytebuddy.pool.TypePool; - -import org.gradle.api.DefaultTask; -import org.gradle.api.file.ConfigurableFileCollection; -import org.gradle.api.file.DirectoryProperty; -import org.gradle.api.provider.Property; -import org.gradle.api.tasks.Input; -import org.gradle.api.tasks.InputFiles; -import org.gradle.api.tasks.OutputDirectory; -import org.gradle.api.tasks.TaskAction; - -import java.io.File; -import java.io.IOException; -import java.lang.reflect.Method; -import java.nio.file.Files; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; - -import co.elastic.apm.android.common.MethodCaller; -import co.elastic.apm.android.common.okhttp.eventlistener.CompositeEventListener; -import okhttp3.EventListener; - -public abstract class OkHttpEventlistenerGenerator extends DefaultTask { - - @Input - public abstract Property getJvmTargetVersion(); - - @InputFiles - public abstract ConfigurableFileCollection getAppRuntimeClasspath(); - - @OutputDirectory - public abstract DirectoryProperty getOutputDir(); - - @TaskAction - public void action() { - String name = CompositeEventListener.getGeneratedName(); - byte[] bytes = getByteBuddy().subclass(CompositeEventListener.class) - .name(name) - .method(ElementMatchers.isDeclaredBy(getEventListenerFromProject()) - .and(not(ElementMatchers.isStatic())) - .and(not(ElementMatchers.isConstructor())) - ).intercept(Advice.to(CompositeEventListenerAdvice.class)) - .make() - .getBytes(); - - String filePath = name.replaceAll("\\.", "/") + ".class"; - storeClassFile(bytes, filePath); - } - - private TypeDescription getEventListenerFromProject() { - TypePool typePool = getTypePool(getAppRuntimeClasspath().getFiles()); - return typePool.describe(EventListener.class.getName()).resolve(); - } - - private TypePool getTypePool(Set dependencies) { - List classFileLocators = new ArrayList<>(); - try { - for (File dependency : dependencies) { - if (dependency.isFile()) { - classFileLocators.add(ClassFileLocator.ForJarFile.of(dependency)); - } - } - } catch (IOException e) { - throw new RuntimeException(e); - } - return TypePool.Default.of(new ClassFileLocator.Compound(classFileLocators)); - } - - private ByteBuddy getByteBuddy() { - ClassFileVersion classFileVersion = ClassFileVersion.ofJavaVersionString(getJvmTargetVersion().get()); - return new ByteBuddy(classFileVersion); - } - - private void storeClassFile(byte[] classBytes, String relativePath) { - File file = new File(getOutputDir().get().getAsFile(), relativePath); - if (!file.getParentFile().exists()) { - boolean dirsCreated = file.getParentFile().mkdirs(); - if (!dirsCreated) { - throw new RuntimeException("Could not create dirs for " + relativePath); - } - } - try { - Files.write(file.toPath(), classBytes); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - public static class CompositeEventListenerAdvice { - - @Advice.OnMethodEnter - public static void onEnter(@Advice.This MethodCaller owner, @Advice.Origin Method self, @Advice.AllArguments Object[] args) { - owner.doCall(self, args); - } - } -} diff --git a/android-plugin/src/main/java/co/elastic/apm/android/plugin/tasks/tools/ClasspathProvider.java b/android-plugin/src/main/java/co/elastic/apm/android/plugin/tasks/tools/ClasspathProvider.java deleted file mode 100644 index 5c661bac5..000000000 --- a/android-plugin/src/main/java/co/elastic/apm/android/plugin/tasks/tools/ClasspathProvider.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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.apm.android.plugin.tasks.tools; - -import com.android.build.api.variant.Variant; - -import org.gradle.api.Action; -import org.gradle.api.artifacts.ArtifactView; -import org.gradle.api.artifacts.Configuration; -import org.gradle.api.attributes.Attribute; -import org.gradle.api.file.FileCollection; - -public class ClasspathProvider implements Action { - - private static final Attribute ARTIFACT_TYPE_ATTR = Attribute.of("artifactType", String.class); - private FileCollection runtimeClasspath; - - public FileCollection getRuntimeClasspath(Variant variant) { - if (runtimeClasspath == null) { - runtimeClasspath = findClasspath(variant); - } - - return runtimeClasspath; - } - - public Configuration getRuntimeConfiguration(Variant variant) { - return variant.getRuntimeConfiguration(); - } - - private FileCollection findClasspath(Variant variant) { - return getRuntimeConfiguration(variant).getIncoming() - .artifactView(this) - .getArtifacts() - .getArtifactFiles(); - } - - @Override - public void execute(ArtifactView.ViewConfiguration configuration) { - configuration.setLenient(false); - configuration.getAttributes().attribute(ARTIFACT_TYPE_ATTR, "android-classes-jar"); - } -} diff --git a/android-sdk-ktx/build.gradle b/android-sdk-ktx/build.gradle index 763e0e2b4..c185474e8 100644 --- a/android-sdk-ktx/build.gradle +++ b/android-sdk-ktx/build.gradle @@ -35,8 +35,8 @@ licensesConfig { } dependencies { - api "io.opentelemetry:opentelemetry-extension-kotlin:$openTelemetry_version" + api libs.opentelemetry.kotlin implementation project(':android-sdk') implementation project(':android-common') - compileOnly "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4" + compileOnly libs.kotlin.coroutines } \ No newline at end of file diff --git a/android-sdk-ktx/metadata/notice.properties b/android-sdk-ktx/metadata/notice.properties index 403b63c43..d47cc92f4 100644 --- a/android-sdk-ktx/metadata/notice.properties +++ b/android-sdk-ktx/metadata/notice.properties @@ -1 +1 @@ -dependencies.hash=81F0B6D19D5388F15DBE3E189D6C1B22 \ No newline at end of file +dependencies.hash=AFDBF157F73953F0B7829B5B718072E7 \ No newline at end of file diff --git a/android-sdk-ktx/src/main/resources/META-INF/NOTICE b/android-sdk-ktx/src/main/resources/META-INF/NOTICE index 9b44139ff..174884054 100644 --- a/android-sdk-ktx/src/main/resources/META-INF/NOTICE +++ b/android-sdk-ktx/src/main/resources/META-INF/NOTICE @@ -5,7 +5,7 @@ Copyright 2018-2022 Elasticsearch B.V. This product includes software licensed under the 'Apache License Version 2.0' license from the following sources: - - Kotlin Stdlib Jdk8 (https://kotlinlang.org/) + - Kotlin Stdlib (https://kotlinlang.org/) - OpenTelemetry Java (https://github.com/open-telemetry/opentelemetry-java) ############################################################################### diff --git a/android-sdk/build.gradle b/android-sdk/build.gradle index c1a98a705..980a6e8f1 100644 --- a/android-sdk/build.gradle +++ b/android-sdk/build.gradle @@ -39,25 +39,36 @@ shadowJar { } dependencies { - api "io.opentelemetry:opentelemetry-api:$openTelemetry_version" - api "io.opentelemetry:opentelemetry-sdk:$openTelemetry_version" - api "io.opentelemetry:opentelemetry-api-events:$openTelemetry_version-alpha" - api "com.squareup.okhttp3:okhttp:$okhttp_version" - api 'org.stagemonitor:stagemonitor-configuration:0.89.1' - implementation "androidx.lifecycle:lifecycle-process:2.5.1" - implementation 'com.blogspot.mydailyjava:weak-lock-free:0.18' + api libs.opentelemetry.sdk + api libs.opentelemetry.android + api libs.opentelemetry.events + api libs.stagemonitor.configuration + api libs.okhttp implementation project(':android-common') - implementation "io.opentelemetry:opentelemetry-exporter-otlp:$openTelemetry_version" - implementation "io.opentelemetry.semconv:opentelemetry-semconv:1.21.0-alpha" - implementation "io.opentelemetry.contrib:opentelemetry-disk-buffering:$openTelemetry_version-alpha" - implementation "androidx.annotation:annotation:$androidAnnotations_version" - implementation 'com.dslplatform:dsl-json-java8:1.10.0' - embedded 'com.github.instacart.truetime-android:library:3.5' + implementation libs.androidx.lifecycle + implementation libs.weaklockfree + implementation libs.opentelemetry.exporter.otlp + implementation libs.opentelemetry.semconv + implementation libs.opentelemetry.diskBuffering + implementation libs.androidx.annotations + implementation libs.dsl.json + embedded libs.truetime annotationProcessor 'co.elastic.apm.compile:processor' compileOnly 'co.elastic.apm.compile:processor' - testImplementation "org.mockito:mockito-core:$mockito_version" - testImplementation "org.mockito:mockito-inline:$mockito_version" - testImplementation "junit:junit:$junit_version" - androidTestImplementation 'androidx.test:runner:1.4.0' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' + testImplementation libs.bundles.mocking + testImplementation libs.junit + + // To avoid enforcing compileSdk > 33: + constraints { + add("implementation", "androidx.navigation:navigation-fragment") { + version { + strictly("2.6.0") + } + } + add("implementation", "androidx.core:core") { + version { + strictly("1.10.1") + } + } + } } \ No newline at end of file diff --git a/android-sdk/metadata/notice.properties b/android-sdk/metadata/notice.properties index d1c5b0a71..dcdfbcf82 100644 --- a/android-sdk/metadata/notice.properties +++ b/android-sdk/metadata/notice.properties @@ -1 +1 @@ -dependencies.hash=81E0A693875E87899494D84AB8C8D79D \ No newline at end of file +dependencies.hash=F58F6A39BDAD2A7E96BD4C0924CF4A0C \ No newline at end of file diff --git a/android-sdk/src/main/java/co/elastic/apm/android/sdk/ElasticApmAgent.java b/android-sdk/src/main/java/co/elastic/apm/android/sdk/ElasticApmAgent.java index b53b51b38..e7f441dca 100644 --- a/android-sdk/src/main/java/co/elastic/apm/android/sdk/ElasticApmAgent.java +++ b/android-sdk/src/main/java/co/elastic/apm/android/sdk/ElasticApmAgent.java @@ -49,7 +49,7 @@ import co.elastic.apm.android.sdk.internal.exceptions.ElasticExceptionHandler; import co.elastic.apm.android.sdk.internal.features.centralconfig.initializer.CentralConfigurationInitializer; import co.elastic.apm.android.sdk.internal.features.centralconfig.poll.ConfigurationPollManager; -import co.elastic.apm.android.sdk.internal.features.launchtime.LaunchTimeActivityCallback; +import co.elastic.apm.android.sdk.internal.features.launchtime.LaunchTimeApplicationListener; import co.elastic.apm.android.sdk.internal.features.lifecycle.ElasticProcessLifecycleObserver; import co.elastic.apm.android.sdk.internal.features.persistence.PersistenceInitializer; import co.elastic.apm.android.sdk.internal.features.sampling.SampleRateManager; @@ -65,21 +65,24 @@ import co.elastic.apm.android.sdk.internal.time.ntp.NtpManager; import co.elastic.apm.android.sdk.internal.utilities.logging.AndroidLoggerFactory; import co.elastic.apm.android.sdk.session.SessionManager; +import io.opentelemetry.android.OpenTelemetryRum; +import io.opentelemetry.android.config.OtelRumConfig; +import io.opentelemetry.android.instrumentation.activity.VisibleScreenTracker; +import io.opentelemetry.android.instrumentation.lifecycle.AndroidLifecycleInstrumentationBuilder; +import io.opentelemetry.android.instrumentation.startup.AppStartupTimer; import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.events.GlobalEventEmitterProvider; -import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; -import io.opentelemetry.context.propagation.ContextPropagators; -import io.opentelemetry.sdk.OpenTelemetrySdk; import io.opentelemetry.sdk.logs.LogRecordProcessor; -import io.opentelemetry.sdk.logs.SdkLoggerProvider; +import io.opentelemetry.sdk.logs.SdkLoggerProviderBuilder; import io.opentelemetry.sdk.logs.data.LogRecordData; import io.opentelemetry.sdk.logs.internal.SdkEventEmitterProvider; -import io.opentelemetry.sdk.metrics.SdkMeterProvider; +import io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder; import io.opentelemetry.sdk.metrics.data.MetricData; import io.opentelemetry.sdk.resources.Resource; import io.opentelemetry.sdk.trace.ReadableSpan; -import io.opentelemetry.sdk.trace.SdkTracerProvider; +import io.opentelemetry.sdk.trace.SdkTracerProviderBuilder; import io.opentelemetry.sdk.trace.SpanProcessor; public final class ElasticApmAgent { @@ -93,35 +96,86 @@ public static ElasticApmAgent get() { return instance; } + /** + * Initializes the Elastic Agent. + * + * @param context The Application context. + * @deprecated Use {@link ElasticApmAgent#initialize(Application)} instead. This method will be + * removed in the next major version release. + */ + @Deprecated public static ElasticApmAgent initialize(Context context) { return initialize(context, null, null); } + /** + * Initializes the Elastic Agent. + * + * @param context The Application context. + * @param configuration The Elastic configuration. + * @deprecated Use {@link ElasticApmAgent#initialize(Application, ElasticApmConfiguration)} instead. This method will be + * removed in the next major version release. + */ + @Deprecated public static ElasticApmAgent initialize(Context context, ElasticApmConfiguration configuration) { return initialize(context, configuration, null); } + /** + * Initializes the Elastic Agent. + * + * @param context The Application context. + * @param connectivity The APM server connectivity config. + * @deprecated Use {@link ElasticApmAgent#initialize(Application, Connectivity)} instead. This method will be + * removed in the next major version release. + */ + @Deprecated public static ElasticApmAgent initialize(Context context, Connectivity connectivity) { return initialize(context, null, connectivity); } + /** + * Initializes the Elastic Agent. + * + * @param context The Application context. + * @param configuration The Elastic configuration. + * @param connectivity The APM server connectivity config. + * @deprecated Use {@link ElasticApmAgent#initialize(Application, ElasticApmConfiguration, Connectivity)} instead. This method will be + * removed in the next major version release. + */ + @Deprecated public static ElasticApmAgent initialize(Context context, ElasticApmConfiguration configuration, Connectivity connectivity) { - return initialize(context, configuration, connectivity, null); + return initialize((Application) context, configuration, connectivity, null); } - private synchronized static ElasticApmAgent initialize(Context context, ElasticApmConfiguration configuration, Connectivity connectivity, AgentDependenciesInjector.Interceptor interceptor) { + public static ElasticApmAgent initialize(Application application) { + return initialize(application, null, null); + } + + public static ElasticApmAgent initialize(Application application, ElasticApmConfiguration configuration) { + return initialize(application, configuration, null); + } + + public static ElasticApmAgent initialize(Application application, Connectivity connectivity) { + return initialize(application, null, connectivity); + } + + public static ElasticApmAgent initialize(Application application, ElasticApmConfiguration configuration, Connectivity connectivity) { + return initialize(application, configuration, connectivity, null); + } + + private synchronized static ElasticApmAgent initialize(Application application, ElasticApmConfiguration configuration, Connectivity connectivity, AgentDependenciesInjector.Interceptor interceptor) { if (instance != null) { throw new IllegalStateException("Already initialized"); } - Context appContext = context.getApplicationContext(); ElasticApmConfiguration finalConfiguration = (configuration == null) ? ElasticApmConfiguration.getDefault() : configuration; Elog.init(new AndroidLoggerFactory(finalConfiguration.libraryLoggingPolicy)); - ServiceManager.initialize(appContext); + ServiceManager.initialize(application); ServiceManager.get().start(); Connectivity finalConnectivity = (connectivity == null) ? Connectivity.getDefault() : connectivity; - AgentDependenciesInjector injector = process(new DefaultAgentDependenciesInjector(appContext, finalConfiguration, finalConnectivity), interceptor); + AgentDependenciesInjector injector = process(new DefaultAgentDependenciesInjector(application, finalConfiguration, finalConnectivity), interceptor); instance = new ElasticApmAgent(finalConfiguration); - instance.onInitializationFinished(appContext, injector); + instance.onInitializationFinished(application, injector); initializePeriodicWork(); return instance; } @@ -165,13 +219,12 @@ private ElasticApmAgent(ElasticApmConfiguration configuration) { flusher = new Flusher(); } - private void onInitializationFinished(Context context, AgentDependenciesInjector injector) { + private void onInitializationFinished(Application application, AgentDependenciesInjector injector) { initializeNtpManager(injector); initializeSessionManager(injector); initializeConfigurations(injector); - initializeOpentelemetry(injector); + initializeOpentelemetry(application, injector); initializeCrashReports(); - initializeLaunchTimeTracker(context); initializeLifecycleObserver(); } @@ -208,20 +261,18 @@ private void initializeSessionManager(AgentDependenciesInjector injector) { SessionManager.set(sessionManager); } - private void initializeLaunchTimeTracker(Context context) { - ((Application) context).registerActivityLifecycleCallbacks(new LaunchTimeActivityCallback()); - } - private void initializeCrashReports() { if (Instrumentations.isCrashReportingEnabled()) { Thread.setDefaultUncaughtExceptionHandler(ElasticExceptionHandler.getInstance()); } } - private void initializeOpentelemetry(AgentDependenciesInjector injector) { - SignalConfiguration signalConfiguration = configuration.signalConfiguration; - if (signalConfiguration == null) { + private void initializeOpentelemetry(Application app, AgentDependenciesInjector injector) { + SignalConfiguration signalConfiguration; + if (configuration.signalConfiguration == null) { signalConfiguration = SignalConfiguration.getDefault(); + } else { + signalConfiguration = configuration.signalConfiguration; } SampleRateManager sampleRateManager = new SampleRateManager(); PersistenceInitializer persistenceInitializer = tryInitializePersistence(signalConfiguration, injector); @@ -230,21 +281,28 @@ private void initializeOpentelemetry(AgentDependenciesInjector injector) { Resource resource = Resource.getDefault() .merge(Resource.create(resourceAttrs)); - SdkMeterProvider meterProvider = getMeterProvider(signalConfiguration, resource, sampleRateManager); - SdkLoggerProvider loggerProvider = getLoggerProvider(signalConfiguration, resource, globalAttributesVisitor, sampleRateManager); - - flusher.setMeterDelegator(meterProvider::forceFlush); - flusher.setLoggerDelegator(loggerProvider::forceFlush); - - SdkEventEmitterProvider eventEmitterProvider = SdkEventEmitterProvider.create(loggerProvider, ntpManager.getClock()); - GlobalEventEmitterProvider.set(eventEmitterProvider); + OtelRumConfig rumConfig = new OtelRumConfig(); + rumConfig.disableNetworkAttributes(); + rumConfig.disableNetworkChangeMonitoring(); + OpenTelemetryRum rum = OpenTelemetryRum.builder(app, rumConfig) + .addTracerProviderCustomizer((sdkTracerProviderBuilder, application) -> configureTracerProviderBuilder(sdkTracerProviderBuilder, signalConfiguration, resource, globalAttributesVisitor, sampleRateManager)) + .addMeterProviderCustomizer((sdkMeterProviderBuilder, application) -> configureMeterProviderBuilder(sdkMeterProviderBuilder, signalConfiguration, resource, sampleRateManager)) + .addLoggerProviderCustomizer((sdkLoggerProviderBuilder, application) -> configureLoggerProviderBuilder(sdkLoggerProviderBuilder, signalConfiguration, resource, globalAttributesVisitor, sampleRateManager)) + .addInstrumentation(instrumentedApplication -> { + // Adding screen spans + new AndroidLifecycleInstrumentationBuilder() + .setVisibleScreenTracker(new VisibleScreenTracker()) + .setStartupTimer(new AppStartupTimer()) + .build().installOn(instrumentedApplication); + + // Adding launch time metrics + instrumentedApplication.registerApplicationStateListener(new LaunchTimeApplicationListener()); + }) + .build(); - OpenTelemetrySdk.builder() - .setTracerProvider(getTracerProvider(signalConfiguration, resource, globalAttributesVisitor, sampleRateManager)) - .setLoggerProvider(loggerProvider) - .setMeterProvider(meterProvider) - .setPropagators(getContextPropagator()) - .buildAndRegisterGlobal(); + OpenTelemetry openTelemetry = rum.getOpenTelemetry(); + GlobalOpenTelemetry.set(openTelemetry); + GlobalEventEmitterProvider.set(SdkEventEmitterProvider.create(openTelemetry.getLogsBridge(), ntpManager.getClock())); if (persistenceInitializer != null) { SignalDiskExporter.set(persistenceInitializer.createSignalDiskExporter()); @@ -280,9 +338,10 @@ private AttributesVisitor getResourceAttributesVisitor() { ); } - private SdkTracerProvider getTracerProvider(SignalConfiguration signalConfiguration, - Resource resource, - AttributesVisitor commonAttrVisitor, SampleRateManager sampleRateManager) { + private SdkTracerProviderBuilder configureTracerProviderBuilder(SdkTracerProviderBuilder builder, + SignalConfiguration signalConfiguration, + Resource resource, + AttributesVisitor commonAttrVisitor, SampleRateManager sampleRateManager) { SpanProcessor spanProcessor = signalConfiguration.getSpanProcessor(); ComposeAttributesVisitor spanAttributesVisitor = AttributesVisitor.compose( commonAttrVisitor, @@ -296,17 +355,17 @@ private SdkTracerProvider getTracerProvider(SignalConfiguration signalConfigurat filter.addAllFilters(configuration.httpTraceConfiguration.httpFilters); processor.setFilter(filter); - return SdkTracerProvider.builder() + return builder .setClock(ntpManager.getClock()) .addSpanProcessor(processor) - .setResource(resource) - .build(); + .setResource(resource); } - private SdkLoggerProvider getLoggerProvider(SignalConfiguration signalConfiguration, - Resource resource, - AttributesVisitor commonAttrVisitor, - SampleRateManager sampleRateManager) { + private SdkLoggerProviderBuilder configureLoggerProviderBuilder(SdkLoggerProviderBuilder builder, + SignalConfiguration signalConfiguration, + Resource resource, + AttributesVisitor commonAttrVisitor, + SampleRateManager sampleRateManager) { LogRecordProcessor logProcessor = signalConfiguration.getLogProcessor(); ComposeAttributesVisitor logAttributes = AttributesVisitor.compose( commonAttrVisitor, @@ -318,31 +377,31 @@ private SdkLoggerProvider getLoggerProvider(SignalConfiguration signalConfigurat logFilter.addAllFilters(configuration.logFilters); elasticProcessor.setFilter(logFilter); - return SdkLoggerProvider.builder() + flusher.setLoggerDelegator(elasticProcessor::forceFlush); + + return builder .setResource(resource) .setClock(ntpManager.getClock()) - .addLogRecordProcessor(elasticProcessor) - .build(); + .addLogRecordProcessor(elasticProcessor); } - private SdkMeterProvider getMeterProvider(SignalConfiguration signalConfiguration, - Resource resource, - SampleRateManager sampleRateManager) { + private SdkMeterProviderBuilder configureMeterProviderBuilder( + SdkMeterProviderBuilder builder, + SignalConfiguration signalConfiguration, + Resource resource, + SampleRateManager sampleRateManager) { ElasticMetricReader elasticMetricReader = new ElasticMetricReader(signalConfiguration.getMetricReader()); ComposableFilter metricFilter = new ComposableFilter<>(); metricFilter.addFilter(sampleRateManager.metricFilter); metricFilter.addAllFilters(configuration.metricFilters); elasticMetricReader.setFilter(metricFilter); - return SdkMeterProvider.builder() + flusher.setMeterDelegator(elasticMetricReader::forceFlush); + + return builder .setClock(ntpManager.getClock()) .registerMetricReader(elasticMetricReader) - .setResource(resource) - .build(); - } - - private ContextPropagators getContextPropagator() { - return ContextPropagators.create(W3CTraceContextPropagator.getInstance()); + .setResource(resource); } private static PeriodicWorkService getPeriodicWorkService() { diff --git a/android-sdk/src/main/java/co/elastic/apm/android/sdk/internal/features/launchtime/LaunchTimeActivityCallback.java b/android-sdk/src/main/java/co/elastic/apm/android/sdk/internal/features/launchtime/LaunchTimeApplicationListener.java similarity index 64% rename from android-sdk/src/main/java/co/elastic/apm/android/sdk/internal/features/launchtime/LaunchTimeActivityCallback.java rename to android-sdk/src/main/java/co/elastic/apm/android/sdk/internal/features/launchtime/LaunchTimeApplicationListener.java index 72f7681e7..9dbd362f3 100644 --- a/android-sdk/src/main/java/co/elastic/apm/android/sdk/internal/features/launchtime/LaunchTimeActivityCallback.java +++ b/android-sdk/src/main/java/co/elastic/apm/android/sdk/internal/features/launchtime/LaunchTimeApplicationListener.java @@ -18,29 +18,21 @@ */ package co.elastic.apm.android.sdk.internal.features.launchtime; -import android.app.Activity; -import android.app.Application; -import android.os.Bundle; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - import java.util.concurrent.TimeUnit; import co.elastic.apm.android.common.internal.logging.Elog; import co.elastic.apm.android.sdk.ElasticApmAgent; import co.elastic.apm.android.sdk.instrumentation.Instrumentations; import co.elastic.apm.android.sdk.metrics.ElasticMeters; +import io.opentelemetry.android.instrumentation.ApplicationStateListener; import io.opentelemetry.api.metrics.BatchCallback; import io.opentelemetry.api.metrics.Meter; import io.opentelemetry.api.metrics.ObservableDoubleMeasurement; -public final class LaunchTimeActivityCallback implements Application.ActivityLifecycleCallbacks { +public final class LaunchTimeApplicationListener implements ApplicationStateListener { @Override - public void onActivityPreResumed(@NonNull Activity activity) { - unregisterCallback(activity); - + public void onApplicationForegrounded() { if (LaunchTimeTracker.stopTimer()) { if (Instrumentations.isAppLaunchTimeEnabled()) { long launchTimeInNanos = LaunchTimeTracker.getElapsedTimeInNanos(); @@ -61,43 +53,8 @@ private void sendAppLaunchTimeMetric(long launchTimeMillis) { batchCallback.close(); } - private void unregisterCallback(@NonNull Activity activity) { - Elog.getLogger().debug("Unregistering launch time activity callback"); - activity.getApplication().unregisterActivityLifecycleCallbacks(this); - } - - @Override - public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) { - - } - - @Override - public void onActivityStarted(@NonNull Activity activity) { - - } - - @Override - public void onActivityResumed(@NonNull Activity activity) { - - } - - @Override - public void onActivityPaused(@NonNull Activity activity) { - - } - - @Override - public void onActivityStopped(@NonNull Activity activity) { - - } - - @Override - public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState) { - - } - @Override - public void onActivityDestroyed(@NonNull Activity activity) { + public void onApplicationBackgrounded() { } } diff --git a/android-sdk/src/main/java/co/elastic/apm/android/sdk/internal/services/metadata/ApmMetadataService.java b/android-sdk/src/main/java/co/elastic/apm/android/sdk/internal/services/metadata/ApmMetadataService.java index 790ffdb77..78277e322 100644 --- a/android-sdk/src/main/java/co/elastic/apm/android/sdk/internal/services/metadata/ApmMetadataService.java +++ b/android-sdk/src/main/java/co/elastic/apm/android/sdk/internal/services/metadata/ApmMetadataService.java @@ -68,11 +68,6 @@ public String getDeploymentEnvironment() { return apmInfoPropertiesProvider.get().getProperty(ApmInfo.KEY_SERVICE_ENVIRONMENT); } - @Nullable - public String getOkHttpVersion() { - return apmInfoPropertiesProvider.get().getProperty(ApmInfo.KEY_SCOPE_OKHTTP_VERSION); - } - @Override public void start() { diff --git a/android-sdk/src/main/java/co/elastic/apm/android/sdk/traces/ElasticTracers.java b/android-sdk/src/main/java/co/elastic/apm/android/sdk/traces/ElasticTracers.java index c6415d83b..e18978506 100644 --- a/android-sdk/src/main/java/co/elastic/apm/android/sdk/traces/ElasticTracers.java +++ b/android-sdk/src/main/java/co/elastic/apm/android/sdk/traces/ElasticTracers.java @@ -21,9 +21,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import co.elastic.apm.android.sdk.internal.services.Service; -import co.elastic.apm.android.sdk.internal.services.ServiceManager; -import co.elastic.apm.android.sdk.internal.services.metadata.ApmMetadataService; import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.api.trace.Tracer; @@ -41,11 +38,6 @@ public static Tracer create(String name) { return create(name, null); } - public static Tracer okhttp() { - ApmMetadataService service = ServiceManager.get().getService(Service.Names.METADATA); - return create("OkHttp", service.getOkHttpVersion()); - } - public static Tracer androidActivity() { return create("Android Activity"); } diff --git a/android-sdk/src/main/java/co/elastic/apm/android/sdk/traces/http/impl/okhttp/OkHttpContextStore.java b/android-sdk/src/main/java/co/elastic/apm/android/sdk/traces/http/impl/okhttp/OkHttpContextStore.java deleted file mode 100644 index 444218868..000000000 --- a/android-sdk/src/main/java/co/elastic/apm/android/sdk/traces/http/impl/okhttp/OkHttpContextStore.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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.apm.android.sdk.traces.http.impl.okhttp; - -import com.blogspot.mydailyjava.weaklockfree.WeakConcurrentMap; - -import io.opentelemetry.context.Context; -import okhttp3.Request; - -public class OkHttpContextStore { - - private final WeakConcurrentMap spanContexts = new WeakConcurrentMap.WithInlinedExpunction<>(); - - public void put(Request request, Context spanContext) { - spanContexts.put(request, spanContext); - } - - public void remove(Request request) { - spanContexts.remove(request); - } - - public Context get(Request request) { - return spanContexts.getIfPresent(request); - } -} diff --git a/android-sdk/src/main/java/co/elastic/apm/android/sdk/traces/http/impl/okhttp/OtelOkHttpEventListener.java b/android-sdk/src/main/java/co/elastic/apm/android/sdk/traces/http/impl/okhttp/OtelOkHttpEventListener.java deleted file mode 100644 index 5793caceb..000000000 --- a/android-sdk/src/main/java/co/elastic/apm/android/sdk/traces/http/impl/okhttp/OtelOkHttpEventListener.java +++ /dev/null @@ -1,267 +0,0 @@ -/* - * 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.apm.android.sdk.traces.http.impl.okhttp; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import java.io.IOException; - -import co.elastic.apm.android.common.internal.logging.Elog; -import co.elastic.apm.android.sdk.ElasticApmAgent; -import co.elastic.apm.android.sdk.attributes.AttributesCreator; -import co.elastic.apm.android.sdk.attributes.AttributesVisitor; -import co.elastic.apm.android.sdk.instrumentation.Instrumentations; -import co.elastic.apm.android.sdk.traces.ElasticTracers; -import co.elastic.apm.android.sdk.traces.http.HttpTraceConfiguration; -import co.elastic.apm.android.sdk.traces.http.data.HttpRequest; -import co.elastic.apm.android.sdk.traces.http.impl.okhttp.utils.WrapperSpanCloser; -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.trace.Span; -import io.opentelemetry.api.trace.SpanKind; -import io.opentelemetry.api.trace.StatusCode; -import io.opentelemetry.api.trace.Tracer; -import io.opentelemetry.context.Context; -import io.opentelemetry.context.ContextKey; -import io.opentelemetry.semconv.SemanticAttributes; -import okhttp3.Call; -import okhttp3.EventListener; -import okhttp3.HttpUrl; -import okhttp3.Request; -import okhttp3.Response; - -public class OtelOkHttpEventListener extends EventListener { - - private static final String SPAN_NAME_FORMAT = "%s %s"; - private static final String TRANSACTION_SPAN_NAME_FORMAT = "Transaction - " + SPAN_NAME_FORMAT; - private static final ContextKey WRAPPER_CLOSER_KEY = ContextKey.named("wrapper-closer"); - private final OkHttpContextStore contextStore; - private HttpTraceConfiguration configuration; - private Tracer okHttpTracer; - - private OtelOkHttpEventListener(OkHttpContextStore contextStore) { - this.contextStore = contextStore; - } - - @Override - public void callStart(Call call) { - super.callStart(call); - if (!Instrumentations.isHttpTracingEnabled()) { - return; - } - Request request = call.request(); - Elog.getLogger().info("Intercepting OkHttp request"); - Elog.getLogger().debug("Intercepting OkHttp request: {}", request.url()); - - if (isOtelExporterCall(request.url())) { - Elog.getLogger().info("Ignoring OTel exporting related http request"); - return; - } - - String method = request.method(); - HttpUrl url = request.url(); - String host = url.host(); - Tracer okhttpTracer = getTracer(); - - Context parentContext = Context.current(); - Span wrapperSpan = null; - if (thereIsNoParentSpan(parentContext)) { - wrapperSpan = createWrapperSpan(okhttpTracer, method, host); - parentContext = parentContext.with(wrapperSpan); - } - - AttributesVisitor httpAttributes = getConfiguration().createHttpAttributesVisitor(convertRequest(request)); - Span span = okhttpTracer.spanBuilder(String.format(SPAN_NAME_FORMAT, method, host)) - .setSpanKind(SpanKind.CLIENT) - .setAllAttributes(AttributesCreator.from(httpAttributes).create()) - .setParent(parentContext) - .startSpan(); - Context spanContext = parentContext.with(span); - - if (wrapperSpan != null) { - // Attaching the wrapper span to end it right after the http span is ended. - spanContext = spanContext.with(WRAPPER_CLOSER_KEY, wrapperSpan::end); - } - - contextStore.put(request, spanContext); - } - - private boolean isOtelExporterCall(HttpUrl url) { - return url.url().getPath().startsWith("/opentelemetry.proto.collector"); - } - - private Span createWrapperSpan(Tracer okhttpTracer, String method, String host) { - Elog.getLogger().info("Creating wrapper span"); - return okhttpTracer.spanBuilder(String.format(TRANSACTION_SPAN_NAME_FORMAT, method, host)) - .startSpan(); - } - - private boolean thereIsNoParentSpan(Context parentContext) { - return parentContext == Context.root(); - } - - @Override - public void responseHeadersEnd(@NonNull Call call, @NonNull Response response) { - Span span = retrieveSpan(call.request()); - if (span != null) { - int code = response.code(); - - setStatusCode(span, code); - setResponseSize(response, span); - - if (isHttpError(code)) { - span.setStatus(StatusCode.ERROR); - span.addEvent(SemanticAttributes.EXCEPTION_EVENT_NAME, Attributes.builder() - .put(SemanticAttributes.EXCEPTION_TYPE, String.valueOf(code)) - .put(SemanticAttributes.EXCEPTION_ESCAPED, false) - .put(SemanticAttributes.EXCEPTION_MESSAGE, response.message()).build()); - } - } - } - - private static void setResponseSize(@NonNull Response response, Span span) { - String contentLength = response.header("Content-Length"); - if (contentLength != null) { - span.setAttribute(SemanticAttributes.HTTP_RESPONSE_BODY_SIZE, Long.valueOf(contentLength)); - } - } - - private static void setStatusCode(Span span, int code) { - span.setAttribute(SemanticAttributes.HTTP_RESPONSE_STATUS_CODE, code); - } - - private static boolean isHttpError(int code) { - return code > 399; - } - - @Override - public void callEnd(Call call) { - super.callEnd(call); - Request request = call.request(); - Context context = getContext(request); - if (context == null) { - return; - } - Elog.getLogger().info("OkHttp request ended"); - Elog.getLogger().debug("OkHttp request ended: {}", request.url()); - Span span = retrieveSpan(context); - if (span != null) { - endSpan(span, context); - } - clearStore(context, request); - } - - @Override - public void callFailed(Call call, IOException ioe) { - super.callFailed(call, ioe); - Request request = call.request(); - Context context = getContext(request); - if (context == null) { - return; - } - Elog.getLogger().info("OkHttp request failed"); - Elog.getLogger().debug("OkHttp request failed: {}", request.url()); - Span span = retrieveSpan(context); - if (span != null) { - span.setStatus(StatusCode.ERROR); - span.recordException(ioe); - endSpan(span, context); - } - clearStore(context, request); - } - - @Nullable - private Span retrieveSpan(Request request) { - return retrieveSpan(getContext(request)); - } - - @Nullable - private Span retrieveSpan(Context context) { - if (context == null) { - return null; - } - Span span = Span.fromContext(context); - if (!isValid(span)) { - return null; - } - - return span; - } - - private void clearStore(Context context, Request request) { - if (context != null) { - contextStore.remove(request); - } - } - - private void endSpan(Span span, Context context) { - span.end(); - endWrapperIfAny(context); - } - - private void endWrapperIfAny(Context context) { - WrapperSpanCloser wrapperSpanCloser = context.get(WRAPPER_CLOSER_KEY); - if (wrapperSpanCloser != null) { - Elog.getLogger().info("Closing wrapper span"); - wrapperSpanCloser.closeWrapper(); - } - } - - private boolean isValid(Span span) { - return span != null && span != Span.getInvalid(); - } - - Context getContext(Request request) { - return contextStore.get(request); - } - - private HttpRequest convertRequest(Request request) { - return new HttpRequest(request.method(), request.url().url()); - } - - private Tracer getTracer() { - if (okHttpTracer == null) { - okHttpTracer = ElasticTracers.okhttp(); - } - - return okHttpTracer; - } - - private HttpTraceConfiguration getConfiguration() { - if (configuration == null) { - configuration = ElasticApmAgent.get().configuration.httpTraceConfiguration; - } - - return configuration; - } - - public static class Factory implements EventListener.Factory { - private final OkHttpContextStore contextStore; - - public Factory(OkHttpContextStore contextStore) { - this.contextStore = contextStore; - } - - @NonNull - @Override - public EventListener create(@NonNull Call call) { - return new OtelOkHttpEventListener(contextStore); - } - } -} \ No newline at end of file diff --git a/android-sdk/src/main/java/co/elastic/apm/android/sdk/traces/http/impl/okhttp/OtelOkHttpInterceptor.java b/android-sdk/src/main/java/co/elastic/apm/android/sdk/traces/http/impl/okhttp/OtelOkHttpInterceptor.java deleted file mode 100644 index 7580c9919..000000000 --- a/android-sdk/src/main/java/co/elastic/apm/android/sdk/traces/http/impl/okhttp/OtelOkHttpInterceptor.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * 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.apm.android.sdk.traces.http.impl.okhttp; - -import java.io.IOException; - -import co.elastic.apm.android.common.internal.logging.Elog; -import io.opentelemetry.api.GlobalOpenTelemetry; -import io.opentelemetry.context.Context; -import io.opentelemetry.context.propagation.TextMapPropagator; -import io.opentelemetry.context.propagation.TextMapSetter; -import okhttp3.Interceptor; -import okhttp3.Request; -import okhttp3.Response; - -public class OtelOkHttpInterceptor implements Interceptor { - - private final OkHttpContextStore contextStore; - - public OtelOkHttpInterceptor(OkHttpContextStore contextStore) { - this.contextStore = contextStore; - } - - @Override - public Response intercept(Chain chain) throws IOException { - Request request = chain.request(); - - Context context = contextStore.get(request); - if (context != null) { - Request.Builder newRequestBuilder = request.newBuilder(); - TextMapPropagator propagator = GlobalOpenTelemetry.getPropagators().getTextMapPropagator(); - propagator.inject(context, newRequestBuilder, new OtelOkhttpTextMapSetter()); - - return chain.proceed(newRequestBuilder.build()); - } - - return chain.proceed(request); - } - - static class OtelOkhttpTextMapSetter implements TextMapSetter { - - @Override - public void set(Request.Builder carrier, String key, String value) { - if (carrier == null) { - return; - } - Elog.getLogger().debug("Adding text map propagator header: " + key + ", value: " + value); - carrier.addHeader(key, value); - } - } -} \ No newline at end of file diff --git a/android-sdk/src/main/java/co/elastic/apm/android/sdk/traces/http/impl/okhttp/utils/WrapperSpanCloser.java b/android-sdk/src/main/java/co/elastic/apm/android/sdk/traces/http/impl/okhttp/utils/WrapperSpanCloser.java deleted file mode 100644 index 3fe2f18f1..000000000 --- a/android-sdk/src/main/java/co/elastic/apm/android/sdk/traces/http/impl/okhttp/utils/WrapperSpanCloser.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * 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.apm.android.sdk.traces.http.impl.okhttp.utils; - -public interface WrapperSpanCloser { - void closeWrapper(); -} diff --git a/android-sdk/src/main/resources/META-INF/NOTICE b/android-sdk/src/main/resources/META-INF/NOTICE index ef3921292..da7fd8f24 100644 --- a/android-sdk/src/main/resources/META-INF/NOTICE +++ b/android-sdk/src/main/resources/META-INF/NOTICE @@ -5,10 +5,10 @@ Copyright 2018-2022 Elasticsearch B.V. This product includes software licensed under the 'Apache License Version 2.0' license from the following sources: - - Android Lifecycle Process (https://developer.android.com/jetpack/androidx/releases/lifecycle#2.5.1) - - Android Support Library Annotations (https://developer.android.com/jetpack/androidx/releases/annotation#1.4.0) + - Android Lifecycle Process (https://developer.android.com/jetpack/androidx/releases/lifecycle#2.6.1) - com.github.instacart.truetime-android:library:3.5 - okhttp (https://square.github.io/okhttp/) + - OpenTelemetry Android (https://github.com/open-telemetry/opentelemetry-android) - OpenTelemetry Java (https://github.com/open-telemetry/opentelemetry-java) - OpenTelemetry Java Contrib (https://github.com/open-telemetry/opentelemetry-java-contrib) - OpenTelemetry Semantic Conventions Java (https://github.com/open-telemetry/semantic-conventions-java) diff --git a/android-test/android-test-common/src/main/java/co/elastic/apm/android/test/common/agent/AgentInitializer.java b/android-test/android-test-common/src/main/java/co/elastic/apm/android/test/common/agent/AgentInitializer.java index edaf05e9f..397303c85 100644 --- a/android-test/android-test-common/src/main/java/co/elastic/apm/android/test/common/agent/AgentInitializer.java +++ b/android-test/android-test-common/src/main/java/co/elastic/apm/android/test/common/agent/AgentInitializer.java @@ -1,5 +1,6 @@ package co.elastic.apm.android.test.common.agent; +import android.app.Application; import android.content.Context; import java.lang.reflect.Field; @@ -14,16 +15,16 @@ public class AgentInitializer { - public static void initialize(Context context, ElasticApmConfiguration configuration) { - initialize(context, configuration, null, null); + public static void initialize(Application application, ElasticApmConfiguration configuration) { + initialize(application, configuration, null, null); } - public static void initialize(Context context, ElasticApmConfiguration + public static void initialize(Application application, ElasticApmConfiguration configuration, Connectivity connectivity, AgentDependenciesInjector.Interceptor injectInterceptor) { if (configuration == null) { configuration = ElasticApmConfiguration.getDefault(); } - internalInitialize(context, configuration, connectivity, injectInterceptor); + internalInitialize(application, configuration, connectivity, injectInterceptor); } public static void injectSignalConfiguration(ElasticApmConfiguration configuration, @@ -37,12 +38,12 @@ public static void injectSignalConfiguration(ElasticApmConfiguration configurati } } - private static void internalInitialize(Context context, ElasticApmConfiguration configuration, + private static void internalInitialize(Application application, ElasticApmConfiguration configuration, Connectivity connectivity, AgentDependenciesInjector.Interceptor interceptor) { try { - Method method = ElasticApmAgent.class.getDeclaredMethod("initialize", Context.class, ElasticApmConfiguration.class, Connectivity.class, AgentDependenciesInjector.Interceptor.class); + Method method = ElasticApmAgent.class.getDeclaredMethod("initialize", Application.class, ElasticApmConfiguration.class, Connectivity.class, AgentDependenciesInjector.Interceptor.class); method.setAccessible(true); - method.invoke(null, context, configuration, connectivity, interceptor); + method.invoke(null, application, configuration, connectivity, interceptor); } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { throw new RuntimeException(e); } diff --git a/android-test/app/build.gradle b/android-test/app/build.gradle index f544b3c0a..6235701c1 100644 --- a/android-test/app/build.gradle +++ b/android-test/app/build.gradle @@ -66,7 +66,7 @@ dependencies { testImplementation "io.opentelemetry:opentelemetry-exporter-otlp:1.28.0" testImplementation "org.mockito:mockito-core:$mockito_version" testImplementation "org.mockito:mockito-inline:$mockito_version" - testImplementation 'org.robolectric:robolectric:4.10.3' + testImplementation 'org.robolectric:robolectric:4.11.1' testImplementation "com.squareup.okhttp3:mockwebserver:$mockwebserver_version" androidTestImplementation "androidx.test:core:1.5.0" androidTestImplementation 'androidx.test.ext:junit:1.1.5' diff --git a/android-test/app/src/androidTest/java/co/elastic/apm/android/test/CoroutineInstrumentationTest.java b/android-test/app/src/androidTest/java/co/elastic/apm/android/test/CoroutineInstrumentationTest.java index 3d1ebd66b..ffc0a2666 100644 --- a/android-test/app/src/androidTest/java/co/elastic/apm/android/test/CoroutineInstrumentationTest.java +++ b/android-test/app/src/androidTest/java/co/elastic/apm/android/test/CoroutineInstrumentationTest.java @@ -16,14 +16,13 @@ public class CoroutineInstrumentationTest extends ActivityEspressoTest spans = getRecordedSpans(3); + List spans = getRecordedSpans(7); - SpanData rootSpan = spans.get(0); - SpanData onCreateSpan = spans.get(1); - SpanData myCoroutineSpan = spans.get(2); + SpanData onCreateSpan = spans.get(0); + SpanData myCoroutineSpan = spans.get(1); Spans.verify(onCreateSpan) - .isDirectChildOf(rootSpan); + .hasNoParent(); Spans.verify(myCoroutineSpan) .isNamed("My span inside a coroutine") diff --git a/android-test/app/src/androidTest/java/co/elastic/apm/android/test/lifecycle/ActivityLifecycleInstrumentationTest.java b/android-test/app/src/androidTest/java/co/elastic/apm/android/test/lifecycle/ActivityLifecycleInstrumentationTest.java index f23d859a6..237c86a0f 100644 --- a/android-test/app/src/androidTest/java/co/elastic/apm/android/test/lifecycle/ActivityLifecycleInstrumentationTest.java +++ b/android-test/app/src/androidTest/java/co/elastic/apm/android/test/lifecycle/ActivityLifecycleInstrumentationTest.java @@ -1,61 +1,39 @@ package co.elastic.apm.android.test.lifecycle; -import static org.junit.Assert.assertNull; - import androidx.lifecycle.Lifecycle; import androidx.test.core.app.ActivityScenario; -import org.junit.Ignore; +import org.junit.Before; import org.junit.Test; import java.util.List; -import co.elastic.apm.android.sdk.ElasticApmAgent; import co.elastic.apm.android.sdk.ElasticApmConfiguration; import co.elastic.apm.android.sdk.instrumentation.InstrumentationConfiguration; -import co.elastic.apm.android.test.activities.ErrorActivity; -import co.elastic.apm.android.test.activities.ErrorHalfWayActivity; import co.elastic.apm.android.test.activities.FullCreationActivity; -import co.elastic.apm.android.test.activities.Hilt_InstrumentedActivity; -import co.elastic.apm.android.test.activities.MissingOnResumeActivity; -import co.elastic.apm.android.test.activities.MissingOnStartAndOnResumeActivity; -import co.elastic.apm.android.test.activities.NoLifecycleMethodsActivity; import co.elastic.apm.android.test.activities.SimpleCoroutineActivity; -import co.elastic.apm.android.test.activities.TitleActivity; import co.elastic.apm.android.test.common.spans.Spans; import io.opentelemetry.sdk.trace.data.SpanData; public class ActivityLifecycleInstrumentationTest extends BaseLifecycleInstrumentationTest { + @Before + public void setUp() { + getSpanExporter().clearCapturedSpans(); + } + @Test public void onCreation_wrapWithSpan() { try (ActivityScenario controller = ActivityScenario.launch(FullCreationActivity.class)) { controller.onActivity(activity -> { - List spans = getRecordedSpans(4); + List spans = getRecordedSpans(1); - SpanData rootSpan = spans.get(0); - SpanData onCreateSpan = spans.get(1); - SpanData onStartSpan = spans.get(2); - SpanData onResumeSpan = spans.get(3); - - Spans.verify(rootSpan) - .hasNoParent() - .isNamed(getRootLifecycleSpanName(FullCreationActivity.class)); + SpanData onCreateSpan = spans.get(0); Spans.verify(onCreateSpan) - .isNamed(getSpanMethodName(ActivityMethod.ON_CREATE)) - .isDirectChildOf(rootSpan); + .hasAttribute("screen.name", "FullCreationActivity") + .isNamed(getSpanMethodName(ActivityMethod.ON_CREATE)); Spans.verify(activity.getOnCreateSpanContext()).belongsTo(onCreateSpan); - - Spans.verify(onStartSpan) - .isNamed(getSpanMethodName(ActivityMethod.ON_START)) - .isDirectChildOf(rootSpan); - Spans.verify(activity.getOnStartSpanContext()).belongsTo(onStartSpan); - - Spans.verify(onResumeSpan) - .isNamed(getSpanMethodName(ActivityMethod.ON_RESUME)) - .isDirectChildOf(rootSpan); - Spans.verify(activity.getOnResumeSpanContext()).belongsTo(onResumeSpan); }); } } @@ -64,7 +42,7 @@ public void onCreation_wrapWithSpan() { public void onBringingBackToForeground_wrapWithSpan_onlyMethodsCalled() { try (ActivityScenario controller = ActivityScenario.launch(FullCreationActivity.class)) { // Creation spans - getRecordedSpans(4); + getRecordedSpans(1); // Backgrounding screen controller.moveToState(Lifecycle.State.CREATED); // The enum name CREATED is misleading, this will STOP the activity. @@ -73,160 +51,42 @@ public void onBringingBackToForeground_wrapWithSpan_onlyMethodsCalled() { controller.moveToState(Lifecycle.State.RESUMED); controller.onActivity(activity -> { - List spans = getRecordedSpans(3); - - SpanData rootSpan = spans.get(0); - SpanData onStartSpan = spans.get(1); - SpanData onResumeSpan = spans.get(2); - - Spans.verify(rootSpan) - .hasNoParent() - .isNamed(getRootLifecycleSpanName(FullCreationActivity.class)); + List spans = getRecordedSpans(5); - Spans.verify(onStartSpan) - .isNamed(getSpanMethodName(ActivityMethod.ON_START)) - .isDirectChildOf(rootSpan); - Spans.verify(activity.getOnStartSpanContext()).belongsTo(onStartSpan); - - Spans.verify(onResumeSpan) - .isNamed(getSpanMethodName(ActivityMethod.ON_RESUME)) - .isDirectChildOf(rootSpan); - Spans.verify(activity.getOnResumeSpanContext()).belongsTo(onResumeSpan); - }); - } - } - - @Test - public void onCreation_whenMissingOnResume_wrapWithSpan() { - try (ActivityScenario controller = ActivityScenario.launch(MissingOnResumeActivity.class)) { - controller.onActivity(activity -> { - List spans = getRecordedSpans(3); - - SpanData rootSpan = spans.get(0); + SpanData onPauseSpan = spans.get(0); SpanData onCreateSpan = spans.get(1); - SpanData onStartSpan = spans.get(2); + SpanData onStopSpan = spans.get(2); + SpanData onPauseSpan2 = spans.get(3); + SpanData onRestartedSpan = spans.get(4); - Spans.verify(rootSpan) + Spans.verify(onPauseSpan) .hasNoParent() - .isNamed(getRootLifecycleSpanName(MissingOnResumeActivity.class)); - + .isNamed(getSpanMethodName(ActivityMethod.ON_PAUSE)); Spans.verify(onCreateSpan) - .isNamed(getSpanMethodName(ActivityMethod.ON_CREATE)) - .isDirectChildOf(rootSpan); - Spans.verify(activity.getOnCreateSpanContext()).belongsTo(onCreateSpan); - - Spans.verify(onStartSpan) - .isNamed(getSpanMethodName(ActivityMethod.ON_START)) - .isDirectChildOf(rootSpan); - Spans.verify(activity.getOnStartSpanContext()).belongsTo(onStartSpan); - }); - } - } - - @Test - public void onCreation_whenMissingOnStartAndOnResume_wrapWithSpan() { - try (ActivityScenario controller = ActivityScenario.launch(MissingOnStartAndOnResumeActivity.class)) { - controller.onActivity(activity -> { - List spans = getRecordedSpans(2); - - SpanData rootSpan = spans.get(0); - SpanData onCreateSpan = spans.get(1); - - Spans.verify(rootSpan) .hasNoParent() - .isNamed(getRootLifecycleSpanName(MissingOnStartAndOnResumeActivity.class)); - - Spans.verify(onCreateSpan) - .isNamed(getSpanMethodName(ActivityMethod.ON_CREATE)) - .isDirectChildOf(rootSpan); - Spans.verify(activity.getOnCreateSpanContext()).belongsTo(onCreateSpan); - }); - } - } - - @Test - public void onCreation_whenOnStartOnly_wrapWithSpan() { - try (ActivityScenario controller = ActivityScenario.launch(MissingOnStartAndOnResumeActivity.class)) { - controller.onActivity(activity -> { - - List spans = getRecordedSpans(2); - - SpanData rootSpan = spans.get(0); - SpanData onCreateSpan = spans.get(1); - - Spans.verify(rootSpan) + .isNamed(getSpanMethodName(ActivityMethod.ON_CREATE)); + Spans.verify(onStopSpan) .hasNoParent() - .isNamed(getRootLifecycleSpanName(MissingOnStartAndOnResumeActivity.class)); - - Spans.verify(onCreateSpan) - .isNamed(getSpanMethodName(ActivityMethod.ON_CREATE)) - .isDirectChildOf(rootSpan); - Spans.verify(activity.getOnCreateSpanContext()).belongsTo(onCreateSpan); + .isNamed(getSpanMethodName(ActivityMethod.ON_STOP)); + Spans.verify(onPauseSpan2) + .hasNoParent() + .isNamed(getSpanMethodName(ActivityMethod.ON_PAUSE)); + Spans.verify(onRestartedSpan) + .isNamed(getSpanMethodName(ActivityMethod.ON_RESUME)) + .hasNoParent(); + Spans.verify(activity.getOnResumeSpanContext()).belongsTo(onRestartedSpan); + Spans.verify(activity.getOnStartSpanContext()).belongsTo(onRestartedSpan); }); } } - @Ignore("No way to run this test using Espresso, the exception doesn't get caught.") - @Test - public void onCreation_whenInterruptedHalfwayByException_endRootSpan() { - try { - try (ActivityScenario ignored = ActivityScenario.launch(ErrorHalfWayActivity.class)) { - } - } catch (IllegalStateException e) { - List spans = getRecordedSpans(3); - - SpanData rootSpan = spans.get(0); - SpanData onCreateSpan = spans.get(1); - SpanData onStartSpan = spans.get(2); - - Spans.verify(rootSpan) - .hasNoParent() - .isNamed(getRootLifecycleSpanName(ErrorHalfWayActivity.class)); - - Spans.verify(onCreateSpan) - .isNamed(getSpanMethodName(ActivityMethod.ON_CREATE)) - .isDirectChildOf(rootSpan); - - Spans.verifyFailed(onStartSpan) - .isNamed(getSpanMethodName(ActivityMethod.ON_START)) - .hasAmountOfRecordedExceptions(1) - .hasRecordedException(e) - .isDirectChildOf(rootSpan); - } - } - - @Test - public void onCreation_whenActivityIsGeneratedByHilt_ignore() { - try (ActivityScenario ignored = ActivityScenario.launch(Hilt_InstrumentedActivity.class)) { - getRecordedSpans(0); - } - } - - @Ignore("No way to run this test using Espresso, the exception doesn't get caught.") - @Test - public void onCreate_recordException() { - try { - try (ActivityScenario ignored = ActivityScenario.launch(ErrorActivity.class)) { - } - } catch (IllegalStateException e) { - List spans = getRecordedSpans(2); - SpanData rootSpan = spans.get(0); - SpanData onCreateSpan = spans.get(1); - - Spans.verifyFailed(onCreateSpan) - .isDirectChildOf(rootSpan) - .hasAmountOfRecordedExceptions(1) - .hasRecordedException(e); - } - } - @Test public void onCreation_whenCoroutineIsLaunched_preserveContext() { try (ActivityScenario ignored = ActivityScenario.launch(SimpleCoroutineActivity.class)) { - List spans = getRecordedSpans(3); + List spans = getRecordedSpans(2); - SpanData onCreateSpan = spans.get(1); - SpanData mySpan = spans.get(2); + SpanData onCreateSpan = spans.get(0); + SpanData mySpan = spans.get(1); Spans.verify(mySpan) .isDirectChildOf(onCreateSpan) @@ -234,31 +94,6 @@ public void onCreation_whenCoroutineIsLaunched_preserveContext() { } } - @Test - public void onCreation_whenTitleIsAvailable_keepPreviouslySetSpanName() { - try (ActivityScenario ignored = ActivityScenario.launch(TitleActivity.class)) { - List spans = getRecordedSpans(2); - - SpanData rootSpan = spans.get(0); - SpanData onCreateSpan = spans.get(1); - - Spans.verify(rootSpan) - .hasNoParent() - .isNamed(getRootLifecycleSpanName(TitleActivity.class)); - - Spans.verify(onCreateSpan) - .isDirectChildOf(rootSpan) - .isNamed(getSpanMethodName(ActivityMethod.ON_CREATE)); - } - } - - @Test - public void onCreation_whenNoLifecycleMethodsAvailable_doNothing() { - try (ActivityScenario ignored = ActivityScenario.launch(NoLifecycleMethodsActivity.class)) { - getRecordedSpans(0); - } - } - @Test public void whenInstrumentationIsDisabled_doNotSendScreenRenderingSpans() { ElasticApmConfiguration configuration = ElasticApmConfiguration.builder() @@ -269,12 +104,4 @@ public void whenInstrumentationIsDisabled_doNotSendScreenRenderingSpans() { getRecordedSpans(0); } } - - @Test - public void whenAgentIsNotInitialized_doNotSendScreenRenderingSpans() { - ElasticApmAgent.resetForTest(); - try (ActivityScenario ignored = ActivityScenario.launch(FullCreationActivity.class)) { - getRecordedSpans(0); - } - } } diff --git a/android-test/app/src/androidTest/java/co/elastic/apm/android/test/lifecycle/BaseLifecycleInstrumentationTest.java b/android-test/app/src/androidTest/java/co/elastic/apm/android/test/lifecycle/BaseLifecycleInstrumentationTest.java index 4e851b239..04e583712 100644 --- a/android-test/app/src/androidTest/java/co/elastic/apm/android/test/lifecycle/BaseLifecycleInstrumentationTest.java +++ b/android-test/app/src/androidTest/java/co/elastic/apm/android/test/lifecycle/BaseLifecycleInstrumentationTest.java @@ -5,9 +5,10 @@ public abstract class BaseLifecycleInstrumentationTest extends BaseEspressoTest { protected enum ActivityMethod { - ON_CREATE("onCreate"), - ON_RESUME("onResume"), - ON_START("onStart"); + ON_CREATE("Created"), + ON_RESUME("Restarted"), + ON_PAUSE("Paused"), + ON_STOP("Stopped"); private final String name; @@ -17,9 +18,10 @@ protected enum ActivityMethod { } protected enum FragmentMethod { - ON_CREATE("onCreate"), - ON_CREATE_VIEW("onCreateView"), - ON_VIEW_CREATED("onViewCreated"); + ON_CREATE("Created"), + ON_VIEW_DESTROYED("ViewDestroyed"), + ON_PAUSE("Paused"), + ON_DESTROY("Destroyed"); private final String name; @@ -35,8 +37,4 @@ protected String getSpanMethodName(ActivityMethod method) { protected String getSpanMethodName(FragmentMethod method) { return method.name; } - - protected String getRootLifecycleSpanName(Class theClass) { - return theClass.getSimpleName() + " - View appearing"; - } } diff --git a/android-test/app/src/androidTest/java/co/elastic/apm/android/test/lifecycle/FragmentLifecycleInstrumentationTest.java b/android-test/app/src/androidTest/java/co/elastic/apm/android/test/lifecycle/FragmentLifecycleInstrumentationTest.java index 0aeb62bec..b975f9849 100644 --- a/android-test/app/src/androidTest/java/co/elastic/apm/android/test/lifecycle/FragmentLifecycleInstrumentationTest.java +++ b/android-test/app/src/androidTest/java/co/elastic/apm/android/test/lifecycle/FragmentLifecycleInstrumentationTest.java @@ -1,169 +1,81 @@ package co.elastic.apm.android.test.lifecycle; -import static org.junit.Assert.assertNull; - import androidx.fragment.app.testing.FragmentScenario; +import androidx.lifecycle.Lifecycle; -import org.junit.Ignore; +import org.junit.Before; import org.junit.Test; import java.util.List; -import co.elastic.apm.android.sdk.ElasticApmAgent; import co.elastic.apm.android.sdk.ElasticApmConfiguration; import co.elastic.apm.android.sdk.instrumentation.InstrumentationConfiguration; import co.elastic.apm.android.test.common.spans.Spans; -import co.elastic.apm.android.test.fragments.ErrorFragment; import co.elastic.apm.android.test.fragments.FullCreationFragment; -import co.elastic.apm.android.test.fragments.Hilt_InstrumentedFragment; -import co.elastic.apm.android.test.fragments.OnCreateMissingFragment; -import co.elastic.apm.android.test.fragments.OnCreateViewOnlyFragment; -import co.elastic.apm.android.test.fragments.ViewlessCreationFragment; import io.opentelemetry.sdk.trace.data.SpanData; public class FragmentLifecycleInstrumentationTest extends BaseLifecycleInstrumentationTest { + @Before + public void setUp() { + getSpanExporter().clearCapturedSpans(); + } + @Test public void onCreation_wrapWithSpans() { try (FragmentScenario scenario = FragmentScenario.launchInContainer(FullCreationFragment.class)) { scenario.onFragment(fragment -> { - List spans = getRecordedSpans(4); - SpanData rootSpan = spans.get(0); - SpanData onCreateSpan = spans.get(1); - SpanData onCreateViewSpan = spans.get(2); - SpanData onViewCreatedSpan = spans.get(3); + List spans = getRecordedSpans(2); + SpanData onCreateActivitySpan = spans.get(0); + SpanData onCreateFragmentSpan = spans.get(1); - Spans.verify(rootSpan) + Spans.verify(onCreateActivitySpan) .hasNoParent() - .isNamed(getRootLifecycleSpanName(FullCreationFragment.class)); - - Spans.verify(onCreateSpan) - .isDirectChildOf(rootSpan) + .hasAttribute("screen.name", "EmptyFragmentActivity") .isNamed(getSpanMethodName(FragmentMethod.ON_CREATE)); - Spans.verify(fragment.getOnCreateSpanContext()).belongsTo(onCreateSpan); - - Spans.verify(onCreateViewSpan) - .isDirectChildOf(rootSpan) - .isNamed(getSpanMethodName(FragmentMethod.ON_CREATE_VIEW)); - Spans.verify(fragment.getOnCreateViewSpanContext()).belongsTo(onCreateViewSpan); - Spans.verify(onViewCreatedSpan) - .isDirectChildOf(rootSpan) - .isNamed(getSpanMethodName(FragmentMethod.ON_VIEW_CREATED)); - Spans.verify(fragment.getOnViewCreatedSpanContext()).belongsTo(onViewCreatedSpan); - }); - } - } - - @Test - public void onCreation_viewlessFragment_ignoreOnViewCreated() { - try (FragmentScenario scenario = FragmentScenario.launchInContainer(ViewlessCreationFragment.class)) { - scenario.onFragment(fragment -> { - List spans = getRecordedSpans(3); - SpanData rootSpan = spans.get(0); - SpanData onCreateSpan = spans.get(1); - SpanData onCreateViewSpan = spans.get(2); - - Spans.verify(rootSpan) + Spans.verify(onCreateFragmentSpan) .hasNoParent() - .isNamed(getRootLifecycleSpanName(ViewlessCreationFragment.class)); - - Spans.verify(onCreateSpan) - .isDirectChildOf(rootSpan) + .hasAttribute("screen.name", "FullCreationFragment") .isNamed(getSpanMethodName(FragmentMethod.ON_CREATE)); - Spans.verify(fragment.getOnCreateSpanContext()).belongsTo(onCreateSpan); - - Spans.verify(onCreateViewSpan) - .isDirectChildOf(rootSpan) - .isNamed(getSpanMethodName(FragmentMethod.ON_CREATE_VIEW)); - Spans.verify(fragment.getOnCreateViewSpanContext()).belongsTo(onCreateViewSpan); - - assertNull(fragment.getOnViewCreatedSpanContext()); + Spans.verify(fragment.getOnCreateSpanContext()).belongsTo(onCreateFragmentSpan); + Spans.verify(fragment.getOnCreateViewSpanContext()).belongsTo(onCreateFragmentSpan); + Spans.verify(fragment.getOnViewCreatedSpanContext()).belongsTo(onCreateFragmentSpan); }); } } @Test - public void onCreation_onCreateMissing_wrapOthersWithSpan() { - try (FragmentScenario scenario = FragmentScenario.launchInContainer(OnCreateMissingFragment.class)) { + public void onDestruction_wrapWithSpans() { + try (FragmentScenario scenario = FragmentScenario.launchInContainer(FullCreationFragment.class)) { scenario.onFragment(fragment -> { - List spans = getRecordedSpans(3); - SpanData rootSpan = spans.get(0); - SpanData onCreateViewSpan = spans.get(1); - SpanData onViewCreatedSpan = spans.get(2); - - Spans.verify(rootSpan) - .hasNoParent() - .isNamed(getRootLifecycleSpanName(OnCreateMissingFragment.class)); + getSpanExporter().clearCapturedSpans(); - Spans.verify(onCreateViewSpan) - .isDirectChildOf(rootSpan) - .isNamed(getSpanMethodName(FragmentMethod.ON_CREATE_VIEW)); - Spans.verify(fragment.getOnCreateViewSpanContext()).belongsTo(onCreateViewSpan); + scenario.moveToState(Lifecycle.State.DESTROYED); - Spans.verify(onViewCreatedSpan) - .isDirectChildOf(rootSpan) - .isNamed(getSpanMethodName(FragmentMethod.ON_VIEW_CREATED)); - Spans.verify(fragment.getOnViewCreatedSpanContext()).belongsTo(onViewCreatedSpan); - }); - } - } + List spans = getRecordedSpans(3); - @Test - public void onCreation_whenOnlyOnCreateViewIsAvailable_ignoreOthers() { - try (FragmentScenario scenario = FragmentScenario.launchInContainer(OnCreateViewOnlyFragment.class)) { - scenario.onFragment(fragment -> { - List spans = getRecordedSpans(2); - SpanData rootSpan = spans.get(0); - SpanData onCreateViewSpan = spans.get(1); + SpanData paused = spans.get(0); + SpanData viewDestroyed = spans.get(1); + SpanData destroyed = spans.get(2); - Spans.verify(rootSpan) + Spans.verify(paused) .hasNoParent() - .isNamed(getRootLifecycleSpanName(OnCreateViewOnlyFragment.class)); - - Spans.verify(onCreateViewSpan) - .isDirectChildOf(rootSpan) - .isNamed(getSpanMethodName(FragmentMethod.ON_CREATE_VIEW)); - Spans.verify(fragment.getOnCreateViewSpanContext()).belongsTo(onCreateViewSpan); + .isNamed(getSpanMethodName(FragmentMethod.ON_PAUSE)) + .hasAttribute("screen.name", "FullCreationFragment"); + Spans.verify(viewDestroyed) + .hasNoParent() + .isNamed(getSpanMethodName(FragmentMethod.ON_VIEW_DESTROYED)) + .hasAttribute("screen.name", "FullCreationFragment"); + Spans.verify(destroyed) + .hasNoParent() + .isNamed(getSpanMethodName(FragmentMethod.ON_DESTROY)) + .hasAttribute("screen.name", "FullCreationFragment"); }); } } - @Test - public void onCreation_whenFragmentIsGeneratedBuHilt_ignore() { - try (FragmentScenario scenario = FragmentScenario.launchInContainer(Hilt_InstrumentedFragment.class)) { - getRecordedSpans(0); - } - } - - @Ignore("Espresso tests have only one application that crashes with this test, causing the following tests to not run.") - @Test - public void onCreation_whenInterruptedHalfwayByException_endRootSpan() { - try (FragmentScenario ignored = FragmentScenario.launchInContainer(ErrorFragment.class)) { - - } catch (NullPointerException e) { - List spans = getRecordedSpans(3); - SpanData rootSpan = spans.get(0); - SpanData onCreateSpan = spans.get(1); - SpanData onCreateViewSpan = spans.get(2); - - Spans.verify(rootSpan) - .hasNoParent() - .isNamed(getRootLifecycleSpanName(ErrorFragment.class)); - - Spans.verify(onCreateSpan) - .isDirectChildOf(rootSpan) - .isNamed(getSpanMethodName(BaseLifecycleInstrumentationTest.FragmentMethod.ON_CREATE)); - - Spans.verifyFailed(onCreateViewSpan) - .isDirectChildOf(rootSpan) - .hasAmountOfRecordedExceptions(1) - .hasRecordedException(e) - .isNamed(getSpanMethodName(BaseLifecycleInstrumentationTest.FragmentMethod.ON_CREATE_VIEW)); - } - } - @Test public void whenInstrumentationIsDisabled_doNotSendScreenRenderingSpans() { ElasticApmConfiguration configuration = ElasticApmConfiguration.builder() @@ -174,12 +86,4 @@ public void whenInstrumentationIsDisabled_doNotSendScreenRenderingSpans() { scenario.onFragment(fullCreationFragment -> getRecordedSpans(0)); } } - - @Test - public void whenAgentIsNotInitialized_doNotSendScreenRenderingSpans() { - ElasticApmAgent.resetForTest(); - try (FragmentScenario scenario = FragmentScenario.launchInContainer(FullCreationFragment.class)) { - scenario.onFragment(fullCreationFragment -> getRecordedSpans(0)); - } - } } diff --git a/android-test/app/src/androidTest/java/co/elastic/apm/android/test/okhttp/NetworkCallingActivityTest.java b/android-test/app/src/androidTest/java/co/elastic/apm/android/test/okhttp/NetworkCallingActivityTest.java index 3b108bcc4..8035335c2 100644 --- a/android-test/app/src/androidTest/java/co/elastic/apm/android/test/okhttp/NetworkCallingActivityTest.java +++ b/android-test/app/src/androidTest/java/co/elastic/apm/android/test/okhttp/NetworkCallingActivityTest.java @@ -1,56 +1,191 @@ package co.elastic.apm.android.test.okhttp; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; +import androidx.annotation.NonNull; + +import org.junit.After; +import org.junit.Before; import org.junit.Test; import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; -import co.elastic.apm.android.test.base.ActivityEspressoTest; +import co.elastic.apm.android.sdk.ElasticApmConfiguration; +import co.elastic.apm.android.sdk.instrumentation.InstrumentationConfiguration; +import co.elastic.apm.android.sdk.traces.ElasticTracers; +import co.elastic.apm.android.test.base.BaseEspressoTest; +import co.elastic.apm.android.test.common.spans.Spans; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.context.Scope; +import io.opentelemetry.sdk.trace.data.SpanData; +import okhttp3.Call; +import okhttp3.Callback; import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import okhttp3.mockwebserver.RecordedRequest; -public class NetworkCallingActivityTest extends ActivityEspressoTest { +public class NetworkCallingActivityTest extends BaseEspressoTest { + private OkHttpClient.Builder clientBuilder; + private MockWebServer webServer; - private MockWebServer mockWebServer; - private HttpUrl mockUrl; + @Before + public void setUp() { + webServer = new MockWebServer(); + try { + webServer.start(); + clientBuilder = new OkHttpClient.Builder(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @After + public void tearDown() { + try { + webServer.shutdown(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } @Test public void whenOtherOkhttpInterceptorsAreSet_propagateParentContext() throws InterruptedException { - activity.makeNetworkCall(mockUrl); + clientBuilder.addInterceptor(chain -> { + Request request = chain.request(); + return chain.proceed(request.newBuilder().addHeader("MY-HEADER", "My header value").build()); + }); + + Span span = ElasticTracers.androidActivity().spanBuilder("Http parent span").startSpan(); + try (Scope ignored = span.makeCurrent()) { + executeSuccessfulHttpCall(200, "{}", Collections.emptyMap()); + } + span.end(); - RecordedRequest recordedRequest = mockWebServer.takeRequest(1, TimeUnit.SECONDS); + RecordedRequest recordedRequest = webServer.takeRequest(1, TimeUnit.SECONDS); assertNotNull(recordedRequest.getHeader("MY-HEADER")); assertNotNull(recordedRequest.getHeader("traceparent")); } - @Override - protected void onBefore() { - mockWebServer = new MockWebServer(); + @Test + public void verifyHttpSpanStructure_whenSucceeded() { + Map responseHeaders = new HashMap<>(); + responseHeaders.put("Content-Length", "2"); + executeSuccessfulHttpCall(200, "{}", responseHeaders); + + List spans = getRecordedSpans(1); + SpanData httpSpan = spans.get(0); + + Spans.verify(httpSpan) + .isNamed("HTTP") + .isOfKind(SpanKind.CLIENT); + } + + @Test + public void verifyHttpSpanStructure_whenReceivingHttpError() { + executeSuccessfulHttpCall(500); + + List spans = getRecordedSpans(1); + SpanData httpSpan = spans.get(0); + + Spans.verifyFailed(httpSpan) + .isNamed("HTTP") + .isOfKind(SpanKind.CLIENT); + } + + @Test + public void verifyHttpSpanStructure_whenFailed() { + executeFailedHttpCall(); + + List spans = getRecordedSpans(1); + SpanData httpSpan = spans.get(0); + + Spans.verifyFailed(httpSpan) + .isNamed("HTTP") + .isOfKind(SpanKind.CLIENT) + .hasAmountOfRecordedExceptions(1); + } + + @Test + public void whenHttpInstrumentationIsDisabled_doNotSendAnyOkHttpSpans() { + ElasticApmConfiguration configuration = ElasticApmConfiguration.builder().setInstrumentationConfiguration(InstrumentationConfiguration.builder() + .enableHttpTracing(false) + .build()).build(); + overrideAgentConfiguration(configuration); + + executeSuccessfulHttpCall(200); + + getRecordedSpans(0); + } + + private void executeSuccessfulHttpCall(int responseCode) { + executeSuccessfulHttpCall(responseCode, "{}", Collections.emptyMap()); + } + + private void executeSuccessfulHttpCall(int responseCode, String responseBody, Map responseHeaders) { + MockResponse mockResponse = new MockResponse().setResponseCode(responseCode).setBody(responseBody); + responseHeaders.forEach(mockResponse::addHeader); + webServer.enqueue(mockResponse); + AtomicReference responseStr = new AtomicReference<>(""); try { - mockWebServer.start(); - mockUrl = mockWebServer.url("/"); - mockWebServer.enqueue(new MockResponse().setBody("{}").setResponseCode(200)); - } catch (IOException e) { + executeHttpCall(new Request.Builder().url(webServer.url("/")).build(), response -> { + try { + responseStr.set(response.body().string()); + } catch (IOException e) { + fail(e.getMessage()); + } + }); + } catch (InterruptedException e) { throw new RuntimeException(e); } + assertEquals("{}", responseStr.get()); } - @Override - protected void onAfter() { + private void executeFailedHttpCall() { + HttpUrl url = webServer.url("/"); try { - mockWebServer.shutdown(); + webServer.shutdown(); } catch (IOException e) { throw new RuntimeException(e); } + + try { + executeHttpCall(new Request.Builder().url(url).build(), response -> { + }); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } } - @Override - protected Class getActivityClass() { - return NetworkCallingActivity.class; + private void executeHttpCall(Request request, Consumer responseConsumer) throws InterruptedException { + CountDownLatch latch = new CountDownLatch(1); + clientBuilder.build().newCall(request).enqueue(new Callback() { + @Override + public void onFailure(@NonNull Call call, @NonNull IOException e) { + latch.countDown(); + } + + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) { + responseConsumer.accept(response); + latch.countDown(); + } + }); + latch.await(1, TimeUnit.SECONDS); } } \ No newline at end of file diff --git a/android-test/app/src/main/AndroidManifest.xml b/android-test/app/src/main/AndroidManifest.xml index da9ac1ffc..125c72fca 100644 --- a/android-test/app/src/main/AndroidManifest.xml +++ b/android-test/app/src/main/AndroidManifest.xml @@ -24,9 +24,6 @@ - diff --git a/android-test/app/src/main/java/co/elastic/apm/android/test/okhttp/NetworkCallingActivity.kt b/android-test/app/src/main/java/co/elastic/apm/android/test/okhttp/NetworkCallingActivity.kt deleted file mode 100644 index 7da7ac43f..000000000 --- a/android-test/app/src/main/java/co/elastic/apm/android/test/okhttp/NetworkCallingActivity.kt +++ /dev/null @@ -1,47 +0,0 @@ -package co.elastic.apm.android.test.okhttp - -import androidx.appcompat.app.AppCompatActivity -import androidx.test.espresso.IdlingResource -import androidx.test.espresso.idling.CountingIdlingResource -import co.elastic.apm.android.sdk.traces.ElasticTracers -import co.elastic.apm.android.test.activities.espresso.IdlingResourceProvider -import okhttp3.* -import java.io.IOException - -class NetworkCallingActivity : AppCompatActivity(), - IdlingResourceProvider { - private val idling = CountingIdlingResource("okhttp") - - fun makeNetworkCall(mockServerUrl: HttpUrl) { - val client = OkHttpClient.Builder() - .addInterceptor { - val request = it.request() - - it.proceed(request.newBuilder().addHeader("MY-HEADER", "My header value").build()) - }.build() - - val request = Request.Builder() - .url(mockServerUrl) - .build() - - val span = ElasticTracers.androidActivity().spanBuilder("Http parent span").startSpan() - span.makeCurrent().use { - idling.increment() - client.newCall(request).enqueue(object : Callback { - - override fun onFailure(call: Call, e: IOException) { - idling.decrement() - } - - override fun onResponse(call: Call, response: Response) { - idling.decrement() - } - }) - } - span.end() - } - - override fun getIdlingResource(): IdlingResource { - return idling - } -} \ No newline at end of file diff --git a/android-test/app/src/test/java/co/elastic/apm/android/test/features/AppLaunchTimeTest.java b/android-test/app/src/test/java/co/elastic/apm/android/test/features/AppLaunchTimeTest.java index 851e9725a..cfc878499 100644 --- a/android-test/app/src/test/java/co/elastic/apm/android/test/features/AppLaunchTimeTest.java +++ b/android-test/app/src/test/java/co/elastic/apm/android/test/features/AppLaunchTimeTest.java @@ -6,7 +6,6 @@ import org.robolectric.android.controller.ActivityController; import org.robolectric.annotation.Config; -import co.elastic.apm.android.sdk.ElasticApmAgent; import co.elastic.apm.android.sdk.ElasticApmConfiguration; import co.elastic.apm.android.sdk.instrumentation.InstrumentationConfiguration; import co.elastic.apm.android.sdk.internal.features.launchtime.LaunchTimeTracker; @@ -26,14 +25,14 @@ public void tearDown() { } @Test - public void whenFirstActivityIsOpen_trackStartupTime_in_onResume() { + public void whenFirstActivityIsOpen_trackStartupTime_in_onStart() { try (ActivityController controller = Robolectric.buildActivity(FullCreationActivity.class)) { - controller.create().start().postCreate(null); + controller.create(); // Checking that there's no metrics up to this point getRecordedMetrics(0); - controller.resume(); + controller.start().postCreate(null); MetricData startupMetric = getRecordedMetric(); diff --git a/android-test/app/src/test/java/co/elastic/apm/android/test/network/okhttp/OkHttpSpansTest.java b/android-test/app/src/test/java/co/elastic/apm/android/test/network/okhttp/OkHttpSpansTest.java deleted file mode 100644 index 90be9763d..000000000 --- a/android-test/app/src/test/java/co/elastic/apm/android/test/network/okhttp/OkHttpSpansTest.java +++ /dev/null @@ -1,289 +0,0 @@ -package co.elastic.apm.android.test.network.okhttp; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.robolectric.annotation.Config; - -import java.io.IOException; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.function.BiConsumer; - -import co.elastic.apm.android.sdk.ElasticApmConfiguration; -import co.elastic.apm.android.sdk.instrumentation.InstrumentationConfiguration; -import co.elastic.apm.android.sdk.traces.ElasticTracers; -import co.elastic.apm.android.sdk.traces.http.impl.okhttp.OkHttpContextStore; -import co.elastic.apm.android.sdk.traces.http.impl.okhttp.OtelOkHttpEventListener; -import co.elastic.apm.android.sdk.traces.http.impl.okhttp.OtelOkHttpInterceptor; -import co.elastic.apm.android.test.common.spans.Spans; -import co.elastic.apm.android.test.testutils.AppWithoutInitializedAgent; -import co.elastic.apm.android.test.testutils.base.BaseRobolectricTest; -import co.elastic.apm.android.test.testutils.base.BaseRobolectricTestApplication; -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.trace.Span; -import io.opentelemetry.api.trace.SpanKind; -import io.opentelemetry.context.Scope; -import io.opentelemetry.sdk.trace.data.SpanData; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; -import okhttp3.mockwebserver.MockResponse; -import okhttp3.mockwebserver.MockWebServer; -import okhttp3.mockwebserver.SocketPolicy; - -public class OkHttpSpansTest extends BaseRobolectricTest { - private MockWebServer webServer; - private OkHttpClient client; - private Request request; - private OkHttpContextStore contextStore; - - @Before - public void setUp() { - webServer = new MockWebServer(); - contextStore = spy(new OkHttpContextStore()); - client = new OkHttpClient.Builder() - .eventListenerFactory(new OtelOkHttpEventListener.Factory(contextStore)) - .addInterceptor(new OtelOkHttpInterceptor(contextStore)) - .build(); - - request = new Request.Builder() - .url(webServer.url("/")) - .build(); - } - - @After - public void tearDown() throws IOException { - webServer.shutdown(); - } - - @Test - public void verifyHttpSpanStructure_whenSucceeded() { - Map responseHeaders = new HashMap<>(); - responseHeaders.put("Content-Length", "2"); - executeSuccessfulHttpCall(request, 200, "{}", responseHeaders); - - List spans = getRecordedSpans(2); - SpanData httpSpan = spans.get(1); - - Spans.verify(httpSpan) - .isNamed("GET localhost") - .isOfKind(SpanKind.CLIENT) - .hasAttribute("url.full", "http://localhost:" + webServer.getPort() + "/") - .hasAttribute("http.request.method", "GET") - .hasAttribute("http.response.status_code", 200) - .hasAttribute("http.response.body.size", 2); - verify(contextStore).remove(any()); - } - - @Test - public void verifyHttpSpanStructure_whenReceivingHttpError() { - executeSuccessfulHttpCall(request, 500); - - List spans = getRecordedSpans(2); - SpanData httpSpan = spans.get(1); - - Spans.verifyFailed(httpSpan) - .isNamed("GET localhost") - .isOfKind(SpanKind.CLIENT) - .hasAttribute("url.full", "http://localhost:" + webServer.getPort() + "/") - .hasAttribute("http.request.method", "GET") - .hasAttribute("http.response.status_code", 500) - .hasEvent("exception", Attributes.builder().put("exception.type", "500") - .put("exception.escaped", false) - .put("exception.message", "Server Error").build()); - } - - @Test - public void verifyHttpSpanStructure_whenFailed() { - executeFailedHttpCall(request); - - List spans = getRecordedSpans(2); - SpanData httpSpan = spans.get(1); - - Spans.verifyFailed(httpSpan) - .isNamed("GET localhost") - .isOfKind(SpanKind.CLIENT) - .hasAmountOfRecordedExceptions(1) - .hasAttribute("url.full", "http://localhost:" + webServer.getPort() + "/") - .hasAttribute("http.request.method", "GET"); - - verify(contextStore).remove(any()); - } - - @Test - public void excludeHttpCallsFromOTelTracesExporter() { - Request otelExporterRequest = new Request.Builder() - .url(webServer.url("/opentelemetry.proto.collector.trace.v1.TraceService/Export")) - .build(); - - executeSuccessfulHttpCall(otelExporterRequest); - - getRecordedSpans(0); - verify(contextStore, never()).put(any(), any()); - } - - @Test - public void excludeHttpCallsFromOTelMetricsExporter() { - Request otelExporterRequest = new Request.Builder() - .url(webServer.url("/opentelemetry.proto.collector.metrics.v1.MetricsService/Export")) - .build(); - - executeSuccessfulHttpCall(otelExporterRequest); - - getRecordedSpans(0); - verify(contextStore, never()).put(any(), any()); - } - - @Test - public void excludeHttpCallsFromOTelLogsExporter() { - Request otelExporterRequest = new Request.Builder() - .url(webServer.url("/opentelemetry.proto.collector.logs.v1.LogsService/Export")) - .build(); - - executeSuccessfulHttpCall(otelExporterRequest); - - getRecordedSpans(0); - verify(contextStore, never()).put(any(), any()); - } - - @Test - public void whenThereIsAnExistingSpanContext_createHttpSpanOnly() { - String existingSpanName = "SomeSpan"; - Span parentSpan = ElasticTracers.create("SomeScope").spanBuilder(existingSpanName).startSpan(); - try (Scope ignored = parentSpan.makeCurrent()) { - executeSuccessfulHttpCall(request); - } finally { - parentSpan.end(); - } - - List spans = getRecordedSpans(2); - - SpanData parentSpanData = spans.get(0); - SpanData httpSpan = spans.get(1); - - Spans.verify(parentSpanData) - .isNamed(existingSpanName) - .hasNoParent(); - - Spans.verify(httpSpan) - .isDirectChildOf(parentSpanData); - } - - @Test - public void whenThereIsNoParentSpanContext_wrapHttpSpanWithTransactionSpan_forSuccessfulCall() { - executeSuccessfulHttpCall(request); - - List spans = getRecordedSpans(2); - - SpanData transactionSpan = spans.get(0); - SpanData httpSpan = spans.get(1); - - Spans.verify(transactionSpan) - .isNamed("Transaction - GET localhost") - .isOfKind(SpanKind.INTERNAL) - .hasNoParent(); - - Spans.verify(httpSpan) - .isOfKind(SpanKind.CLIENT) - .isDirectChildOf(transactionSpan); - } - - @Test - public void whenThereIsNoParentSpanContext_wrapHttpSpanWithTransactionSpan_forFailedCall() { - executeFailedHttpCall(request); - - List spans = getRecordedSpans(2); - - SpanData transactionSpan = spans.get(0); - SpanData httpSpan = spans.get(1); - - Spans.verify(transactionSpan) - .isNamed("Transaction - GET localhost") - .isOfKind(SpanKind.INTERNAL) - .hasNoParent(); - - Spans.verifyFailed(httpSpan) - .isOfKind(SpanKind.CLIENT) - .isDirectChildOf(transactionSpan); - } - - @Config(application = DisabledHttpRequestsApp.class) - @Test - public void whenHttpInstrumentationIsDisabled_doNotSendAnyOkHttpSpans() { - executeSuccessfulHttpCall(request); - - getRecordedSpans(0); - } - - private void executeSuccessfulHttpCall(Request request) { - executeSuccessfulHttpCall(request, 200); - } - - @Config(application = AppWithoutInitializedAgent.class) - @Test - public void whenAgentIsNotInitialized_doNotSendAnyOkHttpSpans() { - executeSuccessfulHttpCall(request); - - getRecordedSpans(0); - } - - private void executeSuccessfulHttpCall(Request request, int responseCode) { - executeSuccessfulHttpCall(request, responseCode, "{}", Collections.emptyMap()); - } - - private void executeSuccessfulHttpCall(Request request, int responseCode, String body, Map headers) { - MockResponse mockResponse = new MockResponse().setResponseCode(responseCode).setBody(body); - headers.forEach(mockResponse::addHeader); - webServer.enqueue(mockResponse); - try { - Response response = executeHttpCall(request); - assertEquals("{}", response.body().string()); - } catch (IOException e) { - fail(e.getMessage()); - } - } - - private void executeFailedHttpCall(Request request) { - webServer.enqueue(new MockResponse() - .setBody("{}") - .setSocketPolicy(SocketPolicy.DISCONNECT_DURING_RESPONSE_BODY)); - Response response = executeHttpCall(request); - try { - response.body().string(); - fail(); - } catch (IOException e) { - assertEquals("unexpected end of stream", e.getMessage()); - } - } - - private Response executeHttpCall(Request request) { - try { - return client.newCall(request).execute(); - } catch (IOException e) { - fail(e.getMessage()); - throw new RuntimeException(e); - } - } - - private static class DisabledHttpRequestsApp extends BaseRobolectricTestApplication { - @Override - public void onCreate() { - super.onCreate(); - ElasticApmConfiguration configuration = ElasticApmConfiguration.builder().setInstrumentationConfiguration(InstrumentationConfiguration.builder() - .enableHttpTracing(false) - .build()).build(); - - initializeAgentWithCustomConfig(configuration); - } - } -} diff --git a/android-test/plugin-test/src/test/java/co/elastic/apm/android/plugin/OkHttpConfigTest.java b/android-test/plugin-test/src/test/java/co/elastic/apm/android/plugin/OkHttpConfigTest.java index c88d00ab3..1fd9e983c 100644 --- a/android-test/plugin-test/src/test/java/co/elastic/apm/android/plugin/OkHttpConfigTest.java +++ b/android-test/plugin-test/src/test/java/co/elastic/apm/android/plugin/OkHttpConfigTest.java @@ -1,16 +1,10 @@ package co.elastic.apm.android.plugin; -import static org.junit.Assert.assertNotNull; - import org.junit.Before; import org.junit.Rule; -import org.junit.Test; import org.junit.rules.TemporaryFolder; import java.io.File; -import java.util.Properties; - -import co.elastic.apm.android.common.ApmInfo; public class OkHttpConfigTest extends BaseAssetsVerificationTest { @@ -24,18 +18,6 @@ public void setUp() { getDefaultElasticBlockBuilder().setServerUrl("http://some.server"); } - @Test - public void whenGeneratingAssetsFile_getProjectsOkHttpVersion() { - setUpProject(); - - runGradle("assembleDebug"); - - verifyTaskIsSuccessful(":debugGenerateApmInfo"); - Properties properties = getGeneratedProperties("debug"); - String okhttpVersion = properties.getProperty(ApmInfo.KEY_SCOPE_OKHTTP_VERSION); - assertNotNull(okhttpVersion); - } - @Override protected File getProjectDir() { return projectTemporaryFolder.getRoot(); diff --git a/build-tools/build.gradle b/build-tools/build.gradle index a36784bb2..2d72953dd 100644 --- a/build-tools/build.gradle +++ b/build-tools/build.gradle @@ -9,18 +9,15 @@ propertiesFile.withInputStream { } dependencies { - implementation 'org.apache.commons:commons-text:1.10.0' - implementation 'commons-io:commons-io:2.13.0' + implementation libs.apache.commons.text + implementation libs.commons.io + implementation libs.spotless.plugin + implementation libs.dokka + implementation libs.nexus.publish.plugin + implementation libs.gradle.publish.plugin + implementation libs.gradle.shadow.plugin implementation "com.android.tools.build:gradle:${properties["androidGradlePlugin_version"]}" - - // This is needed to enforce a higher version than the transitive one (3.0.0) requested by: "com.diffplug.spotless:spotless-plugin-gradle:6.21.0" - implementation "com.squareup.okio:okio:3.5.0" - implementation "com.diffplug.spotless:spotless-plugin-gradle:6.22.0" - implementation "org.jetbrains.dokka:dokka-gradle-plugin:1.9.0" - implementation "io.github.gradle-nexus:publish-plugin:1.3.0" - implementation 'com.gradle.publish:plugin-publish-plugin:1.2.1' - implementation "gradle.plugin.com.github.johnrengelman:shadow:7.1.2" - testImplementation "junit:junit:4.13.2" + testImplementation libs.junit } gradlePlugin { diff --git a/build-tools/processor/build.gradle b/build-tools/processor/build.gradle index a084bcbc8..234dc0609 100644 --- a/build-tools/processor/build.gradle +++ b/build-tools/processor/build.gradle @@ -2,12 +2,10 @@ plugins { id 'java-library' } -def autoService_version = '1.0.1' - dependencies { - implementation 'com.squareup:javapoet:1.13.0' - compileOnly "com.google.auto.service:auto-service-annotations:$autoService_version" - annotationProcessor "com.google.auto.service:auto-service:$autoService_version" + implementation libs.squareup.javaPoet + compileOnly libs.google.autoService.annotations + annotationProcessor libs.google.autoService } java { diff --git a/build-tools/settings.gradle b/build-tools/settings.gradle index 8796197df..dc82e2f18 100644 --- a/build-tools/settings.gradle +++ b/build-tools/settings.gradle @@ -8,6 +8,11 @@ pluginManagement { } } dependencyResolutionManagement { + versionCatalogs { + create("libs") { + from(files("../gradle/libs.versions.toml")) + } + } repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { mavenCentral() diff --git a/build-tools/src/main/java/co/elastic/apm/compile/tools/tasks/BaseTask.java b/build-tools/src/main/java/co/elastic/apm/compile/tools/tasks/BaseTask.java index e5208d102..b5fcf8364 100644 --- a/build-tools/src/main/java/co/elastic/apm/compile/tools/tasks/BaseTask.java +++ b/build-tools/src/main/java/co/elastic/apm/compile/tools/tasks/BaseTask.java @@ -49,7 +49,7 @@ protected List getComponentIdentifiers(Configuration depend } if (!externalDependenciesIds.isEmpty()) { - throw new RuntimeException("POM files not found for the following dependencies: " + externalDependenciesIds); + getLogger().warn("POM files not found for the following dependencies: " + externalDependenciesIds); } return identifiers; diff --git a/build.gradle b/build.gradle index bba2c477d..c18329891 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ plugins { - id 'org.jetbrains.kotlin.jvm' version '1.8.22' apply false - id 'org.jetbrains.kotlin.android' version '1.8.22' apply false + alias(libs.plugins.kotlin.android) apply false + alias(libs.plugins.kotlin.jvm) apply false id 'co.elastic.apm.publishing' } @@ -8,10 +8,4 @@ ext { jvmCompatibility = JavaVersion.VERSION_11 androidMinSdk = 24 androidCompileSdk = 32 - openTelemetry_version = "1.31.0" - androidAnnotations_version = "1.4.0" - bytebuddy_version = "1.14.10" - okhttp_version = '3.11.0' - junit_version = '4.13.2' - mockito_version = '4.9.0' } \ No newline at end of file diff --git a/docs/setup.asciidoc b/docs/setup.asciidoc index d5b43e6e3..dfc90abf3 100644 --- a/docs/setup.asciidoc +++ b/docs/setup.asciidoc @@ -4,16 +4,43 @@ Follow these steps to start reporting your Android application's performance to Elastic APM: 1. <>. -2. <>. +2. <>. 3. <>. -4. <>. -5. <>. +4. <> [float] [[gradle-setup]] === Set up Gradle -First, add the https://plugins.gradle.org/plugin/co.elastic.apm.android[Elastic APM agent plugin] to your application's `build.gradle` file as shown below: +[float] +[[gradle-requirements]] +==== Requirements + +|=== +|Requirement |Minimum version + +|Android Gradle plugin +|7.4.0 + +|Android API level +|24 + +|=== + +[float] +[[minsdk-24-support]] +==== For projects using minSdkVersion < 26 + +Due to Android's limited support for Java 8 features on devices with an API level < 26, or in other words, older than Android 8.0, you must add https://developer.android.com/studio/write/java8-support#library-desugaring[Java 8+ desugaring support] to apps with a `minSdkVersion` less than 26. +If you don't, your app can crash when running on devices using Android OS versions older than 8.0. This is because the https://github.com/open-telemetry/opentelemetry-java[OpenTelemetry Java SDK], which this SDK is built upon, uses Java 8 features. + +You can skip this step if your `minSdkVersion` is 26 or higher. + +[float] +[[adding-gradle-plugin]] +==== Add the Elastic Agent Gradle plugin + +To automatically instrument <>, add the https://plugins.gradle.org/plugin/co.elastic.apm.android[Elastic APM agent plugin] to your application's `build.gradle` file as shown below: [source,groovy] ---- @@ -58,18 +85,9 @@ More info on API Keys {ref}/security-api-create-api-key.html[here]. NOTE: When both `secretToken` and `apiKey` are provided, apiKey has priority and secretToken is ignored. -[float] -[[minsdk-24-support]] -==== For projects using minSdk version < 26 - -Due to Android's limited support for Java 8 features on devices with API level < 26, or in other words, older than Android 8.0, you must add https://developer.android.com/studio/write/java8-support#library-desugaring[Java 8+ desugaring support] to apps with a minSdk version lower than 26. -If you don't, your app can crash when running on devices using Android OS versions older than 8.0. This is because the https://github.com/open-telemetry/opentelemetry-java[OpenTelemetry Java SDK], which this SDK is built upon, uses Java 8 features. - -If your minSdk is 26 or higher, you can skip this step. - [float] [[application-setup]] -=== Set up your application +==== Set up your application After syncing your project with the Gradle changes above, the Elastic APM agent needs to be initialized within your https://developer.android.com/reference/android/app/Application[Application class]. This example shows the simplest way to configure the agent: @@ -91,23 +109,43 @@ class MyApp extends android.app.Application { <1> Initialize the Elastic APM agent once. [float] -[[compile-and-run]] -=== Compile and run +[[manual-setup]] +=== (Optional) Manual set up -All that's left is to compile and run your application. -That's it! +If you can't add the Elastic Agent Gradle plugin to your application as shown above, complete the following steps to set up the Elastic SDK manually. [float] -[[manual-setup]] -=== (Optional) Manual set up +[[gradle-dependencies]] +==== Add the SDK dependency + +Add the https://central.sonatype.com/artifact/co.elastic.apm/android-sdk[Elastic APM agent SDK] to your application's `build.gradle` file as shown below: + +[source,groovy] +---- +// Android app's build.gradle file +dependencies { + implementation "co.elastic.apm:android-sdk:[latest_version]" <1> +} +---- + +<1> You can find the latest version https://central.sonatype.com/artifact/co.elastic.apm/android-sdk[here]. -If you need to set up the Elastic SDK manually, without having to add the Gradle plugin as shown above, you'll need to provide the following configuration parameters at runtime: +[float] +[[manual-configuration]] +==== Configure your app's info and connectivity parameters - Set your app name, version, and environment name, as explained <>. - Set your server connectivity parameters, as explained <>. NOTE: Without the Gradle plugin, the Elastic SDK won't be able to provide automatic instrumentations for its <>. +[float] +[[compile-and-run]] +=== Compile and run + +All that's left is to compile and run your application. +That's it! + [float] [[whats-next]] === What's next? diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 000000000..30691d472 --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,59 @@ +[versions] +opentelemetry-android = "0.3.0" +opentelemetry-android-alpha = "0.3.0-alpha" +opentelemetry = "1.34.1" +opentelemetry-alpha = "1.34.1-alpha" +opentelemetry-semconv = "1.21.0-alpha" +opentelemetry-contrib = "1.31.0-alpha" +mockito = "4.11.0" +byteBuddy = "1.14.11" +okhttp = "4.12.0" +kotlin = "1.9.22" +google-autoService = "1.1.1" + +[libraries] +opentelemetry-sdk = { module = "io.opentelemetry:opentelemetry-sdk", version.ref = "opentelemetry" } +opentelemetry-android = { module = "io.opentelemetry.android:instrumentation", version.ref = "opentelemetry-android-alpha" } +opentelemetry-events = { module = "io.opentelemetry:opentelemetry-api-events", version.ref = "opentelemetry-alpha" } +opentelemetry-android-okhttpLib = { module = "io.opentelemetry.android:okhttp-3.0-library", version.ref = "opentelemetry-android" } +opentelemetry-android-okhttpAgent = { module = "io.opentelemetry.android:okhttp-3.0-agent", version.ref = "opentelemetry-android" } +stagemonitor-configuration = "org.stagemonitor:stagemonitor-configuration:0.89.1" +androidx-lifecycle = "androidx.lifecycle:lifecycle-process:2.5.1" +androidx-annotations = "androidx.annotation:annotation:1.4.0" +weaklockfree = "com.blogspot.mydailyjava:weak-lock-free:0.18" +dsl-json = "com.dslplatform:dsl-json-java8:1.10.0" +truetime = "com.github.instacart.truetime-android:library:3.5" +slf4j-api = "org.slf4j:slf4j-api:2.0.0" +androidx-fragment = "androidx.fragment:fragment:1.5.3" +byteBuddy = { module = "net.bytebuddy:byte-buddy", version.ref = "byteBuddy" } +okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" } +opentelemetry-semconv = { module = "io.opentelemetry.semconv:opentelemetry-semconv", version.ref = "opentelemetry-semconv" } +opentelemetry-kotlin = { module = "io.opentelemetry:opentelemetry-extension-kotlin", version.ref = "opentelemetry" } +opentelemetry-diskBuffering = { module = "io.opentelemetry.contrib:opentelemetry-disk-buffering", version.ref = "opentelemetry-contrib" } +opentelemetry-exporter-otlp = { module = "io.opentelemetry:opentelemetry-exporter-otlp", version.ref = "opentelemetry" } +kotlin-coroutines = "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4" + +#Test tools +mockito-core = { module = "org.mockito:mockito-core", version.ref = "mockito" } +mockito-inline = { module = "org.mockito:mockito-inline", version.ref = "mockito" } +junit = "junit:junit:4.13.2" + +#Compilation tools +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:6.24.0" +dokka = "org.jetbrains.dokka:dokka-gradle-plugin:1.9.0" +nexus-publish-plugin = "io.github.gradle-nexus:publish-plugin:1.3.0" +gradle-publish-plugin = "com.gradle.publish:plugin-publish-plugin:1.2.1" +gradle-shadow-plugin = "gradle.plugin.com.github.johnrengelman:shadow:7.1.2" +squareup-javaPoet = "com.squareup:javapoet:1.13.0" +google-autoService = { module = "com.google.auto.service:auto-service", version.ref = "google-autoService" } +google-autoService-annotations = { module = "com.google.auto.service:auto-service-annotations", version.ref = "google-autoService" } +byteBuddy-plugin = { module = "net.bytebuddy:byte-buddy-gradle-plugin", version.ref = "byteBuddy" } + +[bundles] +mocking = ["mockito-core", "mockito-inline"] + +[plugins] +kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } +kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } \ No newline at end of file diff --git a/manual_licenses_map.txt b/manual_licenses_map.txt index c1a5ea39d..3215b8474 100644 --- a/manual_licenses_map.txt +++ b/manual_licenses_map.txt @@ -1,5 +1,5 @@ -net.bytebuddy:byte-buddy:1.14.10|apache_v2 -net.bytebuddy:byte-buddy-gradle-plugin:1.14.10|apache_v2 +net.bytebuddy:byte-buddy:1.14.11|apache_v2 +net.bytebuddy:byte-buddy-gradle-plugin:1.14.11|apache_v2 org.slf4j:slf4j-api:2.0.0|mit com.squareup.okhttp3:okhttp:3.11.0|apache_v2 com.github.instacart.truetime-android:library:3.5|apache_v2 \ No newline at end of file diff --git a/sample-app/app/build.gradle b/sample-app/app/build.gradle index 74247dad2..36c6b5ce6 100644 --- a/sample-app/app/build.gradle +++ b/sample-app/app/build.gradle @@ -1,17 +1,17 @@ plugins { id 'com.android.application' id 'org.jetbrains.kotlin.android' - id 'co.elastic.apm.android' version '0.13.0' + id 'co.elastic.apm.android' } android { namespace 'co.elastic.apm.android.sample' - compileSdk 32 + compileSdk 33 defaultConfig { applicationId "co.elastic.apm.android.sample" minSdk 26 - targetSdk 32 + targetSdk 33 versionCode 1 versionName "1.0" diff --git a/sample-app/settings.gradle b/sample-app/settings.gradle index e8eee0402..da357fe02 100644 --- a/sample-app/settings.gradle +++ b/sample-app/settings.gradle @@ -15,5 +15,6 @@ dependencyResolutionManagement { } } rootProject.name = "Android APM Sample app" +includeBuild '..' include ':app' include ':backend' diff --git a/settings.gradle b/settings.gradle index 67a7ba19d..0bdbf59fa 100644 --- a/settings.gradle +++ b/settings.gradle @@ -23,6 +23,5 @@ rootProject.name = "APM Android Agent" includeBuild "build-tools" include ':android-plugin' include ':android-sdk' -include ':android-instrumentation' include ':android-common' include ':android-sdk-ktx'