From bb894bba54080754bee9b2ca2a3fc484f4b68600 Mon Sep 17 00:00:00 2001 From: Cesar Munoz <56847527+LikeTheSalad@users.noreply.github.com> Date: Tue, 11 Feb 2025 06:21:11 +0100 Subject: [PATCH 01/24] Creating okhttp instrumentation subprojects --- .../okhttp/library/build.gradle.kts | 7 + .../okhttp/library/metadata/notice.properties | 1 + .../okhttp/internal/OkHttpInstrumentation.kt | 32 +++ .../src/main/resources/META-INF/NOTICE | 215 ++++++++++++++++++ .../okhttp/plugin/build.gradle.kts | 14 ++ .../okhttp/plugin/metadata/notice.properties | 1 + .../okhttp/OkHttpInstrumentationPlugin.kt | 15 ++ .../plugin/src/main/resources/META-INF/NOTICE | 213 +++++++++++++++++ 8 files changed, 498 insertions(+) create mode 100644 instrumentation/okhttp/library/build.gradle.kts create mode 100644 instrumentation/okhttp/library/metadata/notice.properties create mode 100644 instrumentation/okhttp/library/src/main/java/co/elastic/otel/android/okhttp/internal/OkHttpInstrumentation.kt create mode 100644 instrumentation/okhttp/library/src/main/resources/META-INF/NOTICE create mode 100644 instrumentation/okhttp/plugin/build.gradle.kts create mode 100644 instrumentation/okhttp/plugin/metadata/notice.properties create mode 100644 instrumentation/okhttp/plugin/src/main/java/co/elastic/otel/android/okhttp/OkHttpInstrumentationPlugin.kt create mode 100644 instrumentation/okhttp/plugin/src/main/resources/META-INF/NOTICE diff --git a/instrumentation/okhttp/library/build.gradle.kts b/instrumentation/okhttp/library/build.gradle.kts new file mode 100644 index 00000000..662e1978 --- /dev/null +++ b/instrumentation/okhttp/library/build.gradle.kts @@ -0,0 +1,7 @@ +plugins { + id("elastic.instrumentation-library") +} + +android { + namespace = "co.elastic.otel.android.instrumentation.okhttp" +} \ No newline at end of file diff --git a/instrumentation/okhttp/library/metadata/notice.properties b/instrumentation/okhttp/library/metadata/notice.properties new file mode 100644 index 00000000..c9161b6d --- /dev/null +++ b/instrumentation/okhttp/library/metadata/notice.properties @@ -0,0 +1 @@ +dependencies.hash=C98ECB36B923E54714316C7764978779 \ No newline at end of file diff --git a/instrumentation/okhttp/library/src/main/java/co/elastic/otel/android/okhttp/internal/OkHttpInstrumentation.kt b/instrumentation/okhttp/library/src/main/java/co/elastic/otel/android/okhttp/internal/OkHttpInstrumentation.kt new file mode 100644 index 00000000..6c7f6ce1 --- /dev/null +++ b/instrumentation/okhttp/library/src/main/java/co/elastic/otel/android/okhttp/internal/OkHttpInstrumentation.kt @@ -0,0 +1,32 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package co.elastic.otel.android.okhttp.internal + +import android.app.Application +import co.elastic.otel.android.api.ElasticOtelAgent +import co.elastic.otel.android.instrumentation.internal.Instrumentation +import com.google.auto.service.AutoService + +@AutoService(Instrumentation::class) +class OkHttpInstrumentation : Instrumentation { + + override fun install(application: Application, agent: ElasticOtelAgent) { + TODO() + } +} \ No newline at end of file diff --git a/instrumentation/okhttp/library/src/main/resources/META-INF/NOTICE b/instrumentation/okhttp/library/src/main/resources/META-INF/NOTICE new file mode 100644 index 00000000..ba699da2 --- /dev/null +++ b/instrumentation/okhttp/library/src/main/resources/META-INF/NOTICE @@ -0,0 +1,215 @@ +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: + + - Kotlin Stdlib (https://kotlinlang.org/) + - OpenTelemetry Java (https://github.com/open-telemetry/opentelemetry-java) + - OpenTelemetry Semantic Conventions Java (https://github.com/open-telemetry/semantic-conventions-java) + +############################################################################### + + + 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/instrumentation/okhttp/plugin/build.gradle.kts b/instrumentation/okhttp/plugin/build.gradle.kts new file mode 100644 index 00000000..29fbe0dc --- /dev/null +++ b/instrumentation/okhttp/plugin/build.gradle.kts @@ -0,0 +1,14 @@ +plugins { + id("elastic.instrumentation-plugin") +} + +elasticBuildConfig { + projectUri("LIBRARY_URI", "okhttp-library") +} + +elasticInstrumentationPlugins { + create("okhttp") { + implementationClass = "co.elastic.otel.android.okhttp.OkHttpInstrumentationPlugin" + displayName = "Elastic OTel Android instrumentation for tracking OkHttp requests" + } +} \ No newline at end of file diff --git a/instrumentation/okhttp/plugin/metadata/notice.properties b/instrumentation/okhttp/plugin/metadata/notice.properties new file mode 100644 index 00000000..f5f8d1a1 --- /dev/null +++ b/instrumentation/okhttp/plugin/metadata/notice.properties @@ -0,0 +1 @@ +dependencies.hash=AE6C5B2C9A6083179419FCD268110F8F \ No newline at end of file diff --git a/instrumentation/okhttp/plugin/src/main/java/co/elastic/otel/android/okhttp/OkHttpInstrumentationPlugin.kt b/instrumentation/okhttp/plugin/src/main/java/co/elastic/otel/android/okhttp/OkHttpInstrumentationPlugin.kt new file mode 100644 index 00000000..63686bb1 --- /dev/null +++ b/instrumentation/okhttp/plugin/src/main/java/co/elastic/otel/android/okhttp/OkHttpInstrumentationPlugin.kt @@ -0,0 +1,15 @@ +package co.elastic.otel.android.okhttp + +import co.elastic.otel.android.instrumentation.generated.BuildConfig +import co.elastic.otel.android.plugin.internal.InstrumentationPlugin +import org.gradle.api.Project + +class OkHttpInstrumentationPlugin : InstrumentationPlugin() { + + override fun onApply(target: Project) { + target.dependencies.add( + "implementation", + target.dependencies.create(BuildConfig.LIBRARY_URI) + ) + } +} \ No newline at end of file diff --git a/instrumentation/okhttp/plugin/src/main/resources/META-INF/NOTICE b/instrumentation/okhttp/plugin/src/main/resources/META-INF/NOTICE new file mode 100644 index 00000000..d0920edc --- /dev/null +++ b/instrumentation/okhttp/plugin/src/main/resources/META-INF/NOTICE @@ -0,0 +1,213 @@ +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: + + - Kotlin Stdlib (https://kotlinlang.org/) + +############################################################################### + + + 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 From 03c56e16c09116e8c4c50eaa4dcca8d7a589894e Mon Sep 17 00:00:00 2001 From: Cesar Munoz <56847527+LikeTheSalad@users.noreply.github.com> Date: Tue, 11 Feb 2025 06:57:01 +0100 Subject: [PATCH 02/24] Added http span name test --- .../exportergate/ExporterGateManager.kt | 4 +++ .../android/functional/ElasticApmAgentTest.kt | 35 ++++++++++++++++++- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/android-sdk/src/main/java/co/elastic/otel/android/internal/features/exportergate/ExporterGateManager.kt b/android-sdk/src/main/java/co/elastic/otel/android/internal/features/exportergate/ExporterGateManager.kt index ecb42e15..1cfe77ac 100644 --- a/android-sdk/src/main/java/co/elastic/otel/android/internal/features/exportergate/ExporterGateManager.kt +++ b/android-sdk/src/main/java/co/elastic/otel/android/internal/features/exportergate/ExporterGateManager.kt @@ -87,6 +87,10 @@ internal class ExporterGateManager( metricGateQueue.openLatch(holder) } + internal fun spanGateIsOpen(): Boolean { + return spanGateOpen.get() + } + internal fun metricGateIsOpen(): Boolean { return metricGateOpen.get() } diff --git a/android-sdk/src/test/java/co/elastic/otel/android/functional/ElasticApmAgentTest.kt b/android-sdk/src/test/java/co/elastic/otel/android/functional/ElasticApmAgentTest.kt index b6db9f69..26fa30f5 100644 --- a/android-sdk/src/test/java/co/elastic/otel/android/functional/ElasticApmAgentTest.kt +++ b/android-sdk/src/test/java/co/elastic/otel/android/functional/ElasticApmAgentTest.kt @@ -598,6 +598,38 @@ class ElasticApmAgentTest { assertThat(inMemoryExporters.getFinishedMetrics()).hasSize(1) } + @Test + fun `Validate http span name change`() { + agent = inMemoryAgentBuilder().build() + + sendSpan("Normal Span") + sendSpan( + "GET", + Attributes.of( + AttributeKey.stringKey("url.full"), + "http://somehost.com/some/path?q=some%20query" + ) + ) + sendSpan( + "POST", + Attributes.of( + AttributeKey.stringKey("url.full"), + "https://anotherhost.net:8080/some/path?q=elastic" + ) + ) + + await.atMost(Duration.ofSeconds(1)).until { + agent.getExporterGateManager().spanGateIsOpen() + } + + val finishedSpanNames = inMemoryExporters.getFinishedSpans().map { it.name } + assertThat(finishedSpanNames).containsExactlyInAnyOrder( + "Normal Span", + "GET somehost.com", + "POST anotherhost.net:8080" + ) + } + private fun simpleAgentBuilder( url: String, diskBufferingConfiguration: DiskBufferingConfiguration = DiskBufferingConfiguration.disabled() @@ -625,9 +657,10 @@ class ElasticApmAgentTest { assertThat(attributes.get(AttributeKey.stringKey("session.id"))).isEqualTo(value) } - private fun sendSpan(name: String = "span-name") { + private fun sendSpan(name: String = "span-name", attributes: Attributes = Attributes.empty()) { agent.getOpenTelemetry().getTracer("TestTracer") .spanBuilder(name) + .setAllAttributes(attributes) .startSpan() .end() } From 67bd678a008d27b63182afee09a280ea6e34d71e Mon Sep 17 00:00:00 2001 From: Cesar Munoz <56847527+LikeTheSalad@users.noreply.github.com> Date: Tue, 11 Feb 2025 10:55:48 +0100 Subject: [PATCH 03/24] Updating tests --- .../android/functional/ElasticApmAgentTest.kt | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/android-sdk/src/test/java/co/elastic/otel/android/functional/ElasticApmAgentTest.kt b/android-sdk/src/test/java/co/elastic/otel/android/functional/ElasticApmAgentTest.kt index 26fa30f5..3d8f8aac 100644 --- a/android-sdk/src/test/java/co/elastic/otel/android/functional/ElasticApmAgentTest.kt +++ b/android-sdk/src/test/java/co/elastic/otel/android/functional/ElasticApmAgentTest.kt @@ -617,6 +617,20 @@ class ElasticApmAgentTest { "https://anotherhost.net:8080/some/path?q=elastic" ) ) + sendSpan( + "PUT", + Attributes.of( + AttributeKey.stringKey("url.full"), + "http://127.0.0.1:8080/some/path" + ) + ) + sendSpan( + "GET with something else apart from the verb", + Attributes.of( + AttributeKey.stringKey("url.full"), + "https://anotherhost.net:8080/some/path?q=elastic" + ) + ) await.atMost(Duration.ofSeconds(1)).until { agent.getExporterGateManager().spanGateIsOpen() @@ -626,7 +640,9 @@ class ElasticApmAgentTest { assertThat(finishedSpanNames).containsExactlyInAnyOrder( "Normal Span", "GET somehost.com", - "POST anotherhost.net:8080" + "POST anotherhost.net:8080", + "PUT 127.0.0.1:8080", + "GET with something else apart from the verb", ) } From 6dc48fe255866c9b8a353ed885823146138c28a2 Mon Sep 17 00:00:00 2001 From: Cesar Munoz <56847527+LikeTheSalad@users.noreply.github.com> Date: Tue, 11 Feb 2025 13:34:55 +0100 Subject: [PATCH 04/24] Creating http span interceptors --- .../HttpInterceptorSpanExporter.kt | 38 +++++++++++++++++++ .../HttpSpanExporterInterceptor.kt | 13 +++++++ 2 files changed, 51 insertions(+) create mode 100644 android-sdk/src/main/java/co/elastic/otel/android/internal/features/httpinterceptor/HttpInterceptorSpanExporter.kt create mode 100644 android-sdk/src/main/java/co/elastic/otel/android/internal/features/httpinterceptor/HttpSpanExporterInterceptor.kt diff --git a/android-sdk/src/main/java/co/elastic/otel/android/internal/features/httpinterceptor/HttpInterceptorSpanExporter.kt b/android-sdk/src/main/java/co/elastic/otel/android/internal/features/httpinterceptor/HttpInterceptorSpanExporter.kt new file mode 100644 index 00000000..9dcea6f3 --- /dev/null +++ b/android-sdk/src/main/java/co/elastic/otel/android/internal/features/httpinterceptor/HttpInterceptorSpanExporter.kt @@ -0,0 +1,38 @@ +package co.elastic.otel.android.internal.features.httpinterceptor + +import co.elastic.otel.android.interceptor.Interceptor +import io.opentelemetry.sdk.common.CompletableResultCode +import io.opentelemetry.sdk.trace.data.SpanData +import io.opentelemetry.sdk.trace.export.SpanExporter +import io.opentelemetry.semconv.UrlAttributes + +internal class HttpInterceptorSpanExporter( + private val delegate: SpanExporter, + private val httpSpanInterceptor: Interceptor +) : SpanExporter { + + override fun export(spans: Collection): CompletableResultCode { + val processedSpans = mutableListOf() + + spans.forEach { + processedSpans.add(process(it)) + } + + return delegate.export(processedSpans) + } + + private fun process(spanData: SpanData): SpanData { + if (spanData.attributes.get(UrlAttributes.URL_FULL) != null) { + return httpSpanInterceptor.intercept(spanData) + } + return spanData + } + + override fun flush(): CompletableResultCode { + return delegate.flush() + } + + override fun shutdown(): CompletableResultCode { + return delegate.shutdown() + } +} \ No newline at end of file diff --git a/android-sdk/src/main/java/co/elastic/otel/android/internal/features/httpinterceptor/HttpSpanExporterInterceptor.kt b/android-sdk/src/main/java/co/elastic/otel/android/internal/features/httpinterceptor/HttpSpanExporterInterceptor.kt new file mode 100644 index 00000000..d5f148f3 --- /dev/null +++ b/android-sdk/src/main/java/co/elastic/otel/android/internal/features/httpinterceptor/HttpSpanExporterInterceptor.kt @@ -0,0 +1,13 @@ +package co.elastic.otel.android.internal.features.httpinterceptor + +import co.elastic.otel.android.interceptor.Interceptor +import io.opentelemetry.sdk.trace.data.SpanData +import io.opentelemetry.sdk.trace.export.SpanExporter + +internal class HttpSpanExporterInterceptor(private val httpSpanInterceptor: Interceptor) : + Interceptor { + + override fun intercept(item: SpanExporter): SpanExporter { + return HttpInterceptorSpanExporter(item, httpSpanInterceptor) + } +} \ No newline at end of file From bf63ac58fb2a3c427069c98e3fcf43e201cbcf71 Mon Sep 17 00:00:00 2001 From: Cesar Munoz <56847527+LikeTheSalad@users.noreply.github.com> Date: Tue, 11 Feb 2025 13:51:46 +0100 Subject: [PATCH 05/24] Creating HttpSpanNameInterceptor --- ...InterceptorSpanExporter.kt => HttpSpanExporter.kt} | 2 +- .../httpinterceptor/HttpSpanExporterInterceptor.kt | 2 +- .../httpinterceptor/HttpSpanNameInterceptor.kt | 11 +++++++++++ 3 files changed, 13 insertions(+), 2 deletions(-) rename android-sdk/src/main/java/co/elastic/otel/android/internal/features/httpinterceptor/{HttpInterceptorSpanExporter.kt => HttpSpanExporter.kt} (96%) create mode 100644 android-sdk/src/main/java/co/elastic/otel/android/internal/features/httpinterceptor/HttpSpanNameInterceptor.kt diff --git a/android-sdk/src/main/java/co/elastic/otel/android/internal/features/httpinterceptor/HttpInterceptorSpanExporter.kt b/android-sdk/src/main/java/co/elastic/otel/android/internal/features/httpinterceptor/HttpSpanExporter.kt similarity index 96% rename from android-sdk/src/main/java/co/elastic/otel/android/internal/features/httpinterceptor/HttpInterceptorSpanExporter.kt rename to android-sdk/src/main/java/co/elastic/otel/android/internal/features/httpinterceptor/HttpSpanExporter.kt index 9dcea6f3..7ecc76fa 100644 --- a/android-sdk/src/main/java/co/elastic/otel/android/internal/features/httpinterceptor/HttpInterceptorSpanExporter.kt +++ b/android-sdk/src/main/java/co/elastic/otel/android/internal/features/httpinterceptor/HttpSpanExporter.kt @@ -6,7 +6,7 @@ import io.opentelemetry.sdk.trace.data.SpanData import io.opentelemetry.sdk.trace.export.SpanExporter import io.opentelemetry.semconv.UrlAttributes -internal class HttpInterceptorSpanExporter( +internal class HttpSpanExporter( private val delegate: SpanExporter, private val httpSpanInterceptor: Interceptor ) : SpanExporter { diff --git a/android-sdk/src/main/java/co/elastic/otel/android/internal/features/httpinterceptor/HttpSpanExporterInterceptor.kt b/android-sdk/src/main/java/co/elastic/otel/android/internal/features/httpinterceptor/HttpSpanExporterInterceptor.kt index d5f148f3..ec9cb80d 100644 --- a/android-sdk/src/main/java/co/elastic/otel/android/internal/features/httpinterceptor/HttpSpanExporterInterceptor.kt +++ b/android-sdk/src/main/java/co/elastic/otel/android/internal/features/httpinterceptor/HttpSpanExporterInterceptor.kt @@ -8,6 +8,6 @@ internal class HttpSpanExporterInterceptor(private val httpSpanInterceptor: Inte Interceptor { override fun intercept(item: SpanExporter): SpanExporter { - return HttpInterceptorSpanExporter(item, httpSpanInterceptor) + return HttpSpanExporter(item, httpSpanInterceptor) } } \ No newline at end of file diff --git a/android-sdk/src/main/java/co/elastic/otel/android/internal/features/httpinterceptor/HttpSpanNameInterceptor.kt b/android-sdk/src/main/java/co/elastic/otel/android/internal/features/httpinterceptor/HttpSpanNameInterceptor.kt new file mode 100644 index 00000000..008aad3b --- /dev/null +++ b/android-sdk/src/main/java/co/elastic/otel/android/internal/features/httpinterceptor/HttpSpanNameInterceptor.kt @@ -0,0 +1,11 @@ +package co.elastic.otel.android.internal.features.httpinterceptor + +import co.elastic.otel.android.interceptor.Interceptor +import io.opentelemetry.sdk.trace.data.SpanData + +class HttpSpanNameInterceptor : Interceptor { + + override fun intercept(item: SpanData): SpanData { + TODO("Not yet implemented") + } +} \ No newline at end of file From a1f0ca994b31178f7c98aafe88ad15496a082520 Mon Sep 17 00:00:00 2001 From: Cesar Munoz <56847527+LikeTheSalad@users.noreply.github.com> Date: Tue, 11 Feb 2025 15:08:33 +0100 Subject: [PATCH 06/24] Renaming http spans --- .../HttpSpanNameInterceptor.kt | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/android-sdk/src/main/java/co/elastic/otel/android/internal/features/httpinterceptor/HttpSpanNameInterceptor.kt b/android-sdk/src/main/java/co/elastic/otel/android/internal/features/httpinterceptor/HttpSpanNameInterceptor.kt index 008aad3b..c210f3aa 100644 --- a/android-sdk/src/main/java/co/elastic/otel/android/internal/features/httpinterceptor/HttpSpanNameInterceptor.kt +++ b/android-sdk/src/main/java/co/elastic/otel/android/internal/features/httpinterceptor/HttpSpanNameInterceptor.kt @@ -1,11 +1,36 @@ package co.elastic.otel.android.internal.features.httpinterceptor import co.elastic.otel.android.interceptor.Interceptor +import io.opentelemetry.sdk.trace.data.DelegatingSpanData import io.opentelemetry.sdk.trace.data.SpanData +import io.opentelemetry.semconv.UrlAttributes class HttpSpanNameInterceptor : Interceptor { + private companion object { + private val URL_PATTERN = Regex("https?://([^/]+).*") + } override fun intercept(item: SpanData): SpanData { - TODO("Not yet implemented") + val url = item.attributes.get(UrlAttributes.URL_FULL) ?: return item + + return getNewName(item.name, url)?.let { newName -> + NameDelegatingSpanData(item, newName) + } ?: item + } + + private fun getNewName(name: String, url: String): String? { + return URL_PATTERN.matchEntire(url)?.let { match -> + "$name ${match.groupValues[1]}" + } + } + + private class NameDelegatingSpanData( + delegate: SpanData, + private val name: String + ) : DelegatingSpanData(delegate) { + + override fun getName(): String { + return name + } } } \ No newline at end of file From 53a66ebb625aecad088f2e78b378f3dd595fadfe Mon Sep 17 00:00:00 2001 From: Cesar Munoz <56847527+LikeTheSalad@users.noreply.github.com> Date: Tue, 11 Feb 2025 15:13:50 +0100 Subject: [PATCH 07/24] Setting http span interceptor in builder --- .../co/elastic/otel/android/ElasticApmAgent.kt | 13 +++++++++++++ .../httpinterceptor/HttpSpanExporter.kt | 18 ++++++++++++++++++ .../HttpSpanExporterInterceptor.kt | 18 ++++++++++++++++++ .../httpinterceptor/HttpSpanNameInterceptor.kt | 18 ++++++++++++++++++ 4 files changed, 67 insertions(+) diff --git a/android-sdk/src/main/java/co/elastic/otel/android/ElasticApmAgent.kt b/android-sdk/src/main/java/co/elastic/otel/android/ElasticApmAgent.kt index c387d2f7..5f12bd8b 100644 --- a/android-sdk/src/main/java/co/elastic/otel/android/ElasticApmAgent.kt +++ b/android-sdk/src/main/java/co/elastic/otel/android/ElasticApmAgent.kt @@ -36,6 +36,8 @@ import co.elastic.otel.android.internal.features.centralconfig.CentralConfigurat import co.elastic.otel.android.internal.features.centralconfig.CentralConfigurationManager import co.elastic.otel.android.internal.features.diskbuffering.DiskBufferingConfiguration import co.elastic.otel.android.internal.features.exportergate.ExporterGateManager +import co.elastic.otel.android.internal.features.httpinterceptor.HttpSpanExporterInterceptor +import co.elastic.otel.android.internal.features.httpinterceptor.HttpSpanNameInterceptor import co.elastic.otel.android.internal.features.sessionmanager.SessionManager import co.elastic.otel.android.internal.features.sessionmanager.samplerate.SampleRateManager import co.elastic.otel.android.internal.services.ServiceManager @@ -49,6 +51,7 @@ import io.opentelemetry.api.common.Attributes import io.opentelemetry.sdk.common.CompletableResultCode import io.opentelemetry.sdk.logs.export.LogRecordExporter import io.opentelemetry.sdk.metrics.export.MetricExporter +import io.opentelemetry.sdk.trace.data.SpanData import io.opentelemetry.sdk.trace.export.SpanExporter @Suppress("CanBeParameter") @@ -124,6 +127,7 @@ class ElasticApmAgent internal constructor( private var sessionIdGenerator: SessionIdGenerator? = null private var diskBufferingConfiguration: DiskBufferingConfiguration? = null private var loggingPolicy: LoggingPolicy? = null + private var httpSpanInterceptor: Interceptor? = HttpSpanNameInterceptor() private val managedAgentBuilder = ManagedElasticOtelAgent.Builder() internal var internalExporterProviderInterceptor: Interceptor = Interceptor.noop() @@ -193,6 +197,10 @@ class ElasticApmAgent internal constructor( managedAgentBuilder.setProcessorFactory(value) } + fun setHttpSpanInterceptor(value: Interceptor?) = apply { + httpSpanInterceptor = value + } + internal fun setDiskBufferingConfiguration(value: DiskBufferingConfiguration) = apply { diskBufferingConfiguration = value } @@ -246,6 +254,11 @@ class ElasticApmAgent internal constructor( exporterProvider ) ) + + httpSpanInterceptor?.let { + managedAgentBuilder.addSpanExporterInterceptor(HttpSpanExporterInterceptor(it)) + } + return ElasticApmAgent( managedAgentBuilder.build(serviceManager, managedFeatures), apmServerConnectivityManager, diff --git a/android-sdk/src/main/java/co/elastic/otel/android/internal/features/httpinterceptor/HttpSpanExporter.kt b/android-sdk/src/main/java/co/elastic/otel/android/internal/features/httpinterceptor/HttpSpanExporter.kt index 7ecc76fa..616706cc 100644 --- a/android-sdk/src/main/java/co/elastic/otel/android/internal/features/httpinterceptor/HttpSpanExporter.kt +++ b/android-sdk/src/main/java/co/elastic/otel/android/internal/features/httpinterceptor/HttpSpanExporter.kt @@ -1,3 +1,21 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ package co.elastic.otel.android.internal.features.httpinterceptor import co.elastic.otel.android.interceptor.Interceptor diff --git a/android-sdk/src/main/java/co/elastic/otel/android/internal/features/httpinterceptor/HttpSpanExporterInterceptor.kt b/android-sdk/src/main/java/co/elastic/otel/android/internal/features/httpinterceptor/HttpSpanExporterInterceptor.kt index ec9cb80d..5194462a 100644 --- a/android-sdk/src/main/java/co/elastic/otel/android/internal/features/httpinterceptor/HttpSpanExporterInterceptor.kt +++ b/android-sdk/src/main/java/co/elastic/otel/android/internal/features/httpinterceptor/HttpSpanExporterInterceptor.kt @@ -1,3 +1,21 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ package co.elastic.otel.android.internal.features.httpinterceptor import co.elastic.otel.android.interceptor.Interceptor diff --git a/android-sdk/src/main/java/co/elastic/otel/android/internal/features/httpinterceptor/HttpSpanNameInterceptor.kt b/android-sdk/src/main/java/co/elastic/otel/android/internal/features/httpinterceptor/HttpSpanNameInterceptor.kt index c210f3aa..a509a621 100644 --- a/android-sdk/src/main/java/co/elastic/otel/android/internal/features/httpinterceptor/HttpSpanNameInterceptor.kt +++ b/android-sdk/src/main/java/co/elastic/otel/android/internal/features/httpinterceptor/HttpSpanNameInterceptor.kt @@ -1,3 +1,21 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ package co.elastic.otel.android.internal.features.httpinterceptor import co.elastic.otel.android.interceptor.Interceptor From 0454d70394f70a394b0e75542fe466f23dcbe7d3 Mon Sep 17 00:00:00 2001 From: Cesar Munoz <56847527+LikeTheSalad@users.noreply.github.com> Date: Tue, 11 Feb 2025 15:39:47 +0100 Subject: [PATCH 08/24] Validating http known methods --- .../httpinterceptor/HttpSpanNameInterceptor.kt | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/android-sdk/src/main/java/co/elastic/otel/android/internal/features/httpinterceptor/HttpSpanNameInterceptor.kt b/android-sdk/src/main/java/co/elastic/otel/android/internal/features/httpinterceptor/HttpSpanNameInterceptor.kt index a509a621..a0414876 100644 --- a/android-sdk/src/main/java/co/elastic/otel/android/internal/features/httpinterceptor/HttpSpanNameInterceptor.kt +++ b/android-sdk/src/main/java/co/elastic/otel/android/internal/features/httpinterceptor/HttpSpanNameInterceptor.kt @@ -26,13 +26,19 @@ import io.opentelemetry.semconv.UrlAttributes class HttpSpanNameInterceptor : Interceptor { private companion object { private val URL_PATTERN = Regex("https?://([^/]+).*") + private val KNOWN_METHODS = + listOf("CONNECT", "DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT", "TRACE") } override fun intercept(item: SpanData): SpanData { + if (item.name !in KNOWN_METHODS) { + return item + } + val url = item.attributes.get(UrlAttributes.URL_FULL) ?: return item return getNewName(item.name, url)?.let { newName -> - NameDelegatingSpanData(item, newName) + NameOverrideDelegatingSpanData(item, newName) } ?: item } @@ -42,7 +48,7 @@ class HttpSpanNameInterceptor : Interceptor { } } - private class NameDelegatingSpanData( + private class NameOverrideDelegatingSpanData( delegate: SpanData, private val name: String ) : DelegatingSpanData(delegate) { From ce9cfb2f3bb56328b746281dfcff502745adc6de Mon Sep 17 00:00:00 2001 From: Cesar Munoz <56847527+LikeTheSalad@users.noreply.github.com> Date: Tue, 11 Feb 2025 15:49:31 +0100 Subject: [PATCH 09/24] Adding tests --- .../android/functional/ElasticApmAgentTest.kt | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/android-sdk/src/test/java/co/elastic/otel/android/functional/ElasticApmAgentTest.kt b/android-sdk/src/test/java/co/elastic/otel/android/functional/ElasticApmAgentTest.kt index 3d8f8aac..437e2c28 100644 --- a/android-sdk/src/test/java/co/elastic/otel/android/functional/ElasticApmAgentTest.kt +++ b/android-sdk/src/test/java/co/elastic/otel/android/functional/ElasticApmAgentTest.kt @@ -646,6 +646,56 @@ class ElasticApmAgentTest { ) } + @Test + fun `Validate http span name change disabled`() { + agent = inMemoryAgentBuilder() + .setHttpSpanInterceptor(null) + .build() + + sendSpan("Normal Span") + sendSpan( + "GET", + Attributes.of( + AttributeKey.stringKey("url.full"), + "http://somehost.com/some/path?q=some%20query" + ) + ) + sendSpan( + "POST", + Attributes.of( + AttributeKey.stringKey("url.full"), + "https://anotherhost.net:8080/some/path?q=elastic" + ) + ) + sendSpan( + "PUT", + Attributes.of( + AttributeKey.stringKey("url.full"), + "http://127.0.0.1:8080/some/path" + ) + ) + sendSpan( + "GET with something else apart from the verb", + Attributes.of( + AttributeKey.stringKey("url.full"), + "https://anotherhost.net:8080/some/path?q=elastic" + ) + ) + + await.atMost(Duration.ofSeconds(1)).until { + agent.getExporterGateManager().spanGateIsOpen() + } + + val finishedSpanNames = inMemoryExporters.getFinishedSpans().map { it.name } + assertThat(finishedSpanNames).containsExactlyInAnyOrder( + "Normal Span", + "GET", + "POST", + "PUT", + "GET with something else apart from the verb", + ) + } + private fun simpleAgentBuilder( url: String, diskBufferingConfiguration: DiskBufferingConfiguration = DiskBufferingConfiguration.disabled() From c7b6f32fde5798d62c9cf4054eb5b4b211b81344 Mon Sep 17 00:00:00 2001 From: Cesar Munoz <56847527+LikeTheSalad@users.noreply.github.com> Date: Wed, 12 Feb 2025 06:03:36 +0100 Subject: [PATCH 10/24] Created okhttp test project --- .../instrumentation/okhttp/build.gradle.kts | 9 +++++++++ .../otel/android/test/InstrumentationTest.kt | 15 +++++++++++++++ .../okhttp/src/main/AndroidManifest.xml | 16 ++++++++++++++++ .../co/elastic/otel/android/test/MainActivity.kt | 5 +++++ 4 files changed, 45 insertions(+) create mode 100644 android-test/instrumentation/okhttp/build.gradle.kts create mode 100644 android-test/instrumentation/okhttp/src/androidTest/java/co/elastic/otel/android/test/InstrumentationTest.kt create mode 100644 android-test/instrumentation/okhttp/src/main/AndroidManifest.xml create mode 100644 android-test/instrumentation/okhttp/src/main/java/co/elastic/otel/android/test/MainActivity.kt diff --git a/android-test/instrumentation/okhttp/build.gradle.kts b/android-test/instrumentation/okhttp/build.gradle.kts new file mode 100644 index 00000000..1076a107 --- /dev/null +++ b/android-test/instrumentation/okhttp/build.gradle.kts @@ -0,0 +1,9 @@ +plugins { + id("elastic.instrumentation-test-app") + id("co.elastic.otel.android.agent") + id("co.elastic.otel.android.instrumentation-okhttp") +} + +android { + namespace = "co.elastic.otel.android.test" +} \ No newline at end of file diff --git a/android-test/instrumentation/okhttp/src/androidTest/java/co/elastic/otel/android/test/InstrumentationTest.kt b/android-test/instrumentation/okhttp/src/androidTest/java/co/elastic/otel/android/test/InstrumentationTest.kt new file mode 100644 index 00000000..34ea094b --- /dev/null +++ b/android-test/instrumentation/okhttp/src/androidTest/java/co/elastic/otel/android/test/InstrumentationTest.kt @@ -0,0 +1,15 @@ +package co.elastic.otel.android.test + +import co.elastic.otel.android.test.rule.AndroidTestAgentRule +import org.junit.Rule +import org.junit.Test + +class InstrumentationTest { + @get:Rule + val agentRule = AndroidTestAgentRule() + + @Test + fun verifyOkhttpSyncSpan() { + TODO() + } +} \ No newline at end of file diff --git a/android-test/instrumentation/okhttp/src/main/AndroidManifest.xml b/android-test/instrumentation/okhttp/src/main/AndroidManifest.xml new file mode 100644 index 00000000..d0c6c8f4 --- /dev/null +++ b/android-test/instrumentation/okhttp/src/main/AndroidManifest.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android-test/instrumentation/okhttp/src/main/java/co/elastic/otel/android/test/MainActivity.kt b/android-test/instrumentation/okhttp/src/main/java/co/elastic/otel/android/test/MainActivity.kt new file mode 100644 index 00000000..9303df20 --- /dev/null +++ b/android-test/instrumentation/okhttp/src/main/java/co/elastic/otel/android/test/MainActivity.kt @@ -0,0 +1,5 @@ +package co.elastic.otel.android.test + +import android.app.Activity + +class MainActivity : Activity() \ No newline at end of file From dc6ea6b97e28bcb83087a7d165c2f4752e02f1aa Mon Sep 17 00:00:00 2001 From: Cesar Munoz <56847527+LikeTheSalad@users.noreply.github.com> Date: Wed, 12 Feb 2025 07:03:37 +0100 Subject: [PATCH 11/24] Added okhttp simple test --- .../instrumentation/okhttp/build.gradle.kts | 5 + .../otel/android/test/InstrumentationTest.kt | 113 +++++++++++++++++- android-test/settings.gradle.kts | 3 + gradle/instrumentation.versions.toml | 2 + .../okhttp/internal/OkHttpInstrumentation.kt | 2 +- 5 files changed, 122 insertions(+), 3 deletions(-) diff --git a/android-test/instrumentation/okhttp/build.gradle.kts b/android-test/instrumentation/okhttp/build.gradle.kts index 1076a107..592868c8 100644 --- a/android-test/instrumentation/okhttp/build.gradle.kts +++ b/android-test/instrumentation/okhttp/build.gradle.kts @@ -6,4 +6,9 @@ plugins { android { namespace = "co.elastic.otel.android.test" +} + +dependencies { + androidTestImplementation(instrumentation.okhttp) + androidTestImplementation(instrumentation.mockWebServer) } \ No newline at end of file diff --git a/android-test/instrumentation/okhttp/src/androidTest/java/co/elastic/otel/android/test/InstrumentationTest.kt b/android-test/instrumentation/okhttp/src/androidTest/java/co/elastic/otel/android/test/InstrumentationTest.kt index 34ea094b..f7312647 100644 --- a/android-test/instrumentation/okhttp/src/androidTest/java/co/elastic/otel/android/test/InstrumentationTest.kt +++ b/android-test/instrumentation/okhttp/src/androidTest/java/co/elastic/otel/android/test/InstrumentationTest.kt @@ -1,15 +1,124 @@ package co.elastic.otel.android.test import co.elastic.otel.android.test.rule.AndroidTestAgentRule +import io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat +import java.io.IOException +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicReference +import java.util.function.Consumer +import junit.framework.TestCase.fail +import okhttp3.Call +import okhttp3.Callback +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.Response +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer +import org.junit.After +import org.junit.Before import org.junit.Rule import org.junit.Test + class InstrumentationTest { + @get:Rule val agentRule = AndroidTestAgentRule() + private lateinit var webServer: MockWebServer + private lateinit var clientBuilder: OkHttpClient.Builder + + @Before + fun setUp() { + webServer = MockWebServer() + webServer.start() + clientBuilder = OkHttpClient.Builder() + } + + @After + fun tearDown() { + webServer.shutdown() + } + @Test - fun verifyOkhttpSyncSpan() { - TODO() + fun verifyOkHttpSyncCallSpan() { + executeSyncHttpCall("GET", 200, "{}") + + assertThat(agentRule.getFinishedSpans()).hasSize(1) + assertThat(agentRule.getFinishedSpans().first()).hasName("GET") + } + + private fun executeSuccessfulHttpCall(responseCode: Int) { + executeSuccessfulHttpCall(responseCode, "{}", emptyMap()) + } + + private fun executeSuccessfulHttpCall( + responseCode: Int, + responseBody: String, + responseHeaders: Map + ) { + val mockResponse = MockResponse().setResponseCode(responseCode).setBody(responseBody) + responseHeaders.forEach { (name: String, value: String) -> + mockResponse.addHeader(name, value) + } + webServer.enqueue(mockResponse) + + val responseStr = AtomicReference("") + try { + executeAsyncHttpCall( + Request.Builder().url(webServer.url("/")).build() + ) { response -> + try { + responseStr.set(response.body!!.string()) + } catch (e: IOException) { + fail(e.message) + } + } + } catch (e: InterruptedException) { + throw RuntimeException(e) + } + assertThat(responseStr.get()).isEqualTo(responseBody) + } + + private fun executeFailedHttpCall() { + val url = webServer.url("/") + webServer.shutdown() + + try { + executeAsyncHttpCall(Request.Builder().url(url).build()) { } + } catch (e: InterruptedException) { + throw RuntimeException(e) + } + } + + private fun executeAsyncHttpCall(request: Request, responseConsumer: Consumer) { + val latch = CountDownLatch(1) + clientBuilder.build().newCall(request).enqueue(object : Callback { + override fun onFailure(call: Call, e: IOException) { + latch.countDown() + } + + override fun onResponse(call: Call, response: Response) { + responseConsumer.accept(response) + latch.countDown() + } + }) + latch.await(1, TimeUnit.SECONDS) + } + + private fun executeSyncHttpCall( + method: String, + responseCode: Int, + responseBody: String + ): Response { + val mockResponse = MockResponse().setResponseCode(responseCode).setBody(responseBody) + webServer.enqueue(mockResponse) + + val request = Request.Builder() + .method(method, null) + .url(webServer.url("/")).build() + + return clientBuilder.build().newCall(request).execute() } } \ No newline at end of file diff --git a/android-test/settings.gradle.kts b/android-test/settings.gradle.kts index d89b0c9a..4d8e91f2 100644 --- a/android-test/settings.gradle.kts +++ b/android-test/settings.gradle.kts @@ -10,6 +10,9 @@ dependencyResolutionManagement { create("libs") { from(files("../gradle/libs.versions.toml")) } + create("instrumentation") { + from(files("../gradle/instrumentation.versions.toml")) + } } repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { diff --git a/gradle/instrumentation.versions.toml b/gradle/instrumentation.versions.toml index 32842cae..115e92fa 100644 --- a/gradle/instrumentation.versions.toml +++ b/gradle/instrumentation.versions.toml @@ -1,12 +1,14 @@ [libraries] androidx-lifecycle-process = "androidx.lifecycle:lifecycle-process:2.8.7" opentelemetry-api-incubator = "io.opentelemetry:opentelemetry-api-incubator:1.46.0-alpha" +okhttp = "com.squareup.okhttp3:okhttp:4.12.0" # Testing androidx-test-core = "androidx.test:core-ktx:1.6.1" androidx-test-rules = "androidx.test:rules:1.6.1" androidx-test-runner = "androidx.test:runner:1.6.2" androidx-test-ext = "androidx.test.ext:junit:1.2.1" +mockWebServer = "com.squareup.okhttp3:mockwebserver:4.12.0" [bundles] androidTest = ["androidx-test-rules", "androidx-test-runner", "androidx-test-ext", "androidx-test-core"] diff --git a/instrumentation/okhttp/library/src/main/java/co/elastic/otel/android/okhttp/internal/OkHttpInstrumentation.kt b/instrumentation/okhttp/library/src/main/java/co/elastic/otel/android/okhttp/internal/OkHttpInstrumentation.kt index 6c7f6ce1..0a680a21 100644 --- a/instrumentation/okhttp/library/src/main/java/co/elastic/otel/android/okhttp/internal/OkHttpInstrumentation.kt +++ b/instrumentation/okhttp/library/src/main/java/co/elastic/otel/android/okhttp/internal/OkHttpInstrumentation.kt @@ -27,6 +27,6 @@ import com.google.auto.service.AutoService class OkHttpInstrumentation : Instrumentation { override fun install(application: Application, agent: ElasticOtelAgent) { - TODO() + } } \ No newline at end of file From d1fcce7e72bde03a7603e0e56ce28f4b76d58e5c Mon Sep 17 00:00:00 2001 From: Cesar Munoz <56847527+LikeTheSalad@users.noreply.github.com> Date: Wed, 12 Feb 2025 09:33:58 +0100 Subject: [PATCH 12/24] Creating okhttp bytebuddy project --- .../elastic.instrumentation-plugin.gradle.kts | 8 +- .../okhttp/bytebuddy/build.gradle.kts | 7 + .../bytebuddy/metadata/notice.properties | 1 + .../src/main/resources/META-INF/NOTICE | 215 ++++++++++++++++++ .../okhttp/plugin/build.gradle.kts | 1 + .../okhttp/OkHttpInstrumentationPlugin.kt | 4 + 6 files changed, 234 insertions(+), 2 deletions(-) create mode 100644 instrumentation/okhttp/bytebuddy/build.gradle.kts create mode 100644 instrumentation/okhttp/bytebuddy/metadata/notice.properties create mode 100644 instrumentation/okhttp/bytebuddy/src/main/resources/META-INF/NOTICE diff --git a/build-tools/src/main/kotlin/elastic.instrumentation-plugin.gradle.kts b/build-tools/src/main/kotlin/elastic.instrumentation-plugin.gradle.kts index 1e53575b..4f6c5628 100644 --- a/build-tools/src/main/kotlin/elastic.instrumentation-plugin.gradle.kts +++ b/build-tools/src/main/kotlin/elastic.instrumentation-plugin.gradle.kts @@ -17,11 +17,15 @@ abstract class ElasticBuildConfig @Inject constructor( private val buildConfigGroupId: String, private val projectVersion: String ) { - fun projectUri(fieldName: String, dependencyProjectName: String) { + fun projectUri(fieldName: String, artifactName: String) { + dependencyUri(fieldName, "${buildConfigGroupId}:$artifactName:$projectVersion") + } + + fun dependencyUri(fieldName: String, dependencyUri: String) { buildConfigExtension.buildConfigField( "String", fieldName, - """"${buildConfigGroupId}:$dependencyProjectName:$projectVersion"""" + """"$dependencyUri"""" ) } } diff --git a/instrumentation/okhttp/bytebuddy/build.gradle.kts b/instrumentation/okhttp/bytebuddy/build.gradle.kts new file mode 100644 index 00000000..83b52681 --- /dev/null +++ b/instrumentation/okhttp/bytebuddy/build.gradle.kts @@ -0,0 +1,7 @@ +plugins { + id("elastic.instrumentation-library") +} + +android { + namespace = "co.elastic.otel.android.instrumentation.okhttp.bytebuddy" +} \ No newline at end of file diff --git a/instrumentation/okhttp/bytebuddy/metadata/notice.properties b/instrumentation/okhttp/bytebuddy/metadata/notice.properties new file mode 100644 index 00000000..c9161b6d --- /dev/null +++ b/instrumentation/okhttp/bytebuddy/metadata/notice.properties @@ -0,0 +1 @@ +dependencies.hash=C98ECB36B923E54714316C7764978779 \ No newline at end of file diff --git a/instrumentation/okhttp/bytebuddy/src/main/resources/META-INF/NOTICE b/instrumentation/okhttp/bytebuddy/src/main/resources/META-INF/NOTICE new file mode 100644 index 00000000..ba699da2 --- /dev/null +++ b/instrumentation/okhttp/bytebuddy/src/main/resources/META-INF/NOTICE @@ -0,0 +1,215 @@ +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: + + - Kotlin Stdlib (https://kotlinlang.org/) + - OpenTelemetry Java (https://github.com/open-telemetry/opentelemetry-java) + - OpenTelemetry Semantic Conventions Java (https://github.com/open-telemetry/semantic-conventions-java) + +############################################################################### + + + 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/instrumentation/okhttp/plugin/build.gradle.kts b/instrumentation/okhttp/plugin/build.gradle.kts index 29fbe0dc..8d645e0c 100644 --- a/instrumentation/okhttp/plugin/build.gradle.kts +++ b/instrumentation/okhttp/plugin/build.gradle.kts @@ -4,6 +4,7 @@ plugins { elasticBuildConfig { projectUri("LIBRARY_URI", "okhttp-library") + projectUri("BYTEBUDDY_PLUGIN", "okhttp-bytebuddy") } elasticInstrumentationPlugins { diff --git a/instrumentation/okhttp/plugin/src/main/java/co/elastic/otel/android/okhttp/OkHttpInstrumentationPlugin.kt b/instrumentation/okhttp/plugin/src/main/java/co/elastic/otel/android/okhttp/OkHttpInstrumentationPlugin.kt index 63686bb1..c4512b11 100644 --- a/instrumentation/okhttp/plugin/src/main/java/co/elastic/otel/android/okhttp/OkHttpInstrumentationPlugin.kt +++ b/instrumentation/okhttp/plugin/src/main/java/co/elastic/otel/android/okhttp/OkHttpInstrumentationPlugin.kt @@ -11,5 +11,9 @@ class OkHttpInstrumentationPlugin : InstrumentationPlugin() { "implementation", target.dependencies.create(BuildConfig.LIBRARY_URI) ) + target.dependencies.add( + "byteBuddy", + target.dependencies.create(BuildConfig.BYTEBUDDY_PLUGIN) + ) } } \ No newline at end of file From 7146d5146d0d19bda87890bb528e214d3ec593d8 Mon Sep 17 00:00:00 2001 From: Cesar Munoz <56847527+LikeTheSalad@users.noreply.github.com> Date: Wed, 12 Feb 2025 09:51:48 +0100 Subject: [PATCH 13/24] Adding okhttp library bytebuddy plugin symbols --- gradle/instrumentation.versions.toml | 1 + .../okhttp/bytebuddy/build.gradle.kts | 6 +++ .../okhttp/internal/OkHttpClientAdvice.java | 26 +++++++++++ .../okhttp/internal/OkHttpClientPlugin.java | 44 +++++++++++++++++++ .../callback/OkHttpCallbackAdvice.java | 26 +++++++++++ .../callback/OkHttpCallbackPlugin.java | 44 +++++++++++++++++++ .../resources/net.bytebuddy/build.plugins | 2 + .../okhttp/library/build.gradle.kts | 5 +++ .../plugin/OkHttpCallbackAdviceHelper.java | 40 +++++++++++++++++ .../internal/plugin/TracingCallback.java | 42 ++++++++++++++++++ 10 files changed, 236 insertions(+) create mode 100644 instrumentation/okhttp/bytebuddy/src/main/java/co/elastic/otel/android/okhttp/internal/OkHttpClientAdvice.java create mode 100644 instrumentation/okhttp/bytebuddy/src/main/java/co/elastic/otel/android/okhttp/internal/OkHttpClientPlugin.java create mode 100644 instrumentation/okhttp/bytebuddy/src/main/java/co/elastic/otel/android/okhttp/internal/callback/OkHttpCallbackAdvice.java create mode 100644 instrumentation/okhttp/bytebuddy/src/main/java/co/elastic/otel/android/okhttp/internal/callback/OkHttpCallbackPlugin.java create mode 100644 instrumentation/okhttp/bytebuddy/src/main/resources/net.bytebuddy/build.plugins create mode 100644 instrumentation/okhttp/library/src/main/java/co/elastic/otel/android/okhttp/internal/plugin/OkHttpCallbackAdviceHelper.java create mode 100644 instrumentation/okhttp/library/src/main/java/co/elastic/otel/android/okhttp/internal/plugin/TracingCallback.java diff --git a/gradle/instrumentation.versions.toml b/gradle/instrumentation.versions.toml index 115e92fa..61824118 100644 --- a/gradle/instrumentation.versions.toml +++ b/gradle/instrumentation.versions.toml @@ -1,5 +1,6 @@ [libraries] androidx-lifecycle-process = "androidx.lifecycle:lifecycle-process:2.8.7" +opentelemetry-instrumentation-api = "io.opentelemetry.instrumentation:opentelemetry-instrumentation-api:2.12.0" opentelemetry-api-incubator = "io.opentelemetry:opentelemetry-api-incubator:1.46.0-alpha" okhttp = "com.squareup.okhttp3:okhttp:4.12.0" diff --git a/instrumentation/okhttp/bytebuddy/build.gradle.kts b/instrumentation/okhttp/bytebuddy/build.gradle.kts index 83b52681..701bad33 100644 --- a/instrumentation/okhttp/bytebuddy/build.gradle.kts +++ b/instrumentation/okhttp/bytebuddy/build.gradle.kts @@ -4,4 +4,10 @@ plugins { android { namespace = "co.elastic.otel.android.instrumentation.okhttp.bytebuddy" +} + +dependencies { + implementation(project(":instrumentation:okhttp:library")) + implementation(libs.byteBuddy) + implementation(instrumentation.okhttp) } \ No newline at end of file diff --git a/instrumentation/okhttp/bytebuddy/src/main/java/co/elastic/otel/android/okhttp/internal/OkHttpClientAdvice.java b/instrumentation/okhttp/bytebuddy/src/main/java/co/elastic/otel/android/okhttp/internal/OkHttpClientAdvice.java new file mode 100644 index 00000000..bb876e1a --- /dev/null +++ b/instrumentation/okhttp/bytebuddy/src/main/java/co/elastic/otel/android/okhttp/internal/OkHttpClientAdvice.java @@ -0,0 +1,26 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package co.elastic.otel.android.okhttp.internal; + +import net.bytebuddy.asm.Advice; + +import co.elastic.otel.android.okhttp.internal.plugin.OkHttp3Singletons; +import okhttp3.OkHttpClient; + +public class OkHttpClientAdvice { + + @Advice.OnMethodEnter + public static void enter(@Advice.Argument(0) OkHttpClient.Builder builder) { + if (!builder.interceptors().contains(OkHttp3Singletons.CALLBACK_CONTEXT_INTERCEPTOR)) { + builder.interceptors().add(0, OkHttp3Singletons.CALLBACK_CONTEXT_INTERCEPTOR); + builder.interceptors().add(1, OkHttp3Singletons.RESEND_COUNT_CONTEXT_INTERCEPTOR); + builder.interceptors().add(2, OkHttp3Singletons.CONNECTION_ERROR_INTERCEPTOR); + } + if (!builder.networkInterceptors().contains(OkHttp3Singletons.TRACING_INTERCEPTOR)) { + builder.addNetworkInterceptor(OkHttp3Singletons.TRACING_INTERCEPTOR); + } + } +} diff --git a/instrumentation/okhttp/bytebuddy/src/main/java/co/elastic/otel/android/okhttp/internal/OkHttpClientPlugin.java b/instrumentation/okhttp/bytebuddy/src/main/java/co/elastic/otel/android/okhttp/internal/OkHttpClientPlugin.java new file mode 100644 index 00000000..52acf79b --- /dev/null +++ b/instrumentation/okhttp/bytebuddy/src/main/java/co/elastic/otel/android/okhttp/internal/OkHttpClientPlugin.java @@ -0,0 +1,44 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package co.elastic.otel.android.okhttp.internal; + +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/instrumentation/okhttp/bytebuddy/src/main/java/co/elastic/otel/android/okhttp/internal/callback/OkHttpCallbackAdvice.java b/instrumentation/okhttp/bytebuddy/src/main/java/co/elastic/otel/android/okhttp/internal/callback/OkHttpCallbackAdvice.java new file mode 100644 index 00000000..ca759562 --- /dev/null +++ b/instrumentation/okhttp/bytebuddy/src/main/java/co/elastic/otel/android/okhttp/internal/callback/OkHttpCallbackAdvice.java @@ -0,0 +1,26 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package co.elastic.otel.android.okhttp.internal.callback; + +import net.bytebuddy.asm.Advice; + +import co.elastic.otel.android.okhttp.internal.plugin.OkHttpCallbackAdviceHelper; +import co.elastic.otel.android.okhttp.internal.plugin.TracingCallback; +import io.opentelemetry.context.Context; +import okhttp3.Call; +import okhttp3.Callback; + +public class OkHttpCallbackAdvice { + + @Advice.OnMethodEnter + public static void enter( + @Advice.This Call call, + @Advice.Argument(value = 0, readOnly = false) Callback callback) { + if (OkHttpCallbackAdviceHelper.propagateContext(call)) { + callback = new TracingCallback(callback, Context.current()); + } + } +} diff --git a/instrumentation/okhttp/bytebuddy/src/main/java/co/elastic/otel/android/okhttp/internal/callback/OkHttpCallbackPlugin.java b/instrumentation/okhttp/bytebuddy/src/main/java/co/elastic/otel/android/okhttp/internal/callback/OkHttpCallbackPlugin.java new file mode 100644 index 00000000..5fcaa275 --- /dev/null +++ b/instrumentation/okhttp/bytebuddy/src/main/java/co/elastic/otel/android/okhttp/internal/callback/OkHttpCallbackPlugin.java @@ -0,0 +1,44 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package co.elastic.otel.android.okhttp.internal.callback; + +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; + +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 java.io.IOException; +import java.util.regex.Pattern; + +import okhttp3.Callback; + +public class OkHttpCallbackPlugin implements Plugin { + private static final Pattern REAL_CALL_PATTERN = Pattern.compile("^okhttp3\\..*RealCall$"); + + @Override + public DynamicType.Builder apply( + DynamicType.Builder builder, + TypeDescription typeDescription, + ClassFileLocator classFileLocator) { + return builder.visit( + Advice.to(OkHttpCallbackAdvice.class) + .on(named("enqueue").and(takesArgument(0, Callback.class)))); + } + + @Override + public void close() throws IOException { + // No operation. + } + + @Override + public boolean matches(TypeDescription target) { + return REAL_CALL_PATTERN.matcher(target.getTypeName()).matches(); + } +} diff --git a/instrumentation/okhttp/bytebuddy/src/main/resources/net.bytebuddy/build.plugins b/instrumentation/okhttp/bytebuddy/src/main/resources/net.bytebuddy/build.plugins new file mode 100644 index 00000000..bf154824 --- /dev/null +++ b/instrumentation/okhttp/bytebuddy/src/main/resources/net.bytebuddy/build.plugins @@ -0,0 +1,2 @@ +co.elastic.otel.android.okhttp.internal.OkHttpClientPlugin +co.elastic.otel.android.okhttp.internal.callback.OkHttpCallbackPlugin \ No newline at end of file diff --git a/instrumentation/okhttp/library/build.gradle.kts b/instrumentation/okhttp/library/build.gradle.kts index 662e1978..ec1056f1 100644 --- a/instrumentation/okhttp/library/build.gradle.kts +++ b/instrumentation/okhttp/library/build.gradle.kts @@ -4,4 +4,9 @@ plugins { android { namespace = "co.elastic.otel.android.instrumentation.okhttp" +} + +dependencies { + implementation(instrumentation.opentelemetry.instrumentation.api) + compileOnly(instrumentation.okhttp) } \ No newline at end of file diff --git a/instrumentation/okhttp/library/src/main/java/co/elastic/otel/android/okhttp/internal/plugin/OkHttpCallbackAdviceHelper.java b/instrumentation/okhttp/library/src/main/java/co/elastic/otel/android/okhttp/internal/plugin/OkHttpCallbackAdviceHelper.java new file mode 100644 index 00000000..de200ed0 --- /dev/null +++ b/instrumentation/okhttp/library/src/main/java/co/elastic/otel/android/okhttp/internal/plugin/OkHttpCallbackAdviceHelper.java @@ -0,0 +1,40 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package co.elastic.otel.android.okhttp.internal.plugin; + +import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.api.util.VirtualField; +import okhttp3.Call; +import okhttp3.Request; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public final class OkHttpCallbackAdviceHelper { + + public static boolean propagateContext(Call call) { + Context context = Context.current(); + if (shouldPropagateContext(context)) { + VirtualField virtualField = + VirtualField.find(Request.class, Context.class); + virtualField.set(call.request(), context); + return true; + } + + return false; + } + + public static Context tryRecoverPropagatedContextFromCallback(Request request) { + VirtualField virtualField = + VirtualField.find(Request.class, Context.class); + return virtualField.get(request); + } + + private static boolean shouldPropagateContext(Context context) { + return context != Context.root(); + } +} diff --git a/instrumentation/okhttp/library/src/main/java/co/elastic/otel/android/okhttp/internal/plugin/TracingCallback.java b/instrumentation/okhttp/library/src/main/java/co/elastic/otel/android/okhttp/internal/plugin/TracingCallback.java new file mode 100644 index 00000000..9b237255 --- /dev/null +++ b/instrumentation/okhttp/library/src/main/java/co/elastic/otel/android/okhttp/internal/plugin/TracingCallback.java @@ -0,0 +1,42 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package co.elastic.otel.android.okhttp.internal.plugin; + +import java.io.IOException; + +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.Response; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public final class TracingCallback implements Callback { + private final Callback delegate; + private final Context callingContext; + + public TracingCallback(Callback delegate, Context callingContext) { + this.delegate = delegate; + this.callingContext = callingContext; + } + + @Override + public void onFailure(Call call, IOException e) { + try (Scope scope = callingContext.makeCurrent()) { + delegate.onFailure(call, e); + } + } + + @Override + public void onResponse(Call call, Response response) throws IOException { + try (Scope scope = callingContext.makeCurrent()) { + delegate.onResponse(call, response); + } + } +} From bb17b934c389ad6b90879ef7a1ea243c6d9f6cda Mon Sep 17 00:00:00 2001 From: Cesar Munoz <56847527+LikeTheSalad@users.noreply.github.com> Date: Wed, 12 Feb 2025 15:09:56 +0100 Subject: [PATCH 14/24] Configuring OkHttp3Singletons --- gradle/instrumentation.versions.toml | 5 ++ .../okhttp/library/build.gradle.kts | 2 + .../okhttp/internal/OkHttpInstrumentation.kt | 3 +- .../internal/plugin/OkHttp3Singletons.java | 78 +++++++++++++++++++ 4 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 instrumentation/okhttp/library/src/main/java/co/elastic/otel/android/okhttp/internal/plugin/OkHttp3Singletons.java diff --git a/gradle/instrumentation.versions.toml b/gradle/instrumentation.versions.toml index 61824118..5d52cabd 100644 --- a/gradle/instrumentation.versions.toml +++ b/gradle/instrumentation.versions.toml @@ -1,6 +1,11 @@ +[versions] +opentelemetry-instrumentation-alpha = "2.12.0-alpha" + [libraries] androidx-lifecycle-process = "androidx.lifecycle:lifecycle-process:2.8.7" opentelemetry-instrumentation-api = "io.opentelemetry.instrumentation:opentelemetry-instrumentation-api:2.12.0" +opentelemetry-instrumentation-api-incubator = { module = "io.opentelemetry.instrumentation:opentelemetry-instrumentation-api-incubator", version.ref = "opentelemetry-instrumentation-alpha" } +opentelemetry-instrumentation-okhttp = { module = "io.opentelemetry.instrumentation:opentelemetry-okhttp-3.0", version.ref = "opentelemetry-instrumentation-alpha" } opentelemetry-api-incubator = "io.opentelemetry:opentelemetry-api-incubator:1.46.0-alpha" okhttp = "com.squareup.okhttp3:okhttp:4.12.0" diff --git a/instrumentation/okhttp/library/build.gradle.kts b/instrumentation/okhttp/library/build.gradle.kts index ec1056f1..13bb482f 100644 --- a/instrumentation/okhttp/library/build.gradle.kts +++ b/instrumentation/okhttp/library/build.gradle.kts @@ -8,5 +8,7 @@ android { dependencies { implementation(instrumentation.opentelemetry.instrumentation.api) + implementation(instrumentation.opentelemetry.instrumentation.api.incubator) + implementation(instrumentation.opentelemetry.instrumentation.okhttp) compileOnly(instrumentation.okhttp) } \ No newline at end of file diff --git a/instrumentation/okhttp/library/src/main/java/co/elastic/otel/android/okhttp/internal/OkHttpInstrumentation.kt b/instrumentation/okhttp/library/src/main/java/co/elastic/otel/android/okhttp/internal/OkHttpInstrumentation.kt index 0a680a21..5768534d 100644 --- a/instrumentation/okhttp/library/src/main/java/co/elastic/otel/android/okhttp/internal/OkHttpInstrumentation.kt +++ b/instrumentation/okhttp/library/src/main/java/co/elastic/otel/android/okhttp/internal/OkHttpInstrumentation.kt @@ -21,12 +21,13 @@ package co.elastic.otel.android.okhttp.internal import android.app.Application import co.elastic.otel.android.api.ElasticOtelAgent import co.elastic.otel.android.instrumentation.internal.Instrumentation +import co.elastic.otel.android.okhttp.internal.plugin.OkHttp3Singletons import com.google.auto.service.AutoService @AutoService(Instrumentation::class) class OkHttpInstrumentation : Instrumentation { override fun install(application: Application, agent: ElasticOtelAgent) { - + OkHttp3Singletons.configure(agent.getOpenTelemetry()) } } \ No newline at end of file diff --git a/instrumentation/okhttp/library/src/main/java/co/elastic/otel/android/okhttp/internal/plugin/OkHttp3Singletons.java b/instrumentation/okhttp/library/src/main/java/co/elastic/otel/android/okhttp/internal/plugin/OkHttp3Singletons.java new file mode 100644 index 00000000..ba867250 --- /dev/null +++ b/instrumentation/okhttp/library/src/main/java/co/elastic/otel/android/okhttp/internal/plugin/OkHttp3Singletons.java @@ -0,0 +1,78 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package co.elastic.otel.android.okhttp.internal.plugin; + +import static io.opentelemetry.instrumentation.api.internal.HttpConstants.KNOWN_METHODS; + +import java.util.Collections; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import io.opentelemetry.instrumentation.api.incubator.semconv.net.PeerServiceAttributesExtractor; +import io.opentelemetry.instrumentation.api.incubator.semconv.net.PeerServiceResolver; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.semconv.http.HttpClientRequestResendCount; +import io.opentelemetry.instrumentation.api.semconv.http.HttpSpanNameExtractor; +import io.opentelemetry.instrumentation.okhttp.v3_0.internal.ConnectionErrorSpanInterceptor; +import io.opentelemetry.instrumentation.okhttp.v3_0.internal.OkHttpAttributesGetter; +import io.opentelemetry.instrumentation.okhttp.v3_0.internal.OkHttpClientInstrumenterBuilderFactory; +import io.opentelemetry.instrumentation.okhttp.v3_0.internal.TracingInterceptor; +import okhttp3.Interceptor; +import okhttp3.Request; +import okhttp3.Response; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public final class OkHttp3Singletons { + private static final Interceptor NOOP_INTERCEPTOR = chain -> chain.proceed(chain.request()); + public static Interceptor CONNECTION_ERROR_INTERCEPTOR = NOOP_INTERCEPTOR; + public static Interceptor TRACING_INTERCEPTOR = NOOP_INTERCEPTOR; + + public static void configure(OpenTelemetry openTelemetry) { + Instrumenter instrumenter = + OkHttpClientInstrumenterBuilderFactory.create(openTelemetry) + .setKnownMethods(KNOWN_METHODS) + .setSpanNameExtractor( + x -> HttpSpanNameExtractor.builder(OkHttpAttributesGetter.INSTANCE) + .build()) + .addAttributesExtractor( + PeerServiceAttributesExtractor.create( + OkHttpAttributesGetter.INSTANCE, + PeerServiceResolver.create(Collections.emptyMap()))) + .setEmitExperimentalHttpClientMetrics(false) + .build(); + CONNECTION_ERROR_INTERCEPTOR = new ConnectionErrorSpanInterceptor(instrumenter); + TRACING_INTERCEPTOR = new TracingInterceptor(instrumenter, openTelemetry.getPropagators()); + } + + public static final Interceptor CALLBACK_CONTEXT_INTERCEPTOR = + chain -> { + Request request = chain.request(); + Context context = + OkHttpCallbackAdviceHelper.tryRecoverPropagatedContextFromCallback(request); + if (context != null) { + try (Scope ignored = context.makeCurrent()) { + return chain.proceed(request); + } + } + + return chain.proceed(request); + }; + + public static final Interceptor RESEND_COUNT_CONTEXT_INTERCEPTOR = + chain -> { + try (Scope ignored = + HttpClientRequestResendCount.initialize(Context.current()).makeCurrent()) { + return chain.proceed(chain.request()); + } + }; + + private OkHttp3Singletons() { + } +} From 3a006f2a207e8ad849763998e8ce3e9ae721854d Mon Sep 17 00:00:00 2001 From: Cesar Munoz <56847527+LikeTheSalad@users.noreply.github.com> Date: Wed, 12 Feb 2025 15:12:59 +0100 Subject: [PATCH 15/24] Substituting local bytebuddy dependencies --- android-test/build.gradle.kts | 4 ++++ .../okhttp/internal/OkHttpClientAdvice.java | 19 ++++++++++++++++--- .../okhttp/internal/OkHttpClientPlugin.java | 19 ++++++++++++++++--- .../callback/OkHttpCallbackAdvice.java | 19 ++++++++++++++++--- .../callback/OkHttpCallbackPlugin.java | 19 ++++++++++++++++--- .../internal/plugin/OkHttp3Singletons.java | 19 ++++++++++++++++--- .../plugin/OkHttpCallbackAdviceHelper.java | 19 ++++++++++++++++--- .../internal/plugin/TracingCallback.java | 19 ++++++++++++++++--- 8 files changed, 116 insertions(+), 21 deletions(-) diff --git a/android-test/build.gradle.kts b/android-test/build.gradle.kts index 575e5793..e27e51db 100644 --- a/android-test/build.gradle.kts +++ b/android-test/build.gradle.kts @@ -23,6 +23,10 @@ subprojects { substitute(module("co.elastic.otel.android.instrumentation:${moduleId}-library")) .using(module("co.elastic.otel.android.${moduleId}:library:$agentVersion")) } + resolutionStrategy.dependencySubstitution { + substitute(module("co.elastic.otel.android.instrumentation:${moduleId}-bytebuddy")) + .using(module("co.elastic.otel.android.${moduleId}:bytebuddy:$agentVersion")) + } } } } \ No newline at end of file diff --git a/instrumentation/okhttp/bytebuddy/src/main/java/co/elastic/otel/android/okhttp/internal/OkHttpClientAdvice.java b/instrumentation/okhttp/bytebuddy/src/main/java/co/elastic/otel/android/okhttp/internal/OkHttpClientAdvice.java index bb876e1a..90cdce63 100644 --- a/instrumentation/okhttp/bytebuddy/src/main/java/co/elastic/otel/android/okhttp/internal/OkHttpClientAdvice.java +++ b/instrumentation/okhttp/bytebuddy/src/main/java/co/elastic/otel/android/okhttp/internal/OkHttpClientAdvice.java @@ -1,8 +1,21 @@ /* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.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.otel.android.okhttp.internal; import net.bytebuddy.asm.Advice; diff --git a/instrumentation/okhttp/bytebuddy/src/main/java/co/elastic/otel/android/okhttp/internal/OkHttpClientPlugin.java b/instrumentation/okhttp/bytebuddy/src/main/java/co/elastic/otel/android/okhttp/internal/OkHttpClientPlugin.java index 52acf79b..fd88f82e 100644 --- a/instrumentation/okhttp/bytebuddy/src/main/java/co/elastic/otel/android/okhttp/internal/OkHttpClientPlugin.java +++ b/instrumentation/okhttp/bytebuddy/src/main/java/co/elastic/otel/android/okhttp/internal/OkHttpClientPlugin.java @@ -1,8 +1,21 @@ /* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.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.otel.android.okhttp.internal; import net.bytebuddy.asm.Advice; diff --git a/instrumentation/okhttp/bytebuddy/src/main/java/co/elastic/otel/android/okhttp/internal/callback/OkHttpCallbackAdvice.java b/instrumentation/okhttp/bytebuddy/src/main/java/co/elastic/otel/android/okhttp/internal/callback/OkHttpCallbackAdvice.java index ca759562..e63dc655 100644 --- a/instrumentation/okhttp/bytebuddy/src/main/java/co/elastic/otel/android/okhttp/internal/callback/OkHttpCallbackAdvice.java +++ b/instrumentation/okhttp/bytebuddy/src/main/java/co/elastic/otel/android/okhttp/internal/callback/OkHttpCallbackAdvice.java @@ -1,8 +1,21 @@ /* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.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.otel.android.okhttp.internal.callback; import net.bytebuddy.asm.Advice; diff --git a/instrumentation/okhttp/bytebuddy/src/main/java/co/elastic/otel/android/okhttp/internal/callback/OkHttpCallbackPlugin.java b/instrumentation/okhttp/bytebuddy/src/main/java/co/elastic/otel/android/okhttp/internal/callback/OkHttpCallbackPlugin.java index 5fcaa275..7faec0e0 100644 --- a/instrumentation/okhttp/bytebuddy/src/main/java/co/elastic/otel/android/okhttp/internal/callback/OkHttpCallbackPlugin.java +++ b/instrumentation/okhttp/bytebuddy/src/main/java/co/elastic/otel/android/okhttp/internal/callback/OkHttpCallbackPlugin.java @@ -1,8 +1,21 @@ /* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.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.otel.android.okhttp.internal.callback; import static net.bytebuddy.matcher.ElementMatchers.named; diff --git a/instrumentation/okhttp/library/src/main/java/co/elastic/otel/android/okhttp/internal/plugin/OkHttp3Singletons.java b/instrumentation/okhttp/library/src/main/java/co/elastic/otel/android/okhttp/internal/plugin/OkHttp3Singletons.java index ba867250..5721c4fb 100644 --- a/instrumentation/okhttp/library/src/main/java/co/elastic/otel/android/okhttp/internal/plugin/OkHttp3Singletons.java +++ b/instrumentation/okhttp/library/src/main/java/co/elastic/otel/android/okhttp/internal/plugin/OkHttp3Singletons.java @@ -1,8 +1,21 @@ /* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.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.otel.android.okhttp.internal.plugin; import static io.opentelemetry.instrumentation.api.internal.HttpConstants.KNOWN_METHODS; diff --git a/instrumentation/okhttp/library/src/main/java/co/elastic/otel/android/okhttp/internal/plugin/OkHttpCallbackAdviceHelper.java b/instrumentation/okhttp/library/src/main/java/co/elastic/otel/android/okhttp/internal/plugin/OkHttpCallbackAdviceHelper.java index de200ed0..1eec0f38 100644 --- a/instrumentation/okhttp/library/src/main/java/co/elastic/otel/android/okhttp/internal/plugin/OkHttpCallbackAdviceHelper.java +++ b/instrumentation/okhttp/library/src/main/java/co/elastic/otel/android/okhttp/internal/plugin/OkHttpCallbackAdviceHelper.java @@ -1,8 +1,21 @@ /* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.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.otel.android.okhttp.internal.plugin; import io.opentelemetry.context.Context; diff --git a/instrumentation/okhttp/library/src/main/java/co/elastic/otel/android/okhttp/internal/plugin/TracingCallback.java b/instrumentation/okhttp/library/src/main/java/co/elastic/otel/android/okhttp/internal/plugin/TracingCallback.java index 9b237255..0bf7a928 100644 --- a/instrumentation/okhttp/library/src/main/java/co/elastic/otel/android/okhttp/internal/plugin/TracingCallback.java +++ b/instrumentation/okhttp/library/src/main/java/co/elastic/otel/android/okhttp/internal/plugin/TracingCallback.java @@ -1,8 +1,21 @@ /* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.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.otel.android.okhttp.internal.plugin; import java.io.IOException; From 72fe4b34e35a8b14968b943e5227a8844bb9305f Mon Sep 17 00:00:00 2001 From: Cesar Munoz <56847527+LikeTheSalad@users.noreply.github.com> Date: Wed, 12 Feb 2025 15:27:59 +0100 Subject: [PATCH 16/24] Moving bytebuddy plugin definitions --- .../src/main/resources/{ => META-INF}/net.bytebuddy/build.plugins | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename instrumentation/okhttp/bytebuddy/src/main/resources/{ => META-INF}/net.bytebuddy/build.plugins (100%) diff --git a/instrumentation/okhttp/bytebuddy/src/main/resources/net.bytebuddy/build.plugins b/instrumentation/okhttp/bytebuddy/src/main/resources/META-INF/net.bytebuddy/build.plugins similarity index 100% rename from instrumentation/okhttp/bytebuddy/src/main/resources/net.bytebuddy/build.plugins rename to instrumentation/okhttp/bytebuddy/src/main/resources/META-INF/net.bytebuddy/build.plugins From 2b5e5d4f6bdec88537ef55de607029de4e035959 Mon Sep 17 00:00:00 2001 From: Cesar Munoz <56847527+LikeTheSalad@users.noreply.github.com> Date: Wed, 12 Feb 2025 15:46:01 +0100 Subject: [PATCH 17/24] Flushing spans for androidTests --- .../co/elastic/otel/android/api/flusher/SpanFlusher.kt | 7 +++++++ .../main/java/co/elastic/otel/android/ElasticApmAgent.kt | 7 ++++++- .../otel/android/internal/api/ManagedElasticOtelAgent.kt | 7 ++++++- .../otel/android/functional/ElasticApmAgentTest.kt | 3 +++ .../co/elastic/otel/android/test/InstrumentationTest.kt | 2 ++ .../java/co/elastic/otel/android/test/rule/AgentRule.kt | 9 +++++++-- 6 files changed, 31 insertions(+), 4 deletions(-) create mode 100644 android-api/src/main/java/co/elastic/otel/android/api/flusher/SpanFlusher.kt diff --git a/android-api/src/main/java/co/elastic/otel/android/api/flusher/SpanFlusher.kt b/android-api/src/main/java/co/elastic/otel/android/api/flusher/SpanFlusher.kt new file mode 100644 index 00000000..210efac8 --- /dev/null +++ b/android-api/src/main/java/co/elastic/otel/android/api/flusher/SpanFlusher.kt @@ -0,0 +1,7 @@ +package co.elastic.otel.android.api.flusher + +import io.opentelemetry.sdk.common.CompletableResultCode + +interface SpanFlusher { + fun flushSpans(): CompletableResultCode +} \ No newline at end of file diff --git a/android-sdk/src/main/java/co/elastic/otel/android/ElasticApmAgent.kt b/android-sdk/src/main/java/co/elastic/otel/android/ElasticApmAgent.kt index 5f12bd8b..65a88f9d 100644 --- a/android-sdk/src/main/java/co/elastic/otel/android/ElasticApmAgent.kt +++ b/android-sdk/src/main/java/co/elastic/otel/android/ElasticApmAgent.kt @@ -22,6 +22,7 @@ import android.app.Application import co.elastic.otel.android.api.ElasticOtelAgent import co.elastic.otel.android.api.flusher.LogRecordFlusher import co.elastic.otel.android.api.flusher.MetricFlusher +import co.elastic.otel.android.api.flusher.SpanFlusher import co.elastic.otel.android.common.internal.logging.Elog import co.elastic.otel.android.exporters.ExporterProvider import co.elastic.otel.android.exporters.configuration.ExportProtocol @@ -60,7 +61,7 @@ class ElasticApmAgent internal constructor( private val apmServerConnectivityManager: ApmServerConnectivityManager, private val centralConfigurationManager: CentralConfigurationManager, private val sampleRateManager: SampleRateManager -) : ElasticOtelAgent, MetricFlusher, LogRecordFlusher { +) : ElasticOtelAgent, MetricFlusher, LogRecordFlusher, SpanFlusher { init { centralConfigurationManager.initialize(delegate.openTelemetry) @@ -79,6 +80,10 @@ class ElasticApmAgent internal constructor( return delegate.flushLogRecords() } + override fun flushSpans(): CompletableResultCode { + return delegate.flushSpans() + } + override fun close() { delegate.close() } diff --git a/android-sdk/src/main/java/co/elastic/otel/android/internal/api/ManagedElasticOtelAgent.kt b/android-sdk/src/main/java/co/elastic/otel/android/internal/api/ManagedElasticOtelAgent.kt index 38012983..5238975e 100644 --- a/android-sdk/src/main/java/co/elastic/otel/android/internal/api/ManagedElasticOtelAgent.kt +++ b/android-sdk/src/main/java/co/elastic/otel/android/internal/api/ManagedElasticOtelAgent.kt @@ -22,6 +22,7 @@ import android.app.Application import co.elastic.otel.android.api.ElasticOtelAgent import co.elastic.otel.android.api.flusher.LogRecordFlusher import co.elastic.otel.android.api.flusher.MetricFlusher +import co.elastic.otel.android.api.flusher.SpanFlusher import co.elastic.otel.android.exporters.ExporterProvider import co.elastic.otel.android.features.session.SessionIdGenerator import co.elastic.otel.android.interceptor.Interceptor @@ -50,7 +51,7 @@ class ManagedElasticOtelAgent private constructor( private val serviceManager: ServiceManager, internal val openTelemetry: ElasticOpenTelemetry, internal val features: ManagedFeatures -) : ElasticOtelAgent, MetricFlusher, LogRecordFlusher { +) : ElasticOtelAgent, MetricFlusher, LogRecordFlusher, SpanFlusher { init { features.elasticClockManager.initialize() @@ -79,6 +80,10 @@ class ManagedElasticOtelAgent private constructor( return openTelemetry.sdk.sdkLoggerProvider.forceFlush() } + override fun flushSpans(): CompletableResultCode { + return openTelemetry.sdk.sdkTracerProvider.forceFlush() + } + internal fun getElasticClockManager(): ElasticClockManager { return features.elasticClockManager } diff --git a/android-sdk/src/test/java/co/elastic/otel/android/functional/ElasticApmAgentTest.kt b/android-sdk/src/test/java/co/elastic/otel/android/functional/ElasticApmAgentTest.kt index 437e2c28..ec1a55fa 100644 --- a/android-sdk/src/test/java/co/elastic/otel/android/functional/ElasticApmAgentTest.kt +++ b/android-sdk/src/test/java/co/elastic/otel/android/functional/ElasticApmAgentTest.kt @@ -118,10 +118,12 @@ class ElasticApmAgentTest { val delegate = mockk() val flushMetricsResult = mockk() val flushLogRecordsResult = mockk() + val flushSpansRecordsResult = mockk() val openTelemetry = mockk() every { delegate.getOpenTelemetry() }.returns(openTelemetry) every { delegate.flushMetrics() }.returns(flushMetricsResult) every { delegate.flushLogRecords() }.returns(flushLogRecordsResult) + every { delegate.flushSpans() }.returns(flushSpansRecordsResult) every { delegate.openTelemetry }.returns(mockk()) every { delegate.close() } just Runs @@ -135,6 +137,7 @@ class ElasticApmAgentTest { assertThat(agent.getOpenTelemetry()).isEqualTo(openTelemetry) assertThat(agent.flushMetrics()).isEqualTo(flushMetricsResult) assertThat(agent.flushLogRecords()).isEqualTo(flushLogRecordsResult) + assertThat(agent.flushSpans()).isEqualTo(flushSpansRecordsResult) agent.close() verify { delegate.close() } } diff --git a/android-test/instrumentation/okhttp/src/androidTest/java/co/elastic/otel/android/test/InstrumentationTest.kt b/android-test/instrumentation/okhttp/src/androidTest/java/co/elastic/otel/android/test/InstrumentationTest.kt index f7312647..befdac32 100644 --- a/android-test/instrumentation/okhttp/src/androidTest/java/co/elastic/otel/android/test/InstrumentationTest.kt +++ b/android-test/instrumentation/okhttp/src/androidTest/java/co/elastic/otel/android/test/InstrumentationTest.kt @@ -45,6 +45,8 @@ class InstrumentationTest { fun verifyOkHttpSyncCallSpan() { executeSyncHttpCall("GET", 200, "{}") + agentRule.flushSpans().join(5, TimeUnit.SECONDS) + assertThat(agentRule.getFinishedSpans()).hasSize(1) assertThat(agentRule.getFinishedSpans().first()).hasName("GET") } diff --git a/test-tools/agent-rule/src/main/java/co/elastic/otel/android/test/rule/AgentRule.kt b/test-tools/agent-rule/src/main/java/co/elastic/otel/android/test/rule/AgentRule.kt index a814bc1c..32b3b247 100644 --- a/test-tools/agent-rule/src/main/java/co/elastic/otel/android/test/rule/AgentRule.kt +++ b/test-tools/agent-rule/src/main/java/co/elastic/otel/android/test/rule/AgentRule.kt @@ -9,6 +9,7 @@ import co.elastic.otel.android.internal.services.ServiceManager import co.elastic.otel.android.internal.time.SystemTimeProvider import co.elastic.otel.android.internal.time.ntp.SntpClient import co.elastic.otel.android.test.common.ElasticAttributes.DEFAULT_SESSION_ID +import io.opentelemetry.sdk.common.CompletableResultCode import io.opentelemetry.sdk.logs.data.LogRecordData import io.opentelemetry.sdk.logs.export.LogRecordExporter import io.opentelemetry.sdk.metrics.data.MetricData @@ -24,7 +25,7 @@ import org.junit.runners.model.Statement abstract class AgentRule : TestRule { @Volatile - private var agent: ElasticOtelAgent? = null + private var agent: ManagedElasticOtelAgent? = null @Volatile private var inMemoryExporters: InMemoryExporterProvider? = null @@ -62,7 +63,11 @@ abstract class AgentRule : TestRule { return inMemoryExporters!!.getFinishedSpans() } - private fun createAgent(application: Application): ElasticOtelAgent { + fun flushSpans(): CompletableResultCode { + return agent!!.flushSpans() + } + + private fun createAgent(application: Application): ManagedElasticOtelAgent { val serviceManager = ServiceManager.create(application) val features = ManagedElasticOtelAgent.ManagedFeatures.Builder(application) .setSntpClient(LocalSntpClient()) From 6a419f6e677cff5468449caccaa11f65b739dbed Mon Sep 17 00:00:00 2001 From: Cesar Munoz <56847527+LikeTheSalad@users.noreply.github.com> Date: Wed, 12 Feb 2025 16:09:10 +0100 Subject: [PATCH 18/24] Validating successful okhttp sync requests --- ...lastic.instrumentation-test-app.gradle.kts | 1 + .../otel/android/test/InstrumentationTest.kt | 19 ++++++++++++++++--- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/android-test/buildSrc/src/main/kotlin/elastic.instrumentation-test-app.gradle.kts b/android-test/buildSrc/src/main/kotlin/elastic.instrumentation-test-app.gradle.kts index 43b089bd..fab02e0c 100644 --- a/android-test/buildSrc/src/main/kotlin/elastic.instrumentation-test-app.gradle.kts +++ b/android-test/buildSrc/src/main/kotlin/elastic.instrumentation-test-app.gradle.kts @@ -45,5 +45,6 @@ dependencies { testImplementation(libs.findBundle("mocking").get()) testImplementation(libs.findLibrary("junit4").get()) testImplementation(libs.findLibrary("assertj").get()) + androidTestImplementation("co.elastic.otel.android:test-common") androidTestImplementation("co.elastic.otel.android:androidtest-agent-rule") } \ No newline at end of file diff --git a/android-test/instrumentation/okhttp/src/androidTest/java/co/elastic/otel/android/test/InstrumentationTest.kt b/android-test/instrumentation/okhttp/src/androidTest/java/co/elastic/otel/android/test/InstrumentationTest.kt index befdac32..5138dcb0 100644 --- a/android-test/instrumentation/okhttp/src/androidTest/java/co/elastic/otel/android/test/InstrumentationTest.kt +++ b/android-test/instrumentation/okhttp/src/androidTest/java/co/elastic/otel/android/test/InstrumentationTest.kt @@ -1,7 +1,10 @@ package co.elastic.otel.android.test import co.elastic.otel.android.test.rule.AndroidTestAgentRule +import io.opentelemetry.api.common.AttributeKey +import io.opentelemetry.api.trace.SpanKind import io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat +import io.opentelemetry.sdk.trace.data.StatusData import java.io.IOException import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit @@ -10,6 +13,7 @@ import java.util.function.Consumer import junit.framework.TestCase.fail import okhttp3.Call import okhttp3.Callback +import okhttp3.HttpUrl import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.Response @@ -43,12 +47,20 @@ class InstrumentationTest { @Test fun verifyOkHttpSyncCallSpan() { - executeSyncHttpCall("GET", 200, "{}") + val url = webServer.url("/") + executeSyncHttpCall("GET", 200, "{}", url) agentRule.flushSpans().join(5, TimeUnit.SECONDS) assertThat(agentRule.getFinishedSpans()).hasSize(1) assertThat(agentRule.getFinishedSpans().first()).hasName("GET") + .hasKind(SpanKind.CLIENT) + .hasStatus(StatusData.ok()) + .hasAttribute(AttributeKey.stringKey("url.full"), url.toString()) + .hasAttribute(AttributeKey.stringKey("http.request.method"), "GET") + .hasAttribute(AttributeKey.longKey("http.response.status_code"), 200) + .hasAttribute(AttributeKey.stringKey("server.address"), "localhost") + .hasAttribute(AttributeKey.longKey("server.port"), webServer.port.toLong()) } private fun executeSuccessfulHttpCall(responseCode: Int) { @@ -112,14 +124,15 @@ class InstrumentationTest { private fun executeSyncHttpCall( method: String, responseCode: Int, - responseBody: String + responseBody: String, + url: HttpUrl = webServer.url("/") ): Response { val mockResponse = MockResponse().setResponseCode(responseCode).setBody(responseBody) webServer.enqueue(mockResponse) val request = Request.Builder() .method(method, null) - .url(webServer.url("/")).build() + .url(url).build() return clientBuilder.build().newCall(request).execute() } From 7a340f7dff166cd149938d2f11082bf4f9473f9b Mon Sep 17 00:00:00 2001 From: Cesar Munoz <56847527+LikeTheSalad@users.noreply.github.com> Date: Wed, 12 Feb 2025 16:21:09 +0100 Subject: [PATCH 19/24] Adding tests --- .../otel/android/test/InstrumentationTest.kt | 59 +++++++++++++------ 1 file changed, 40 insertions(+), 19 deletions(-) diff --git a/android-test/instrumentation/okhttp/src/androidTest/java/co/elastic/otel/android/test/InstrumentationTest.kt b/android-test/instrumentation/okhttp/src/androidTest/java/co/elastic/otel/android/test/InstrumentationTest.kt index 5138dcb0..da4a3567 100644 --- a/android-test/instrumentation/okhttp/src/androidTest/java/co/elastic/otel/android/test/InstrumentationTest.kt +++ b/android-test/instrumentation/okhttp/src/androidTest/java/co/elastic/otel/android/test/InstrumentationTest.kt @@ -63,25 +63,57 @@ class InstrumentationTest { .hasAttribute(AttributeKey.longKey("server.port"), webServer.port.toLong()) } - private fun executeSuccessfulHttpCall(responseCode: Int) { - executeSuccessfulHttpCall(responseCode, "{}", emptyMap()) + @Test + fun verifyOkHttpAsyncCallSpan() { + val url = webServer.url("/") + executeAsyncSuccessfulHttpCall("GET", 200, "{}", url) + + agentRule.flushSpans().join(5, TimeUnit.SECONDS) + + assertThat(agentRule.getFinishedSpans()).hasSize(1) + assertThat(agentRule.getFinishedSpans().first()).hasName("GET") + .hasKind(SpanKind.CLIENT) + .hasStatus(StatusData.ok()) + .hasAttribute(AttributeKey.stringKey("url.full"), url.toString()) + .hasAttribute(AttributeKey.stringKey("http.request.method"), "GET") + .hasAttribute(AttributeKey.longKey("http.response.status_code"), 200) + .hasAttribute(AttributeKey.stringKey("server.address"), "localhost") + .hasAttribute(AttributeKey.longKey("server.port"), webServer.port.toLong()) } - private fun executeSuccessfulHttpCall( + @Test + fun verifyOkHttpAsyncCallSpan_withErrorCode() { + val url = webServer.url("/") + executeAsyncSuccessfulHttpCall("GET", 500, "{}", url) + + agentRule.flushSpans().join(5, TimeUnit.SECONDS) + + assertThat(agentRule.getFinishedSpans()).hasSize(1) + assertThat(agentRule.getFinishedSpans().first()).hasName("GET") + .hasKind(SpanKind.CLIENT) + .hasStatus(StatusData.ok()) + .hasAttribute(AttributeKey.stringKey("url.full"), url.toString()) + .hasAttribute(AttributeKey.stringKey("http.request.method"), "GET") + .hasAttribute(AttributeKey.longKey("http.response.status_code"), 500) + .hasAttribute(AttributeKey.stringKey("server.address"), "localhost") + .hasAttribute(AttributeKey.longKey("server.port"), webServer.port.toLong()) + } + + private fun executeAsyncSuccessfulHttpCall( + method: String, responseCode: Int, responseBody: String, - responseHeaders: Map + url: HttpUrl = webServer.url("/") ) { val mockResponse = MockResponse().setResponseCode(responseCode).setBody(responseBody) - responseHeaders.forEach { (name: String, value: String) -> - mockResponse.addHeader(name, value) - } webServer.enqueue(mockResponse) val responseStr = AtomicReference("") try { executeAsyncHttpCall( - Request.Builder().url(webServer.url("/")).build() + Request.Builder() + .method(method, null) + .url(url).build() ) { response -> try { responseStr.set(response.body!!.string()) @@ -95,17 +127,6 @@ class InstrumentationTest { assertThat(responseStr.get()).isEqualTo(responseBody) } - private fun executeFailedHttpCall() { - val url = webServer.url("/") - webServer.shutdown() - - try { - executeAsyncHttpCall(Request.Builder().url(url).build()) { } - } catch (e: InterruptedException) { - throw RuntimeException(e) - } - } - private fun executeAsyncHttpCall(request: Request, responseConsumer: Consumer) { val latch = CountDownLatch(1) clientBuilder.build().newCall(request).enqueue(object : Callback { From 03660a4060c4e0c2276b43b4ffff65364f18fce5 Mon Sep 17 00:00:00 2001 From: Cesar Munoz <56847527+LikeTheSalad@users.noreply.github.com> Date: Wed, 12 Feb 2025 16:24:19 +0100 Subject: [PATCH 20/24] Updating notice files --- NOTICE | 2 ++ .../bytebuddy/metadata/notice.properties | 2 +- .../src/main/resources/META-INF/NOTICE | 23 +++++++++++++++++-- .../okhttp/library/metadata/notice.properties | 2 +- .../src/main/resources/META-INF/NOTICE | 3 +-- 5 files changed, 26 insertions(+), 6 deletions(-) diff --git a/NOTICE b/NOTICE index 2b7cf531..8c10a2ce 100644 --- a/NOTICE +++ b/NOTICE @@ -10,6 +10,8 @@ This product includes software licensed under the 'Apache License Version 2.0' l - Core (https://developer.android.com/jetpack/androidx/releases/core#1.15.0) - Kotlin Stdlib (https://kotlinlang.org/) - Lifecycle Process (https://developer.android.com/jetpack/androidx/releases/lifecycle#2.8.7) + - okhttp (https://square.github.io/okhttp/) + - OpenTelemetry Instrumentation for Java (https://github.com/open-telemetry/opentelemetry-java-instrumentation) - 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/instrumentation/okhttp/bytebuddy/metadata/notice.properties b/instrumentation/okhttp/bytebuddy/metadata/notice.properties index c9161b6d..3ddbf3ea 100644 --- a/instrumentation/okhttp/bytebuddy/metadata/notice.properties +++ b/instrumentation/okhttp/bytebuddy/metadata/notice.properties @@ -1 +1 @@ -dependencies.hash=C98ECB36B923E54714316C7764978779 \ No newline at end of file +dependencies.hash=4A276CF60AB76D6B3CF159088DAEF679 \ No newline at end of file diff --git a/instrumentation/okhttp/bytebuddy/src/main/resources/META-INF/NOTICE b/instrumentation/okhttp/bytebuddy/src/main/resources/META-INF/NOTICE index ba699da2..ee0b7cb6 100644 --- a/instrumentation/okhttp/bytebuddy/src/main/resources/META-INF/NOTICE +++ b/instrumentation/okhttp/bytebuddy/src/main/resources/META-INF/NOTICE @@ -5,9 +5,28 @@ Copyright 2018-2022 Elasticsearch B.V. This product includes software licensed under the 'Apache License Version 2.0' license from the following sources: + - Byte Buddy (without dependencies) - Kotlin Stdlib (https://kotlinlang.org/) - - OpenTelemetry Java (https://github.com/open-telemetry/opentelemetry-java) - - OpenTelemetry Semantic Conventions Java (https://github.com/open-telemetry/semantic-conventions-java) + - okhttp (https://square.github.io/okhttp/) + +############################################################################### + +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. + ############################################################################### diff --git a/instrumentation/okhttp/library/metadata/notice.properties b/instrumentation/okhttp/library/metadata/notice.properties index c9161b6d..033aab13 100644 --- a/instrumentation/okhttp/library/metadata/notice.properties +++ b/instrumentation/okhttp/library/metadata/notice.properties @@ -1 +1 @@ -dependencies.hash=C98ECB36B923E54714316C7764978779 \ No newline at end of file +dependencies.hash=E112661E7F7F6CAC665D704580DAFA77 \ No newline at end of file diff --git a/instrumentation/okhttp/library/src/main/resources/META-INF/NOTICE b/instrumentation/okhttp/library/src/main/resources/META-INF/NOTICE index ba699da2..f795135c 100644 --- a/instrumentation/okhttp/library/src/main/resources/META-INF/NOTICE +++ b/instrumentation/okhttp/library/src/main/resources/META-INF/NOTICE @@ -6,8 +6,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 (https://kotlinlang.org/) - - OpenTelemetry Java (https://github.com/open-telemetry/opentelemetry-java) - - OpenTelemetry Semantic Conventions Java (https://github.com/open-telemetry/semantic-conventions-java) + - OpenTelemetry Instrumentation for Java (https://github.com/open-telemetry/opentelemetry-java-instrumentation) ############################################################################### From 890c7c6a3086857ef0ba01cd13bc59714f8ee578 Mon Sep 17 00:00:00 2001 From: Cesar Munoz <56847527+LikeTheSalad@users.noreply.github.com> Date: Wed, 12 Feb 2025 18:02:11 +0100 Subject: [PATCH 21/24] Adding build variant listener --- .../otel/android/plugin/ElasticAgentPlugin.kt | 22 ++++++++++++++++++- .../extensions/BytecodeInstrumentation.kt | 7 ++++++ .../plugin/extensions/ElasticApmExtension.kt | 13 +++++++++++ .../plugin/internal/BuildVariantListener.kt | 5 +++++ .../plugin/internal/InstrumentationPlugin.kt | 4 ++-- 5 files changed, 48 insertions(+), 3 deletions(-) create mode 100644 android-plugin/src/main/java/co/elastic/otel/android/plugin/extensions/BytecodeInstrumentation.kt create mode 100644 android-plugin/src/main/java/co/elastic/otel/android/plugin/extensions/ElasticApmExtension.kt create mode 100644 android-plugin/src/main/java/co/elastic/otel/android/plugin/internal/BuildVariantListener.kt diff --git a/android-plugin/src/main/java/co/elastic/otel/android/plugin/ElasticAgentPlugin.kt b/android-plugin/src/main/java/co/elastic/otel/android/plugin/ElasticAgentPlugin.kt index 105cf948..b371de9d 100644 --- a/android-plugin/src/main/java/co/elastic/otel/android/plugin/ElasticAgentPlugin.kt +++ b/android-plugin/src/main/java/co/elastic/otel/android/plugin/ElasticAgentPlugin.kt @@ -20,19 +20,39 @@ package co.elastic.otel.android.plugin import co.elastic.otel.android.common.internal.logging.Elog import co.elastic.otel.android.generated.BuildConfig +import co.elastic.otel.android.plugin.extensions.ElasticApmExtension +import co.elastic.otel.android.plugin.internal.BuildVariantListener import co.elastic.otel.android.plugin.internal.logging.GradleLoggerFactory +import com.android.build.api.variant.ApplicationAndroidComponentsExtension import net.bytebuddy.build.gradle.android.ByteBuddyAndroidPlugin import org.gradle.api.Plugin import org.gradle.api.Project -internal class ElasticAgentPlugin : Plugin { +class ElasticAgentPlugin : Plugin { private lateinit var project: Project + private val buildVariantListeners = mutableSetOf() override fun apply(target: Project) { this.project = target Elog.init(GradleLoggerFactory()) addByteBuddyPlugin() addSdkDependency() + val extension = project.extensions.create("elasticAgent", ElasticApmExtension::class.java) + val androidComponents = + project.extensions.getByType(ApplicationAndroidComponentsExtension::class.java) + androidComponents.finalizeDsl { androidExtension -> + val disableForBuildTypes = extension.bytecodeInstrumentation.disableForBuildTypes.get() + androidExtension.buildTypes.forEach { buildType -> + val name = buildType.name + if (name !in disableForBuildTypes) { + buildVariantListeners.forEach { listener -> listener.onBuildVariant(name) } + } + } + } + } + + fun addBuildVariantListener(listener: BuildVariantListener) { + buildVariantListeners.add(listener) } private fun addByteBuddyPlugin() { diff --git a/android-plugin/src/main/java/co/elastic/otel/android/plugin/extensions/BytecodeInstrumentation.kt b/android-plugin/src/main/java/co/elastic/otel/android/plugin/extensions/BytecodeInstrumentation.kt new file mode 100644 index 00000000..eb2a1daa --- /dev/null +++ b/android-plugin/src/main/java/co/elastic/otel/android/plugin/extensions/BytecodeInstrumentation.kt @@ -0,0 +1,7 @@ +package co.elastic.otel.android.plugin.extensions + +import org.gradle.api.provider.ListProperty + +interface BytecodeInstrumentation { + val disableForBuildTypes: ListProperty +} diff --git a/android-plugin/src/main/java/co/elastic/otel/android/plugin/extensions/ElasticApmExtension.kt b/android-plugin/src/main/java/co/elastic/otel/android/plugin/extensions/ElasticApmExtension.kt new file mode 100644 index 00000000..f65f9407 --- /dev/null +++ b/android-plugin/src/main/java/co/elastic/otel/android/plugin/extensions/ElasticApmExtension.kt @@ -0,0 +1,13 @@ +package co.elastic.otel.android.plugin.extensions + +import javax.inject.Inject +import org.gradle.api.Action +import org.gradle.api.model.ObjectFactory + +abstract class ElasticApmExtension @Inject constructor(objects: ObjectFactory) { + val bytecodeInstrumentation = objects.newInstance(BytecodeInstrumentation::class.java) + + fun bytecodeInstrumentation(action: Action) { + action.execute(bytecodeInstrumentation) + } +} \ No newline at end of file diff --git a/android-plugin/src/main/java/co/elastic/otel/android/plugin/internal/BuildVariantListener.kt b/android-plugin/src/main/java/co/elastic/otel/android/plugin/internal/BuildVariantListener.kt new file mode 100644 index 00000000..cd78b163 --- /dev/null +++ b/android-plugin/src/main/java/co/elastic/otel/android/plugin/internal/BuildVariantListener.kt @@ -0,0 +1,5 @@ +package co.elastic.otel.android.plugin.internal + +interface BuildVariantListener { + fun onBuildVariant(name: String) +} \ No newline at end of file diff --git a/android-plugin/src/main/java/co/elastic/otel/android/plugin/internal/InstrumentationPlugin.kt b/android-plugin/src/main/java/co/elastic/otel/android/plugin/internal/InstrumentationPlugin.kt index 1f45ecdc..bd507786 100644 --- a/android-plugin/src/main/java/co/elastic/otel/android/plugin/internal/InstrumentationPlugin.kt +++ b/android-plugin/src/main/java/co/elastic/otel/android/plugin/internal/InstrumentationPlugin.kt @@ -12,8 +12,8 @@ abstract class InstrumentationPlugin : Plugin { apply(ElasticAgentPlugin::class.java) } } - onApply(target) + onApply(target, target.plugins.getPlugin(ElasticAgentPlugin::class.java)) } - abstract fun onApply(target: Project) + abstract fun onApply(target: Project, agentPlugin: ElasticAgentPlugin) } \ No newline at end of file From 6c004cde7633f6226e1e5bd9adaa53e1c095d0a1 Mon Sep 17 00:00:00 2001 From: Cesar Munoz <56847527+LikeTheSalad@users.noreply.github.com> Date: Wed, 12 Feb 2025 18:03:40 +0100 Subject: [PATCH 22/24] Updating instrumentation plugins --- .../elastic/otel/android/crash/CrashInstrumentationPlugin.kt | 3 ++- .../otel/android/launchtime/LaunchTimeInstrumentationPlugin.kt | 3 ++- .../elastic/otel/android/okhttp/OkHttpInstrumentationPlugin.kt | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/instrumentation/crash/plugin/src/main/java/co/elastic/otel/android/crash/CrashInstrumentationPlugin.kt b/instrumentation/crash/plugin/src/main/java/co/elastic/otel/android/crash/CrashInstrumentationPlugin.kt index 2cdb953c..9fd93c47 100644 --- a/instrumentation/crash/plugin/src/main/java/co/elastic/otel/android/crash/CrashInstrumentationPlugin.kt +++ b/instrumentation/crash/plugin/src/main/java/co/elastic/otel/android/crash/CrashInstrumentationPlugin.kt @@ -1,12 +1,13 @@ package co.elastic.otel.android.crash import co.elastic.otel.android.instrumentation.generated.BuildConfig +import co.elastic.otel.android.plugin.ElasticAgentPlugin import co.elastic.otel.android.plugin.internal.InstrumentationPlugin import org.gradle.api.Project class CrashInstrumentationPlugin : InstrumentationPlugin() { - override fun onApply(target: Project) { + override fun onApply(target: Project, agentPlugin: ElasticAgentPlugin) { target.dependencies.add( "implementation", target.dependencies.create(BuildConfig.LIBRARY_URI) diff --git a/instrumentation/launchtime/plugin/src/main/java/co/elastic/otel/android/launchtime/LaunchTimeInstrumentationPlugin.kt b/instrumentation/launchtime/plugin/src/main/java/co/elastic/otel/android/launchtime/LaunchTimeInstrumentationPlugin.kt index de5f26b5..4997ed7c 100644 --- a/instrumentation/launchtime/plugin/src/main/java/co/elastic/otel/android/launchtime/LaunchTimeInstrumentationPlugin.kt +++ b/instrumentation/launchtime/plugin/src/main/java/co/elastic/otel/android/launchtime/LaunchTimeInstrumentationPlugin.kt @@ -1,12 +1,13 @@ package co.elastic.otel.android.launchtime import co.elastic.otel.android.instrumentation.generated.BuildConfig +import co.elastic.otel.android.plugin.ElasticAgentPlugin import co.elastic.otel.android.plugin.internal.InstrumentationPlugin import org.gradle.api.Project class LaunchTimeInstrumentationPlugin : InstrumentationPlugin() { - override fun onApply(target: Project) { + override fun onApply(target: Project, agentPlugin: ElasticAgentPlugin) { target.dependencies.add( "implementation", target.dependencies.create(BuildConfig.LIBRARY_URI) diff --git a/instrumentation/okhttp/plugin/src/main/java/co/elastic/otel/android/okhttp/OkHttpInstrumentationPlugin.kt b/instrumentation/okhttp/plugin/src/main/java/co/elastic/otel/android/okhttp/OkHttpInstrumentationPlugin.kt index c4512b11..d5472ee0 100644 --- a/instrumentation/okhttp/plugin/src/main/java/co/elastic/otel/android/okhttp/OkHttpInstrumentationPlugin.kt +++ b/instrumentation/okhttp/plugin/src/main/java/co/elastic/otel/android/okhttp/OkHttpInstrumentationPlugin.kt @@ -1,12 +1,13 @@ package co.elastic.otel.android.okhttp import co.elastic.otel.android.instrumentation.generated.BuildConfig +import co.elastic.otel.android.plugin.ElasticAgentPlugin import co.elastic.otel.android.plugin.internal.InstrumentationPlugin import org.gradle.api.Project class OkHttpInstrumentationPlugin : InstrumentationPlugin() { - override fun onApply(target: Project) { + override fun onApply(target: Project, agentPlugin: ElasticAgentPlugin) { target.dependencies.add( "implementation", target.dependencies.create(BuildConfig.LIBRARY_URI) From 902fd94d625d1543e449afe26cbd524babcd2195 Mon Sep 17 00:00:00 2001 From: Cesar Munoz <56847527+LikeTheSalad@users.noreply.github.com> Date: Wed, 12 Feb 2025 18:16:04 +0100 Subject: [PATCH 23/24] Attaching bytebuddy plugins using the ByteBuddyDependencyAttacher --- .../internal/ByteBuddyDependencyAttacher.kt | 15 +++++++++++++++ .../otel/android/test/InstrumentationTest.kt | 1 - .../android/okhttp/OkHttpInstrumentationPlugin.kt | 9 ++++++--- 3 files changed, 21 insertions(+), 4 deletions(-) create mode 100644 android-plugin/src/main/java/co/elastic/otel/android/plugin/internal/ByteBuddyDependencyAttacher.kt diff --git a/android-plugin/src/main/java/co/elastic/otel/android/plugin/internal/ByteBuddyDependencyAttacher.kt b/android-plugin/src/main/java/co/elastic/otel/android/plugin/internal/ByteBuddyDependencyAttacher.kt new file mode 100644 index 00000000..4ebb15ca --- /dev/null +++ b/android-plugin/src/main/java/co/elastic/otel/android/plugin/internal/ByteBuddyDependencyAttacher.kt @@ -0,0 +1,15 @@ +package co.elastic.otel.android.plugin.internal + +import org.gradle.api.Project + +class ByteBuddyDependencyAttacher( + private val project: Project, + private val dependencyUri: String +) : BuildVariantListener { + + override fun onBuildVariant(name: String) { + project.configurations.maybeCreate("${name}ByteBuddy").dependencies.add( + project.dependencies.create(dependencyUri) + ) + } +} \ No newline at end of file diff --git a/android-test/instrumentation/okhttp/src/androidTest/java/co/elastic/otel/android/test/InstrumentationTest.kt b/android-test/instrumentation/okhttp/src/androidTest/java/co/elastic/otel/android/test/InstrumentationTest.kt index da4a3567..20bb88c4 100644 --- a/android-test/instrumentation/okhttp/src/androidTest/java/co/elastic/otel/android/test/InstrumentationTest.kt +++ b/android-test/instrumentation/okhttp/src/androidTest/java/co/elastic/otel/android/test/InstrumentationTest.kt @@ -24,7 +24,6 @@ import org.junit.Before import org.junit.Rule import org.junit.Test - class InstrumentationTest { @get:Rule diff --git a/instrumentation/okhttp/plugin/src/main/java/co/elastic/otel/android/okhttp/OkHttpInstrumentationPlugin.kt b/instrumentation/okhttp/plugin/src/main/java/co/elastic/otel/android/okhttp/OkHttpInstrumentationPlugin.kt index d5472ee0..aaec5175 100644 --- a/instrumentation/okhttp/plugin/src/main/java/co/elastic/otel/android/okhttp/OkHttpInstrumentationPlugin.kt +++ b/instrumentation/okhttp/plugin/src/main/java/co/elastic/otel/android/okhttp/OkHttpInstrumentationPlugin.kt @@ -2,6 +2,7 @@ package co.elastic.otel.android.okhttp import co.elastic.otel.android.instrumentation.generated.BuildConfig import co.elastic.otel.android.plugin.ElasticAgentPlugin +import co.elastic.otel.android.plugin.internal.ByteBuddyDependencyAttacher import co.elastic.otel.android.plugin.internal.InstrumentationPlugin import org.gradle.api.Project @@ -12,9 +13,11 @@ class OkHttpInstrumentationPlugin : InstrumentationPlugin() { "implementation", target.dependencies.create(BuildConfig.LIBRARY_URI) ) - target.dependencies.add( - "byteBuddy", - target.dependencies.create(BuildConfig.BYTEBUDDY_PLUGIN) + agentPlugin.addBuildVariantListener( + ByteBuddyDependencyAttacher( + target, + BuildConfig.BYTEBUDDY_PLUGIN + ) ) } } \ No newline at end of file From 050577aff117f0a8d993a0807438eb28a5246210 Mon Sep 17 00:00:00 2001 From: Cesar Munoz <56847527+LikeTheSalad@users.noreply.github.com> Date: Thu, 13 Feb 2025 09:27:38 +0100 Subject: [PATCH 24/24] Updating tests --- .../otel/android/internal/time/ntp/UdpClientTest.kt | 9 ++++----- .../co/elastic/otel/android/testutils/TestUdpServer.kt | 8 ++++++-- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/android-sdk/src/test/java/co/elastic/otel/android/internal/time/ntp/UdpClientTest.kt b/android-sdk/src/test/java/co/elastic/otel/android/internal/time/ntp/UdpClientTest.kt index 1b00f062..e5dc60e7 100644 --- a/android-sdk/src/test/java/co/elastic/otel/android/internal/time/ntp/UdpClientTest.kt +++ b/android-sdk/src/test/java/co/elastic/otel/android/internal/time/ntp/UdpClientTest.kt @@ -37,14 +37,13 @@ class UdpClientTest { companion object { private const val SERVER_HOST = "localhost" - private const val SERVER_PORT = 4447 } @BeforeEach fun setUp() { - server = TestUdpServer(SERVER_PORT) + server = TestUdpServer() server.start() - client = UdpClient(SERVER_HOST, SERVER_PORT, 256) + client = UdpClient(SERVER_HOST, server.getPort(), 256) } @AfterEach @@ -99,7 +98,7 @@ class UdpClientTest { @Test fun `Server port is not reachable`() { - client = UdpClient(SERVER_HOST, SERVER_PORT + 1, 256) + client = UdpClient(SERVER_HOST, server.getPort() + 1, 256) assertThrows { client.send("Example".toByteArray(), Duration.ofSeconds(1)) @@ -117,7 +116,7 @@ class UdpClientTest { @Test fun `Server host not found`() { - client = UdpClient("nonexistent", SERVER_PORT, 256) + client = UdpClient("nonexistent", 1, 256) assertThrows { client.send("Example".toByteArray()) } diff --git a/android-sdk/src/test/java/co/elastic/otel/android/testutils/TestUdpServer.kt b/android-sdk/src/test/java/co/elastic/otel/android/testutils/TestUdpServer.kt index 51e0a83f..2972f884 100644 --- a/android-sdk/src/test/java/co/elastic/otel/android/testutils/TestUdpServer.kt +++ b/android-sdk/src/test/java/co/elastic/otel/android/testutils/TestUdpServer.kt @@ -22,9 +22,9 @@ import java.net.DatagramPacket import java.net.DatagramSocket import java.net.SocketException -class TestUdpServer(port: Int) : Thread() { +class TestUdpServer : Thread() { private val buf = ByteArray(256) - val socket = DatagramSocket(port) + val socket = DatagramSocket() @Volatile var responseHandler: (DatagramPacket) -> Unit = { clientPacket -> @@ -53,4 +53,8 @@ class TestUdpServer(port: Int) : Thread() { fun close() { socket.close() } + + fun getPort(): Int { + return socket.localPort + } }