diff --git a/.circleci/config.yml b/.circleci/config.yml index d47b11370a6a..cee365552a11 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -9,11 +9,11 @@ defaults: &defaults JAVA_HOME: /usr/local/openjdk-11 cache_keys: &cache_keys - # Reset the cache approx every release keys: - - trace-java-{{ checksum "trace-java.gradle" }}-{{ .Branch }}-{{ .Revision }} - - trace-java-{{ checksum "trace-java.gradle" }}-{{ .Branch }} - - trace-java-{{ checksum "trace-java.gradle" }} + # Rev the version when the cache gets too big + - trace-java-v1-{{ .Branch }}-{{ .Revision }} + - trace-java-v1-{{ .Branch }} + # - dd-trace-java-v1- jobs: build: @@ -27,7 +27,7 @@ jobs: - run: name: Build Project - command: GRADLE_OPTS="-Dorg.gradle.jvmargs='-Xmx1G -Xms64M' -Dota.forkedMaxHeapSize=1G -Dota.forkedMinHeapSize=64M" ./gradlew clean compileTestGroovy compileLatestDepTestGroovy compileTestScala compileLatestDepTestScala compileTestJava compileLatestDepTestJava :opentelemetry-auto:shadowJar :auto-exporters:opentelemetry-auto-exporters-jaeger:shadowJar :auto-exporters:opentelemetry-auto-exporters-otlp:shadowJar :auto-exporters:opentelemetry-auto-exporters-logging:shadowJar --build-cache --parallel --stacktrace --no-daemon --max-workers=8 + command: GRADLE_OPTS="-Dorg.gradle.jvmargs='-Xmx1G -Xms64M' -Dota.forkedMaxHeapSize=1G -Dota.forkedMinHeapSize=64M" ./gradlew clean compileTestGroovy compileLatestDepTestGroovy compileTestScala compileLatestDepTestScala compileTestJava compileLatestDepTestJava :opentelemetry-auto:shadowJar :auto-exporters:opentelemetry-auto-exporters-jaeger:shadowJar :auto-exporters:opentelemetry-auto-exporters-otlp:shadowJar :auto-exporters:opentelemetry-auto-exporters-logging:shadowJar :auto-exporters:opentelemetry-auto-exporters-zipkin:shadowJar --build-cache --parallel --stacktrace --no-daemon --max-workers=8 - run: name: Collect Libs @@ -44,7 +44,7 @@ jobs: - workspace - save_cache: - key: trace-java-{{ checksum "trace-java.gradle" }}-{{ .Branch }}-{{ .Revision }} + key: trace-java-v1-{{ .Branch }}-{{ .Revision }} paths: ~/.gradle @@ -114,16 +114,6 @@ jobs: environment: - TEST_TASK: testJavaZULU8 - test_9: - <<: *default_test_job - environment: - - TEST_TASK: testJava9 - - test_10: - <<: *default_test_job - environment: - - TEST_TASK: testJava10 - test_11: <<: *default_test_job environment: @@ -188,19 +178,13 @@ jobs: steps: - checkout - - restore_cache: - # Reset the cache approx every release - keys: - - trace-java-muzzle-{{ checksum "trace-java.gradle" }} + # We are not running with a cache here because it gets very big and ends up taking more time + # restoring/saving than the actual increase in time it takes just downloading the artifacts each time. - run: name: Verify Muzzle command: SKIP_BUILDSCAN="true" GRADLE_OPTS="-Dorg.gradle.jvmargs='-Xmx4G -Xms64M' -Dota.forkedMaxHeapSize=4G -Dota.forkedMinHeapSize=64M" ./gradlew muzzle --parallel --stacktrace --no-daemon --max-workers=16 - - save_cache: - key: trace-java-muzzle-{{ checksum "trace-java.gradle" }} - paths: ~/.gradle - publish: &publish <<: *defaults steps: @@ -265,18 +249,6 @@ workflows: # filters: # tags: # only: /.*/ - # - test_9: - # requires: - # - build - # filters: - # tags: - # only: /.*/ - # - test_10: - # requires: - # - build - # filters: - # tags: - # only: /.*/ - test_11: requires: - build @@ -341,8 +313,6 @@ workflows: - test_latest # - test_ibm8 # - test_zulu8 - # - test_9 - # - test_10 - test_11 # - test_zulu11 # - test_12 @@ -364,8 +334,6 @@ workflows: - test_latest # - test_ibm8 # - test_zulu8 - # - test_9 - # - test_10 - test_11 # - test_zulu11 # - test_12 diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 8c76a82d8e94..6d52df5d1b98 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -12,4 +12,4 @@ # https://help.github.com/en/articles/about-code-owners # -* @jkwatson @safris @trask @tylerbenson +* @iNikem @jkwatson @trask @tylerbenson diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4a09a5772ab9..866e404d6fb0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,99 +1,164 @@ ## Contributing -Pull requests for bug fixes are welcome, but before submitting new features or changes to current functionality [open an issue](https://github.com/open-telemetry/opentelemetry-auto-instr-java/issues/new) -and discuss your ideas or propose the changes you wish to make. After a resolution is reached a PR can be submitted for review. +Pull requests for bug fixes are welcome, but before submitting new features +or changes to current functionality [open an +issue](https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/new) +and discuss your ideas or propose the changes you wish to make. After a +resolution is reached a PR can be submitted for review. -In order to fully build and test this whole repository you need the following: -* Installed both JDK 8 and 9. -* Java 8 should be set as default: `java -version` should give you version 8. -* Defined environment variables `JAVA_8_HOME` and `JAVA_9_HOME` which point to the corresponding java homes. +In order to build and test this whole repository you need JDK 11+. +Some instrumentations and tests may put constraints on which java versions they support. +See [Executing tests with specific java version](#Executing tests with specific java version) below. ### Plugin structure -OpenTelemetry Auto Instrumentation java agent's jar can logically be divided into 3 parts. + +OpenTelemetry Auto Instrumentation java agent's jar can logically be divided +into 3 parts. #### `java-agent` module -This module consists of single class `io.opentelemetry.auto.bootstrap.AgentBootstrap` -which implements [Java instrumentation agent](https://docs.oracle.com/javase/7/docs/api/java/lang/instrument/package-summary.html). -This class is loaded during application startup by application classloader. -Its sole responsibility is to push agent's classes into JVM's bootstrap classloader -and immediately delegate to `io.opentelemetry.auto.bootstrap.Agent` (now in the bootstrap class loader) class from there. + +This module consists of single class +`io.opentelemetry.auto.bootstrap.AgentBootstrap` which implements [Java +instrumentation +agent](https://docs.oracle.com/javase/7/docs/api/java/lang/instrument/package-summary.html). +This class is loaded during application startup by application classloader. +Its sole responsibility is to push agent's classes into JVM's bootstrap +classloader and immediately delegate to +`io.opentelemetry.auto.bootstrap.Agent` (now in the bootstrap class loader) +class from there. #### `agent-bootstrap` module -This module contains support classes for actual instrumentations to be loaded later and separately. -These classes should be available from all possible classloaders in the running application. -For this reason `java-agent` puts all these classes into JVM's bootstrap classloader. -For the same reason this module should be as small as possible and have as few dependencies as possible. -Otherwise, there is a risk of accidentally exposing this classes to the actual application. + +This module contains support classes for actual instrumentations to be loaded +later and separately. These classes should be available from all possible +classloaders in the running application. For this reason `java-agent` puts +all these classes into JVM's bootstrap classloader. For the same reason this +module should be as small as possible and have as few dependencies as +possible. Otherwise, there is a risk of accidentally exposing this classes to +the actual application. #### `agent-tooling` module and `instrumentation` submodules -Contains everything necessary to make instrumentation machinery work, -including integration with [ByteBuddy](https://bytebuddy.net/) and actual library-specific instrumentations. -As these classes depend on many classes from different libraries, -it is paramount to hide all these classes from the host application. -This is achieved in the following way: -* When `java-agent` module builds the final agent, it moves all classes from `instrumentation` submodules -and `agent-tooling` module into a separate folder inside final jar file, called `auto-tooling-and-instrumentation.isolated`. -In addition, the extension of all class files is changed from `class` to `classdata`. -This ensures that general classloaders cannot find nor load these classes. -* When `io.opentelemetry.auto.bootstrap.Agent` starts up, -it creates an instance of `io.opentelemetry.auto.bootstrap.AgentClassLoader`, -loads an `io.opentelemetry.auto.tooling.AgentInstaller` from that `AgentClassLoader` -and then passes control on to the `AgentInstaller` (now in the `AgentClassLoader`). The `AgentInstaller` then installs all of the instrumentations with the help of ByteBuddy. - -The complicated process above ensures that the majority of auto-instrumentation agent's classes -are totally isolated from application classes, -and an instrumented class from arbitrary classloader in JVM can still access helper classes from bootstrap classloader. + +Contains everything necessary to make instrumentation machinery work, +including integration with [ByteBuddy](https://bytebuddy.net/) and actual +library-specific instrumentations. As these classes depend on many classes +from different libraries, it is paramount to hide all these classes from the +host application. This is achieved in the following way: + +- When `java-agent` module builds the final agent, it moves all classes from +`instrumentation` submodules and `agent-tooling` module into a separate +folder inside final jar file, called +`auto-tooling-and-instrumentation.isolated`. In addition, the extension of +all class files is changed from `class` to `classdata`. This ensures that +general classloaders cannot find nor load these classes. +- When `io.opentelemetry.auto.bootstrap.Agent` starts up, it creates an +instance of `io.opentelemetry.auto.bootstrap.AgentClassLoader`, loads an +`io.opentelemetry.auto.tooling.AgentInstaller` from that `AgentClassLoader` +and then passes control on to the `AgentInstaller` (now in the +`AgentClassLoader`). The `AgentInstaller` then installs all of the +instrumentations with the help of ByteBuddy. + +The complicated process above ensures that the majority of +auto-instrumentation agent's classes are totally isolated from application +classes, and an instrumented class from arbitrary classloader in JVM can +still access helper classes from bootstrap classloader. #### Agent jar structure -If you now look inside `java-agent/build/libs/opentelemetry-auto-.jar`, -you will see the following "clusters" of classes: -* `auto-tooling-and-instrumentation.isolated/` - contains `agent-tooling` module and -`instrumentation` submodules, loaded and isolated inside `AgentClassLoader`. -Including OpenTelemetry SDK. -* `io/opentelemetry/auto/bootstrap/` - contains `agent-bootstrap` module and available in -bootstrap classloader. -* `io/opentelemetry/auto/shaded/` - contains OpenTelemetry API and its dependencies. -Shaded during creation of `java-agent` jar file by Shadow Gradle plugin. + +If you now look inside +`java-agent/build/libs/opentelemetry-auto-.jar`, you will see the +following "clusters" of classes: + +- `auto-tooling-and-instrumentation.isolated/` - contains `agent-tooling` +module and `instrumentation` submodules, loaded and isolated inside +`AgentClassLoader`. Including OpenTelemetry SDK. +- `io/opentelemetry/auto/bootstrap/` - contains `agent-bootstrap` module and +available in bootstrap classloader. +- `io/opentelemetry/auto/shaded/` - contains OpenTelemetry API and its +dependencies. Shaded during creation of `java-agent` jar file by Shadow +Gradle plugin. + +### Building + +#### Snapshot builds + +For developers testing code changes before a release is complete, there are +snapshot builds of the `master` branch. When a PR is merged to `master`, a +circleci build is kicked off as a github action which shows up as a github +check on the git commit on `master` branch, i.e. a green checkmark. Clicking +on the green checkmark you can view the `build_test_deploy` workflow and the +`build` job shows the artifacts hosted on circleci. The artifacts will be +named like: + +``` +libs/exporter-support--SNAPSHOT.jar +libs/opentelemetry-auto--SNAPSHOT.jar +libs/opentelemetry-auto-exporters-jaeger--SNAPSHOT.jar +libs/opentelemetry-auto-exporters-logging--SNAPSHOT.jar +libs/opentelemetry-auto-exporters-otlp--SNAPSHOT.jar +``` + +#### Building from source + +Build using Java 11: + +```gradle assemble``` + +and then you can find the java agent artifact at +`java-agent/build/lib/opentelemetry-auto-.jar`. ### Testing + #### Java versions + Open Telemetry Auto Instrumentation's minimal supported version is java 7. -All jar files that we produce, unless noted otherwise, have bytecode compatible with java 7 runtime. -In addition to that we test our code with all later java versions as well: from 8 to 14. +All jar files that we produce, unless noted otherwise, have bytecode +compatible with java 7 runtime. In addition to that we test our code with all +later java versions as well: from 8 to 14. Some libraries that we auto-instrument may have higher minimal requirements. -In this case we compile and test corresponding auto-instrumentation with higher java version as required by library. -The resulting classes will have higher bytecode level, -but as it matches library's java version, no runtime problem arise. +In this case we compile and test corresponding auto-instrumentation with +higher java version as required by library. The resulting classes will have +higher bytecode level, but as it matches library's java version, no runtime +problem arise. #### Instrumentation tests -Executing `./gradlew instrumentation:test` will run tests for all supported auto-instrumentations -using that java version which runs the Gradle build itself. -These tests usually use the minimal supported version of the instrumented library. -In addition to that each instrumentation has a separate test set called `latestDepTest`. -It was created by [Gradle test sets plugin](https://github.com/unbroken-dome/gradle-testsets-plugin). -It uses the very same tests as before, but declares a dynamic dependency on the latest available version of this library. -You can run them all by executing `./gradlew latestDepTest`. +Executing `./gradlew instrumentation:test` will run tests for all supported +auto-instrumentations using that java version which runs the Gradle build +itself. These tests usually use the minimal supported version of the +instrumented library. + +In addition to that each instrumentation has a separate test set called +`latestDepTest`. It was created by [Gradle test sets +plugin](https://github.com/unbroken-dome/gradle-testsets-plugin). It uses the +very same tests as before, but declares a dynamic dependency on the latest +available version of this library. You can run them all by executing +`./gradlew latestDepTest`. #### Executing tests with specific java version -In order to run tests on a specific java version, just execute `./gradlew testJava7` (or `testJava11` or `latestDepTestJava14` etc). -Then Gradle task rule will kick in and do the following: -* check, if Gradle already runs on a java with required version -* if not, look for an environment variable named `JAVA_N_HOME`, where `N` is the requested java version -* if Gradle could not found requested java version, then build will fail -* Gradle will now find all corresponding test tasks and configure them to use java executable of the requested version. -This works both for tasks named `test` and `latestDepTest`. -But currently does not work for other custom test tasks, such as those created by test sets plugin. +In order to run tests on a specific java version, just execute `./gradlew +testJava7` (or `testJava11` or `latestDepTestJava14` etc). Then Gradle task +rule will kick in and do the following: + +- check, if Gradle already runs on a java with required version +- if not, look for an environment variable named `JAVA_N_HOME`, where `N` is the requested java version +- if Gradle could not found requested java version, then build will fail +- Gradle will now find all corresponding test tasks and configure them to use java executable of the requested version. + +This works both for tasks named `test` and `latestDepTest`. But currently +does not work for other custom test tasks, such as those created by test sets +plugin. ### Style guideline -We follow the [Google Java Style Guide](https://google.github.io/styleguide/javaguide.html). -Our build will fail if source code is not formatted according to that style. +We follow the [Google Java Style +Guide](https://google.github.io/styleguide/javaguide.html). Our build will +fail if source code is not formatted according to that style. -To verify code style manually run the following command, -which uses [google-java-format](https://github.com/google/google-java-format) library: +To verify code style manually run the following command, which uses +[google-java-format](https://github.com/google/google-java-format) library: `./gradlew verifyGoogleJavaFormat` @@ -101,8 +166,8 @@ or on Windows `gradlew.bat verifyGoogleJavaFormat` -Instead of fixing style inconsistencies by hand, you can run gradle task `googleJavaFormat` -to automatically fix all found issues: +Instead of fixing style inconsistencies by hand, you can run gradle task +`googleJavaFormat` to automatically fix all found issues: `./gradlew googleJavaFormat` @@ -111,16 +176,19 @@ or on Windows `gradlew.bat googleJavaFormat` #### Pre-commit hook -To completely delegate code style formatting to the machine, -you can add [git pre-commit hook](https://git-scm.com/docs/githooks). -We provide an example script in `buildscripts/pre-commit` file. -Just copy or symlink it into `.git/hooks` folder. +To completely delegate code style formatting to the machine, you can add [git +pre-commit hook](https://git-scm.com/docs/githooks). We provide an example +script in `buildscripts/pre-commit` file. Just copy or symlink it into +`.git/hooks` folder. + + +#### Editorconfig -#### Editorconfig -As additional convenience for IntelliJ Idea users, we provide `.editorconfig` file. -Idea will automatically use it to adjust its code formatting settings. -It does not support all required rules, so you still have to run `googleJavaFormat` from time to time. +As additional convenience for IntelliJ Idea users, we provide `.editorconfig` +file. Idea will automatically use it to adjust its code formatting settings. +It does not support all required rules, so you still have to run +`googleJavaFormat` from time to time. ### Intellij IDEA @@ -136,4 +204,4 @@ Suggested plugins and settings: ![import layout](https://user-images.githubusercontent.com/734411/43430811-28442636-94ae-11e8-86f1-f270ddcba023.png) * [Google Java Format](https://plugins.jetbrains.com/plugin/8527-google-java-format) * [Save Actions](https://plugins.jetbrains.com/plugin/7642-save-actions) - ![Recommended Settings](https://user-images.githubusercontent.com/734411/43430944-db84bf8a-94ae-11e8-8cec-0daa064937c4.png) + ![Recommended Settings](https://user-images.githubusercontent.com/734411/43430944-db84bf8a-94ae-11e8-8cec-0daa064937c4.png) \ No newline at end of file diff --git a/README.md b/README.md index a3e032b4ef68..deeb507495b4 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,75 @@ # OpenTelemetry Auto-Instrumentation for Java ## Introduction -This project provides a Java agent that can be attached to any Java 7+ application and dynamically injects bytecode -to capture telemetry from a number of popular libraries and frameworks. + +This project provides a Java agent JAR that can be attached to any Java 7+ +application and dynamically injects bytecode to capture telemetry from a +number of popular libraries and frameworks. The telemetry data can exported +in a variety of formats each provided as their own independent JAR. In +addition, the agent and exporter can be configured via command line arguments +or environment variables. The net result is the ability to gather telemetry +data from a Java application without code changes. + +## Getting Started + +Download the [latest +release](https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases) +of the Java agent and available exporters. + +The instrumentation agent is enabled using the `-javaagent` flag to the JVM. +Configuration parameters are passed as Java system properties (`-D` flags) or +as environment variables. Both the Java agent and exporter configuration must +be defined before the application JAR. For example: + +``` +java -javaagent:path/to/opentelemetry-auto-.jar \ + -Dota.exporter.jar=path/to/opentelemetry-auto-exporters-jaeger-.jar \ + -Dota.exporter.jaeger.endpoint=localhost:14250 \ + -Dota.exporter.jaeger.service.name=shopping \ + -jar myapp.jar +``` + +### Configuration parameters (subject to change!) + +Note: These parameter names are very likely to change over time, so please check +back here when trying out a new version! Please report any bugs or unexpected +behavior you may find. + +#### Jaeger exporter + +A simple wrapper for the Jaeger exporter of opentelemetry-java. It currently +only supports gRPC as its communications protocol. + +| System property | Environment variable | Purpose | +|----------------------------------|----------------------------------|----------------------------------------------------------------------| +| ota.exporter.jaeger.endpoint | OTA_EXPORTER_JAEGER_ENDPOINT | The Jaeger endpoint to connect to. Currently only gRPC is supported. | +| ota.exporter.jaeger.service.name | OTA_EXPORTER_JAEGER_SERVICE_NAME | The service name of this JVM instance | + +#### Zipkin exporter +A simple wrapper for the Zipkin exporter of opentelemetry-java. It POSTs json in [Zipkin format](https://zipkin.io/zipkin-api/#/default/post_spans) to a specified HTTP URL. + +| System property | Environment variable | Purpose | +|----------------------------------|----------------------------------|----------------------------------------------------------------------| +| ota.exporter.zipkin.endpoint | OTA_EXPORTER_ZIPKIN_ENDPOINT | The Zipkin endpoint to connect to. Currently only HTTP is supported. | +| ota.exporter.zipkin.service.name | OTA_EXPORTER_ZIPKIN_SERVICE_NAME | The service name of this JVM instance + +#### OTLP exporter + +A simple wrapper for the OTLP exporter of opentelemetry-java. + +| System property | Environment variable | Purpose | +|----------------------------------|----------------------------------|----------------------------------------------------------------------| +| ota.exporter.jar | OTA_EXPORTER_JAR | Path to the exporter fat-jar that you want to use | +| ota.exporter.otlp.endpoint | OTA_EXPORTER_OTLP_ENDPOINT | The OTLP endpoint to connect to. | + +#### Logging Exporter + +The logging exporter simply prints the name of the span along with its +attributes to stdout. It is used manly for testing and debugging. + +| System property | Environment variable | Purpose | +|-----------------------------|-----------------------------|------------------------------------------------------------------------------| +| ota.exporter.logging.prefix | OTA_EXPORTER_LOGGING_PREFIX | An optional string that is printed in front of the span name and attributes. | ## Supported Java libraries and frameworks @@ -58,94 +125,69 @@ to capture telemetry from a number of popular libraries and frameworks. | [Spymemcached](https://github.com/couchbase/spymemcached) | 2.12+ | | [Twilio](https://github.com/twilio/twilio-java) | 6.6+ | -### Download and run - -#### Release builds - -Download the [latest release](https://github.com/open-telemetry/opentelemetry-auto-instr-java/releases). - -The instrumentation agent is enabled using the -javaagent flag to the JVM. Configuration parameters are passed -as Java system properties (-D flags) or as environment variables. This is an example: - -``` -java -javaagent:path/to/opentelemetry-auto-.jar \ - -Dota.exporter.jar=path/to/opentelemetry-auto-exporters-jaeger-.jar \ - -Dota.exporter.jaeger.endpoint=localhost:14250 \ - -Dota.exporter.jaeger.service.name=shopping \ - -jar myapp.jar -``` +### Disabled instrumentations -#### Snapshot builds +Some instrumentations can produce too many spans and make traces very noisy. +For this reason the following instrumentations are disabled by default: +- `jdbc-datasource` which creates spans whenever `java.sql.DataSource#getConnection` method is called. +- `servlet-filter` which creates spans around Servlet Filter methods. +- `servlet-service` which creates spans around Servlet methods. + +To enable them, add `ota.integration..enabled` system property: +`-Dota.integration.jdbc-datasource.enabled=true` -For developers testing code changes before a release is complete, -there are snapshot builds of the `master` branch. When a PR is -merged to `master`, a circleci build is kicked off as a github -action which shows up as a github check on the git commit on `master` -branch, i.e. a green checkmark. Clicking on the green checkmark you -can view the `build_test_deploy` workflow and the `build` job shows -the artifacts hosted on circleci. The artifacts will be named like: +#### Grizzly instrumentation -``` -libs/exporter-support--SNAPSHOT.jar -libs/opentelemetry-auto--SNAPSHOT.jar -libs/opentelemetry-auto-exporters-jaeger--SNAPSHOT.jar -libs/opentelemetry-auto-exporters-logging--SNAPSHOT.jar -libs/opentelemetry-auto-exporters-otlp--SNAPSHOT.jar -``` +Whenever you use +[Grizzly](https://javaee.github.io/grizzly/httpserverframework.html) for +Servlet-based applications, you get better experience from Servlet-specific +support. As these two instrumentations conflict with each other, more generic +instrumentation for Grizzly http server is disabled by default. If needed, +you can enable it by add the following system property: +`-Dota.integration.grizzly.enabled=true` -### Configuration parameters (subject to change!) -| System property | Environment variable | Purpose | -|------------------|----------------------|---------------------------------------------------| -| ota.exporter.jar | OTA_EXPORTER_JAR | Path to the exporter fat-jar that you want to use | +## Manually instrumenting -### Available exporters -There are three exporters available under -[releases](https://github.com/open-telemetry/opentelemetry-auto-instr-java/releases). +You can use the OpenTelemetry `getTracer` or the `@WithSpan` annotation to +manually instrument your Java application. -#### Jaeger exporter -A simple wrapper for the Jaeger exporter of opentelemetry-java. It currently only supports gRPC as its communications protocol. +### Configure the OpenTelemetry getTracer -| System property | Environment variable | Purpose | -|----------------------------------|----------------------------------|----------------------------------------------------------------------| -| ota.exporter.jaeger.endpoint | OTA_EXPORTER_JAEGER_ENDPOINT | The Jaeger endpoint to connect to. Currently only gRPC is supported. | -| ota.exporter.jaeger.service.name | OTA_EXPORTER_JAEGER_SERVICE_NAME | The service name of this JVM instance | +OpenTelemetry offers a tracer to easily enable custom instrumentation +throughout your application. See the [OpenTelemetry Java +QuickStart](https://github.com/open-telemetry/opentelemetry-java/blob/master/QUICKSTART.md#tracing) +for an example of how to configure it. -#### OTLP exporter -A simple wrapper for the OTLP exporter of opentelemetry-java. +### Configure a WithSpan annotation -| System property | Environment variable | Purpose | -|----------------------------------|----------------------------------|----------------------------------------------------------------------| -| ota.exporter.otlp.endpoint | OTA_EXPORTER_OTLP_ENDPOINT | The OTLP endpoint to connect to. | +If you want to configure custom instrumentation and don't want to use the +OpenTelemetry `getTracer` and API directly, configure a `@WithSpan` +annotation. Add the trace annotation to your application's code: -#### Logging Exporter -The logging exporter simply prints the name of the span along with its attributes to stdout. It is used manly -for testing and debugging. +```java +import io.opentelemetry.contrib.auto.annotations.WithSpan; -| System property | Environment variable | Purpose | -|-----------------------------|-----------------------------|------------------------------------------------------------------------------| -| ota.exporter.logging.prefix | OTA_EXPORTER_LOGGING_PREFIX | An optional string that is printed in front of the span name and attributes. | +public class MyClass { + @WithSpan + public void MyLogic() { + <...> + } +} +``` -These parameter names are very likely to change over time, so please check back here when trying out a new version! +Each time the application invokes the annotated method, it creates a span +that denote its duration and provides any thrown exceptions. -Please report any bugs or unexpected behavior you may find. +#### Configuration -## Disabled instrumentations -Some instrumentations can produce too many spans and make traces very noisy. -For this reason the following instrumentations are disabled by default: -- `jdbc-datasource` which creates spans whenever `java.sql.DataSource#getConnection` method is called. -- `servlet-filter` which creates spans around Servlet Filter methods. -- `servlet-service` which creates spans around Servlet methods. - -To enable them, add `ota.integration..enabled` system property: -`-Dota.integration.jdbc-datasource.enabled=true` +The `@WithSpan` annotation requires code changes to implement. You can +disable the annotation at runtime via the exclude configuration or +environment variables: -### Grizzly instrumentation -Whenever you use [Grizzly](https://javaee.github.io/grizzly/httpserverframework.html) -for Servlet-based applications, you get better experience from Servlet-specific support. -As these two instrumentations conflict with each other, more generic instrumentation for Grizzly -http server is disabled by default. -If needed, you can enable it by add the following system property: -`-Dota.integration.grizzly.enabled=true` +| System property | Environment variable | Purpose | +|----------------------------------|----------------------------------|----------------------------------------------------------------------| +| trace.classes.exclude | TRACE_CLASSES_EXCLUDE | Exclude classes with the `@WithSpan` annotation | +| trace.methods.exclude | TRACE_METHODS_EXCLUDE | Exclude methods with the `@WithSpan` annotation | ## Troubleshooting @@ -153,10 +195,5 @@ To turn on the agent's internal debug logging: `-Dio.opentelemetry.auto.slf4j.simpleLogger.defaultLogLevel=debug` -## Building from source - -Build using Java 8: - -```gradle assemble``` - -and then you can find the java agent artifact at `java-agent/build/lib/opentelemetry-auto-.jar`. +Note these logs are extremely verbose. Enable debug logging only when needed. +Debug logging negatively impacts the performance of your application. \ No newline at end of file diff --git a/agent-bootstrap/src/main/java/io/opentelemetry/auto/bootstrap/InstrumentationContext.java b/agent-bootstrap/src/main/java/io/opentelemetry/auto/bootstrap/InstrumentationContext.java index 5fb45c02dd82..ee2d9914ce19 100644 --- a/agent-bootstrap/src/main/java/io/opentelemetry/auto/bootstrap/InstrumentationContext.java +++ b/agent-bootstrap/src/main/java/io/opentelemetry/auto/bootstrap/InstrumentationContext.java @@ -25,8 +25,10 @@ private InstrumentationContext() {} *

Conceptually this can be thought of as a map lookup to fetch a second level map given * keyClass. * - *

However, the implementation is actually provided by bytecode transformation for performance - * reasons. + *

In reality, the calls to this method are re-written to something more performant + * while injecting advice into a method. + * + *

This method must only be called within an Advice class. * * @param keyClass The key class context is attached to. * @param contextClass The context class attached to the user class. diff --git a/agent-bootstrap/src/main/java/io/opentelemetry/auto/bootstrap/instrumentation/api/Pair.java b/agent-bootstrap/src/main/java/io/opentelemetry/auto/bootstrap/instrumentation/api/Pair.java new file mode 100644 index 000000000000..dedb1ae14913 --- /dev/null +++ b/agent-bootstrap/src/main/java/io/opentelemetry/auto/bootstrap/instrumentation/api/Pair.java @@ -0,0 +1,47 @@ +/* + * Copyright The OpenTelemetry Authors + * + * 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. + */ +package io.opentelemetry.auto.bootstrap.instrumentation.api; + +public final class Pair { + + public static Pair of(final T left, final U right) { + return new Pair<>(left, right); + } + + private final T left; + private final U right; + + Pair(final T left, final U right) { + this.left = left; + this.right = right; + } + + public T getLeft() { + return left; + } + + public U getRight() { + return right; + } + + public boolean hasLeft() { + return null != left; + } + + public boolean hasRight() { + return null != right; + } +} diff --git a/agent-bootstrap/src/main/java/io/opentelemetry/auto/bootstrap/instrumentation/decorator/HttpServerDecorator.java b/agent-bootstrap/src/main/java/io/opentelemetry/auto/bootstrap/instrumentation/decorator/HttpServerDecorator.java index 535c66b935e8..9b153ba20daf 100644 --- a/agent-bootstrap/src/main/java/io/opentelemetry/auto/bootstrap/instrumentation/decorator/HttpServerDecorator.java +++ b/agent-bootstrap/src/main/java/io/opentelemetry/auto/bootstrap/instrumentation/decorator/HttpServerDecorator.java @@ -27,6 +27,7 @@ @Slf4j public abstract class HttpServerDecorator extends ServerDecorator { public static final String SPAN_ATTRIBUTE = "io.opentelemetry.auto.span"; + public static final String RESPONSE_ATTRIBUTE = "io.opentelemetry.auto.response"; public static final String DEFAULT_SPAN_NAME = "HTTP request"; protected abstract String method(REQUEST request); diff --git a/agent-bootstrap/src/main/java/io/opentelemetry/auto/bootstrap/instrumentation/java/concurrent/State.java b/agent-bootstrap/src/main/java/io/opentelemetry/auto/bootstrap/instrumentation/java/concurrent/State.java index ff12a51ee68b..12051b9d2370 100644 --- a/agent-bootstrap/src/main/java/io/opentelemetry/auto/bootstrap/instrumentation/java/concurrent/State.java +++ b/agent-bootstrap/src/main/java/io/opentelemetry/auto/bootstrap/instrumentation/java/concurrent/State.java @@ -38,11 +38,13 @@ private State() {} public void setParentSpan(final Span parentSpan) { final boolean result = parentSpanRef.compareAndSet(null, parentSpan); if (!result && parentSpanRef.get() != parentSpan) { - log.debug( - "Failed to set parent span because another parent span is already set {}: new: {}, old: {}", - this, - parentSpan, - parentSpanRef.get()); + if (log.isDebugEnabled()) { + log.debug( + "Failed to set parent span because another parent span is already set {}: new: {}, old: {}", + this, + parentSpan, + parentSpanRef.get()); + } } } diff --git a/agent-bootstrap/src/main/java/io/opentelemetry/auto/config/Config.java b/agent-bootstrap/src/main/java/io/opentelemetry/auto/config/Config.java index 3e2243edbf60..824467a4a1af 100644 --- a/agent-bootstrap/src/main/java/io/opentelemetry/auto/config/Config.java +++ b/agent-bootstrap/src/main/java/io/opentelemetry/auto/config/Config.java @@ -55,7 +55,6 @@ public class Config { private static final Pattern ENV_REPLACEMENT = Pattern.compile("[^a-zA-Z0-9_]"); public static final String EXPORTER_JAR = "exporter.jar"; - public static final String SERVICE = "service"; public static final String PROPAGATORS = "propagators"; public static final String CONFIGURATION_FILE = "trace.config"; public static final String TRACE_ENABLED = "trace.enabled"; @@ -74,6 +73,8 @@ public class Config { public static final String RUNTIME_CONTEXT_FIELD_INJECTION = "trace.runtime.context.field.injection"; + public static final String KAFKA_CLIENT_PROPAGATION_ENABLED = "kafka.client.propagation.enabled"; + public static final String LOG_INJECTION_ENABLED = "log.injection.enabled"; public static final String EXPERIMENTAL_LOG_CAPTURE_THRESHOLD = "experimental.log.capture.threshold"; @@ -94,6 +95,8 @@ public class Config { public static final boolean DEFAULT_LOG_INJECTION_ENABLED = false; public static final String DEFAULT_EXPERIMENTAL_LOG_CAPTURE_THRESHOLD = null; + public static final boolean DEFAULT_KAFKA_CLIENT_PROPAGATION_ENABLED = true; + private static final String DEFAULT_TRACE_ANNOTATIONS = null; private static final boolean DEFAULT_TRACE_EXECUTORS_ALL = false; private static final String DEFAULT_TRACE_EXECUTORS = ""; @@ -104,7 +107,6 @@ public class Config { public static final boolean DEFAULT_SQL_NORMALIZER_ENABLED = true; @Getter private final String exporterJar; - @Getter private final String serviceName; @Getter private final List propagators; @Getter private final boolean traceEnabled; @Getter private final boolean integrationsEnabled; @@ -144,6 +146,8 @@ public class Config { @Getter private final boolean sqlNormalizerEnabled; + @Getter private final boolean kafkaClientPropagationEnabled; + // Values from an optionally provided properties file private static Properties propertiesFromConfigFile; @@ -154,7 +158,6 @@ public class Config { propagators = getListSettingFromEnvironment(PROPAGATORS, null); exporterJar = getSettingFromEnvironment(EXPORTER_JAR, null); - serviceName = getSettingFromEnvironment(SERVICE, "(unknown)"); traceEnabled = getBooleanSettingFromEnvironment(TRACE_ENABLED, DEFAULT_TRACE_ENABLED); integrationsEnabled = getBooleanSettingFromEnvironment(INTEGRATIONS_ENABLED, DEFAULT_INTEGRATIONS_ENABLED); @@ -206,13 +209,16 @@ public class Config { sqlNormalizerEnabled = getBooleanSettingFromEnvironment(SQL_NORMALIZER_ENABLED, DEFAULT_SQL_NORMALIZER_ENABLED); + kafkaClientPropagationEnabled = + getBooleanSettingFromEnvironment( + KAFKA_CLIENT_PROPAGATION_ENABLED, DEFAULT_KAFKA_CLIENT_PROPAGATION_ENABLED); + log.debug("New instance: {}", this); } // Read order: Properties -> Parent private Config(final Properties properties, final Config parent) { exporterJar = properties.getProperty(EXPORTER_JAR, parent.exporterJar); - serviceName = properties.getProperty(SERVICE, parent.serviceName); propagators = getPropertyListValue(properties, PROPAGATORS, parent.propagators); @@ -264,7 +270,11 @@ private Config(final Properties properties, final Config parent) { traceExecutors = getPropertyListValue(properties, TRACE_EXECUTORS, parent.traceExecutors); sqlNormalizerEnabled = - getPropertyBooleanValue(properties, SQL_NORMALIZER_ENABLED, DEFAULT_SQL_NORMALIZER_ENABLED); + getPropertyBooleanValue(properties, SQL_NORMALIZER_ENABLED, parent.sqlNormalizerEnabled); + + kafkaClientPropagationEnabled = + getPropertyBooleanValue( + properties, KAFKA_CLIENT_PROPAGATION_ENABLED, parent.kafkaClientPropagationEnabled); log.debug("New instance: {}", this); } diff --git a/agent-tooling/src/main/java/io/opentelemetry/auto/tooling/AgentInstaller.java b/agent-tooling/src/main/java/io/opentelemetry/auto/tooling/AgentInstaller.java index f7ca88129093..1d4ba7a65744 100644 --- a/agent-tooling/src/main/java/io/opentelemetry/auto/tooling/AgentInstaller.java +++ b/agent-tooling/src/main/java/io/opentelemetry/auto/tooling/AgentInstaller.java @@ -207,11 +207,13 @@ public void onError( final JavaModule module, final boolean loaded, final Throwable throwable) { - log.debug( - "Failed to handle {} for transformation on classloader {}: {}", - typeName, - classLoader, - throwable.getMessage()); + if (log.isDebugEnabled()) { + log.debug( + "Failed to handle {} for transformation on classloader {}: {}", + typeName, + classLoader, + throwable.getMessage()); + } } @Override diff --git a/agent-tooling/src/main/java/io/opentelemetry/auto/tooling/ExporterClassLoader.java b/agent-tooling/src/main/java/io/opentelemetry/auto/tooling/ExporterClassLoader.java index c10807b801f8..16a76f52b85e 100644 --- a/agent-tooling/src/main/java/io/opentelemetry/auto/tooling/ExporterClassLoader.java +++ b/agent-tooling/src/main/java/io/opentelemetry/auto/tooling/ExporterClassLoader.java @@ -22,15 +22,19 @@ import java.net.URL; import java.net.URLClassLoader; import java.util.Enumeration; +import java.util.jar.JarFile; +import java.util.jar.Manifest; +import lombok.extern.slf4j.Slf4j; import net.bytebuddy.jar.asm.ClassReader; import net.bytebuddy.jar.asm.ClassWriter; import net.bytebuddy.jar.asm.commons.ClassRemapper; +@Slf4j public class ExporterClassLoader extends URLClassLoader { // We need to prefix the names to prevent the gradle shadowJar relocation rules from touching // them. It's possible to do this by excluding this class from shading, but it may cause issue // with transitive dependencies down the line. - private final ShadingRemapper remapper = + private static final ShadingRemapper remapper = new ShadingRemapper( rule( "#io.opentelemetry.OpenTelemetry", @@ -52,8 +56,11 @@ public class ExporterClassLoader extends URLClassLoader { rule("#java.util.logging.Logger", "#io.opentelemetry.auto.bootstrap.PatchLogger"), rule("#org.slf4j", "#io.opentelemetry.auto.slf4j")); - public ExporterClassLoader(final URL[] urls, final ClassLoader parent) { - super(urls, parent); + private final Manifest manifest; + + public ExporterClassLoader(final URL url, final ClassLoader parent) { + super(new URL[] {url}, parent); + this.manifest = getManifest(url); } @Override @@ -69,16 +76,79 @@ public Enumeration getResources(final String name) throws IOException { @Override protected Class findClass(final String name) throws ClassNotFoundException { - // Use resource loading to get the class as a stream of bytes, then use ASM to transform it. - try (final InputStream in = getResourceAsStream(name.replace('.', '/') + ".class")) { - final ClassWriter cw = new ClassWriter(0); - final ClassReader cr = new ClassReader(in); - cr.accept(new ClassRemapper(cw, remapper), ClassReader.EXPAND_FRAMES); - final byte[] bytes = cw.toByteArray(); + InputStream in = getResourceAsStream(name.replace('.', '/') + ".class"); + if (in == null) { + throw new ClassNotFoundException(name); + } + try { + final byte[] bytes = remapClassBytes(in); + definePackageIfNeeded(name); return defineClass(name, bytes, 0, bytes.length); } catch (final IOException e) { - throw new ClassNotFoundException(name); + throw new ClassNotFoundException(name, e); + } finally { + try { + in.close(); + } catch (IOException e) { + log.debug(e.getMessage(), e); + } + } + } + + private void definePackageIfNeeded(String className) { + String packageName = getPackageName(className); + if (packageName == null) { + // default package + return; + } + if (isPackageDefined(packageName)) { + // package has already been defined + return; + } + try { + definePackage(packageName); + } catch (IllegalArgumentException e) { + // this exception is thrown when the package has already been defined, which is possible due + // to race condition with the check above + if (!isPackageDefined(packageName)) { + // this shouldn't happen however + log.error(e.getMessage(), e); + } + } + } + + private boolean isPackageDefined(String packageName) { + return getPackage(packageName) != null; + } + + private void definePackage(String packageName) { + if (manifest == null) { + definePackage(packageName, null, null, null, null, null, null, null); + } else { + definePackage(packageName, manifest, null); + } + } + + private static byte[] remapClassBytes(InputStream in) throws IOException { + final ClassWriter cw = new ClassWriter(0); + final ClassReader cr = new ClassReader(in); + cr.accept(new ClassRemapper(cw, remapper), ClassReader.EXPAND_FRAMES); + return cw.toByteArray(); + } + + private static String getPackageName(String className) { + int index = className.lastIndexOf('.'); + return index == -1 ? null : className.substring(0, index); + } + + private static Manifest getManifest(URL url) { + try { + JarFile jarFile = new JarFile(url.getFile()); + return jarFile.getManifest(); + } catch (IOException e) { + log.warn(e.getMessage(), e); } + return null; } } diff --git a/agent-tooling/src/main/java/io/opentelemetry/auto/tooling/HelperInjector.java b/agent-tooling/src/main/java/io/opentelemetry/auto/tooling/HelperInjector.java index b760fbb14945..806f71355216 100644 --- a/agent-tooling/src/main/java/io/opentelemetry/auto/tooling/HelperInjector.java +++ b/agent-tooling/src/main/java/io/opentelemetry/auto/tooling/HelperInjector.java @@ -142,12 +142,14 @@ public DynamicType.Builder transform( helperModules.add(new WeakReference<>(javaModule.unwrap())); } } catch (final Exception e) { - log.error( - "Error preparing helpers while processing {} for {}. Failed to inject helper classes into instance {}", - typeDescription, - requestingName, - classLoader, - e); + if (log.isErrorEnabled()) { + log.error( + "Error preparing helpers while processing {} for {}. Failed to inject helper classes into instance {}", + typeDescription, + requestingName, + classLoader, + e); + } throw new RuntimeException(e); } @@ -166,7 +168,7 @@ private Map> injectBootstrapClassLoader( // a reference count -- but for now, starting simple. // Failures to create a tempDir are propagated as IOException and handled by transform - File tempDir = createTempDir(); + final File tempDir = createTempDir(); try { return ClassInjector.UsingInstrumentation.of( tempDir, @@ -206,15 +208,15 @@ private void ensureModuleCanReadHelperModules(final JavaModule target) { } } - private static final File createTempDir() throws IOException { + private static File createTempDir() throws IOException { return Files.createTempDirectory("opentelemetry-temp-jars").toFile(); } - private static final void deleteTempDir(final File file) { + private static void deleteTempDir(final File file) { // Not using Files.delete for deleting the directory because failures // create Exceptions which may prove expensive. Instead using the // older File API which simply returns a boolean. - boolean deleted = file.delete(); + final boolean deleted = file.delete(); if (!deleted) { file.deleteOnExit(); } diff --git a/agent-tooling/src/main/java/io/opentelemetry/auto/tooling/Instrumenter.java b/agent-tooling/src/main/java/io/opentelemetry/auto/tooling/Instrumenter.java index e2b3328ba3bb..85169d06d3e5 100644 --- a/agent-tooling/src/main/java/io/opentelemetry/auto/tooling/Instrumenter.java +++ b/agent-tooling/src/main/java/io/opentelemetry/auto/tooling/Instrumenter.java @@ -160,21 +160,25 @@ public boolean matches( if (log.isDebugEnabled()) { final List mismatches = muzzle.getMismatchedReferenceSources(classLoader); - log.debug( - "Instrumentation muzzled: {} -- {} on {}", - instrumentationNames, - Instrumenter.Default.this.getClass().getName(), - classLoader); + if (log.isDebugEnabled()) { + log.debug( + "Instrumentation muzzled: {} -- {} on {}", + instrumentationNames, + Instrumenter.Default.this.getClass().getName(), + classLoader); + } for (final Reference.Mismatch mismatch : mismatches) { log.debug("-- {}", mismatch); } } } else { - log.debug( - "Applying instrumentation: {} -- {} on {}", - instrumentationPrimaryName, - Instrumenter.Default.this.getClass().getName(), - classLoader); + if (log.isDebugEnabled()) { + log.debug( + "Applying instrumentation: {} -- {} on {}", + instrumentationPrimaryName, + Instrumenter.Default.this.getClass().getName(), + classLoader); + } } return isMatch; } diff --git a/agent-tooling/src/main/java/io/opentelemetry/auto/tooling/PropagatorsInitializer.java b/agent-tooling/src/main/java/io/opentelemetry/auto/tooling/PropagatorsInitializer.java index a04ebe304760..d37c7309b608 100644 --- a/agent-tooling/src/main/java/io/opentelemetry/auto/tooling/PropagatorsInitializer.java +++ b/agent-tooling/src/main/java/io/opentelemetry/auto/tooling/PropagatorsInitializer.java @@ -44,11 +44,18 @@ public static void initializePropagators(List propagators) { DefaultContextPropagators.Builder propagatorsBuilder = DefaultContextPropagators.builder(); + boolean addedPropagator = false; for (String propagatorId : propagators) { HttpTextFormat textPropagator = TEXTMAP_PROPAGATORS.get(propagatorId.trim().toLowerCase()); if (textPropagator != null) { + if (addedPropagator) { + log.warn( + "Only one propagator per concern can be added, " + textPropagator + " is ignored"); + continue; + } propagatorsBuilder.addHttpTextFormat(textPropagator); log.info("Added " + textPropagator + " propagator"); + addedPropagator = true; } else { log.warn("No matching propagator for " + propagatorId); } diff --git a/agent-tooling/src/main/java/io/opentelemetry/auto/tooling/TracerInstaller.java b/agent-tooling/src/main/java/io/opentelemetry/auto/tooling/TracerInstaller.java index d2adc77afc09..c0a04fb4c881 100644 --- a/agent-tooling/src/main/java/io/opentelemetry/auto/tooling/TracerInstaller.java +++ b/agent-tooling/src/main/java/io/opentelemetry/auto/tooling/TracerInstaller.java @@ -64,7 +64,7 @@ private static synchronized void installExportersFromJar(final String exporterJa } final DefaultExporterConfig config = new DefaultExporterConfig("exporter"); final ExporterClassLoader exporterLoader = - new ExporterClassLoader(new URL[] {url}, TracerInstaller.class.getClassLoader()); + new ExporterClassLoader(url, TracerInstaller.class.getClassLoader()); final SpanExporterFactory spanExporterFactory = getExporterFactory(SpanExporterFactory.class, exporterLoader); diff --git a/agent-tooling/src/main/java/io/opentelemetry/auto/tooling/VersionLogger.java b/agent-tooling/src/main/java/io/opentelemetry/auto/tooling/VersionLogger.java index c2ec2b8fcc56..ca97f8d42496 100644 --- a/agent-tooling/src/main/java/io/opentelemetry/auto/tooling/VersionLogger.java +++ b/agent-tooling/src/main/java/io/opentelemetry/auto/tooling/VersionLogger.java @@ -30,12 +30,14 @@ public static void logAllVersions() { "opentelemetry-auto - version: {}", getVersionString( ClassLoader.getSystemClassLoader().getResourceAsStream("opentelemetry-auto.version"))); - log.debug( - "Running on Java {}. JVM {} - {} - {}", - System.getProperty("java.version"), - System.getProperty("java.vm.name"), - System.getProperty("java.vm.vendor"), - System.getProperty("java.vm.version")); + if (log.isDebugEnabled()) { + log.debug( + "Running on Java {}. JVM {} - {} - {}", + System.getProperty("java.version"), + System.getProperty("java.vm.name"), + System.getProperty("java.vm.vendor"), + System.getProperty("java.vm.version")); + } } private static String getVersionString(final InputStream stream) { diff --git a/agent-tooling/src/main/java/io/opentelemetry/auto/tooling/bytebuddy/matcher/SafeErasureMatcher.java b/agent-tooling/src/main/java/io/opentelemetry/auto/tooling/bytebuddy/matcher/SafeErasureMatcher.java index 8592515e73ca..3f7f2cd55146 100644 --- a/agent-tooling/src/main/java/io/opentelemetry/auto/tooling/bytebuddy/matcher/SafeErasureMatcher.java +++ b/agent-tooling/src/main/java/io/opentelemetry/auto/tooling/bytebuddy/matcher/SafeErasureMatcher.java @@ -62,11 +62,13 @@ static TypeDescription safeAsErasure(final TypeDefinition typeDefinition) { try { return typeDefinition.asErasure(); } catch (final Exception e) { - log.debug( - "{} trying to get erasure for target {}: {}", - e.getClass().getSimpleName(), - safeTypeDefinitionName(typeDefinition), - e.getMessage()); + if (log.isDebugEnabled()) { + log.debug( + "{} trying to get erasure for target {}: {}", + e.getClass().getSimpleName(), + safeTypeDefinitionName(typeDefinition), + e.getMessage()); + } return null; } } diff --git a/agent-tooling/src/main/java/io/opentelemetry/auto/tooling/bytebuddy/matcher/SafeHasSuperTypeMatcher.java b/agent-tooling/src/main/java/io/opentelemetry/auto/tooling/bytebuddy/matcher/SafeHasSuperTypeMatcher.java index 6c00c6e5d5c8..971df9a616d3 100644 --- a/agent-tooling/src/main/java/io/opentelemetry/auto/tooling/bytebuddy/matcher/SafeHasSuperTypeMatcher.java +++ b/agent-tooling/src/main/java/io/opentelemetry/auto/tooling/bytebuddy/matcher/SafeHasSuperTypeMatcher.java @@ -119,11 +119,13 @@ private List safeGetInterfaces(final TypeDefinition typeDefiniti interfaceTypes.add(interfaceIter.next()); } } catch (final Exception e) { - log.debug( - "{} trying to get interfaces for target {}: {}", - e.getClass().getSimpleName(), - safeTypeDefinitionName(typeDefinition), - e.getMessage()); + if (log.isDebugEnabled()) { + log.debug( + "{} trying to get interfaces for target {}: {}", + e.getClass().getSimpleName(), + safeTypeDefinitionName(typeDefinition), + e.getMessage()); + } } return interfaceTypes; } @@ -132,11 +134,13 @@ static TypeDefinition safeGetSuperClass(final TypeDefinition typeDefinition) { try { return typeDefinition.getSuperClass(); } catch (final Exception e) { - log.debug( - "{} trying to get super class for target {}: {}", - e.getClass().getSimpleName(), - safeTypeDefinitionName(typeDefinition), - e.getMessage()); + if (log.isDebugEnabled()) { + log.debug( + "{} trying to get super class for target {}: {}", + e.getClass().getSimpleName(), + safeTypeDefinitionName(typeDefinition), + e.getMessage()); + } return null; } } diff --git a/agent-tooling/src/main/java/io/opentelemetry/auto/tooling/context/FieldBackedProvider.java b/agent-tooling/src/main/java/io/opentelemetry/auto/tooling/context/FieldBackedProvider.java index 6e7044645934..88dc7c989e59 100644 --- a/agent-tooling/src/main/java/io/opentelemetry/auto/tooling/context/FieldBackedProvider.java +++ b/agent-tooling/src/main/java/io/opentelemetry/auto/tooling/context/FieldBackedProvider.java @@ -229,11 +229,13 @@ public void visitMethodInsn( final String keyClassName = ((Type) stack[1]).getClassName(); final TypeDescription contextStoreImplementationClass = getContextStoreImplementation(keyClassName, contextClassName); - log.debug( - "Rewriting context-store map fetch for instrumenter {}: {} -> {}", - instrumenter.getClass().getName(), - keyClassName, - contextClassName); + if (log.isDebugEnabled()) { + log.debug( + "Rewriting context-store map fetch for instrumenter {}: {} -> {}", + instrumenter.getClass().getName(), + keyClassName, + contextClassName); + } if (contextStoreImplementationClass == null) { throw new IllegalStateException( String.format( diff --git a/agent-tooling/src/test/groovy/io/opentelemetry/auto/test/ClassLoaderMatcherTest.groovy b/agent-tooling/src/test/groovy/io/opentelemetry/auto/test/ClassLoaderMatcherTest.groovy index 590422b2b7a8..eaf07aafebe8 100644 --- a/agent-tooling/src/test/groovy/io/opentelemetry/auto/test/ClassLoaderMatcherTest.groovy +++ b/agent-tooling/src/test/groovy/io/opentelemetry/auto/test/ClassLoaderMatcherTest.groovy @@ -32,7 +32,8 @@ class ClassLoaderMatcherTest extends AgentSpecification { def "skips exporter classloader"() { setup: - final URLClassLoader exporterLoader = new ExporterClassLoader(new URL[0], null) + URL url = new URL("file://") + final URLClassLoader exporterLoader = new ExporterClassLoader(url, null) expect: ClassLoaderMatcher.skipClassLoader().matches(exporterLoader) } diff --git a/auto-exporters/auto-exporters.gradle b/auto-exporters/auto-exporters.gradle index e633f0dc7a9b..00e605e413a1 100644 --- a/auto-exporters/auto-exporters.gradle +++ b/auto-exporters/auto-exporters.gradle @@ -6,17 +6,20 @@ dependencies { testCompile project(':auto-exporters:opentelemetry-auto-exporters-otlp') testCompile project(':auto-exporters:opentelemetry-auto-exporters-jaeger') testCompile project(':auto-exporters:opentelemetry-auto-exporters-logging') + testCompile project(':auto-exporters:opentelemetry-auto-exporters-zipkin') } tasks.withType(Test).configureEach() { dependsOn ':auto-exporters:opentelemetry-auto-exporters-otlp:shadowJar' dependsOn ':auto-exporters:opentelemetry-auto-exporters-jaeger:shadowJar' dependsOn ':auto-exporters:opentelemetry-auto-exporters-logging:shadowJar' + dependsOn ':auto-exporters:opentelemetry-auto-exporters-zipkin:shadowJar' doFirst { systemProperty 'projectVersion', allprojects.version[0] systemProperty 'adapterRoot', "${rootDir}/auto-exporters" systemProperty 'otlpExporterJar', project(':auto-exporters:opentelemetry-auto-exporters-otlp').tasks.shadowJar.archivePath systemProperty 'jaegerExporterJar', project(':auto-exporters:opentelemetry-auto-exporters-jaeger').tasks.shadowJar.archivePath systemProperty 'loggingExporterJar', project(':auto-exporters:opentelemetry-auto-exporters-logging').tasks.shadowJar.archivePath + systemProperty 'zipkinExporterJar', project(':auto-exporters:opentelemetry-auto-exporters-zipkin').tasks.shadowJar.archivePath } } diff --git a/auto-exporters/src/test/groovy/io/opentelemetry/auto/exporteradapters/ExporterAdaptersTest.groovy b/auto-exporters/src/test/groovy/io/opentelemetry/auto/exporteradapters/ExporterAdaptersTest.groovy index c49e36521b7e..66356210d01d 100644 --- a/auto-exporters/src/test/groovy/io/opentelemetry/auto/exporteradapters/ExporterAdaptersTest.groovy +++ b/auto-exporters/src/test/groovy/io/opentelemetry/auto/exporteradapters/ExporterAdaptersTest.groovy @@ -34,6 +34,9 @@ class ExporterAdaptersTest extends Specification { @Shared def loggingExporterJar = System.getProperty("loggingExporterJar") + @Shared + def zipkinExporterJar = System.getProperty("zipkinExporterJar") + @Shared def jaegerDir = new File("${adapterRoot}/jaeger-adapter/build/libs") @@ -45,7 +48,7 @@ class ExporterAdaptersTest extends Specification { file != null where: - exporter << [otlpExporterJar, jaegerExporterJar, loggingExporterJar] + exporter << [otlpExporterJar, jaegerExporterJar, loggingExporterJar, zipkinExporterJar] } def "test exporter load"() { @@ -53,8 +56,7 @@ class ExporterAdaptersTest extends Specification { def file = new File(exporter) println "Attempting to load ${file.toString()} for ${classname}" assert file.exists(): "${file.toString()} does not exist" - URL[] urls = [file.toURI().toURL()] - def classLoader = new ExporterClassLoader(urls, this.getClass().getClassLoader()) + def classLoader = new ExporterClassLoader(file.toURI().toURL(), this.getClass().getClassLoader()) def serviceLoader = ServiceLoader.load(SpanExporterFactory, classLoader) when: @@ -71,5 +73,6 @@ class ExporterAdaptersTest extends Specification { otlpExporterJar | 'io.opentelemetry.auto.exporters.otlp.OtlpSpanExporterFactory' jaegerExporterJar | 'io.opentelemetry.auto.exporters.jaeger.JaegerExporterFactory' loggingExporterJar | 'io.opentelemetry.auto.exporters.logging.LoggingExporterFactory' + zipkinExporterJar | 'io.opentelemetry.auto.exporters.zipkin.ZipkinExporterFactory' } } diff --git a/auto-exporters/zipkin/src/main/java/io/opentelemetry/auto/exporters/zipkin/ZipkinExporterFactory.java b/auto-exporters/zipkin/src/main/java/io/opentelemetry/auto/exporters/zipkin/ZipkinExporterFactory.java new file mode 100644 index 000000000000..785cd345c7cd --- /dev/null +++ b/auto-exporters/zipkin/src/main/java/io/opentelemetry/auto/exporters/zipkin/ZipkinExporterFactory.java @@ -0,0 +1,42 @@ +/* + * Copyright The OpenTelemetry Authors + * + * 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. + */ +package io.opentelemetry.auto.exporters.zipkin; + +import io.opentelemetry.exporters.zipkin.ZipkinExporterConfiguration; +import io.opentelemetry.exporters.zipkin.ZipkinSpanExporter; +import io.opentelemetry.sdk.contrib.auto.config.Config; +import io.opentelemetry.sdk.contrib.auto.config.SpanExporterFactory; +import io.opentelemetry.sdk.trace.export.SpanExporter; +import zipkin2.reporter.okhttp3.OkHttpSender; + +public class ZipkinExporterFactory implements SpanExporterFactory { + private static final String ZIPKIN_ENDPOINT = "zipkin.endpoint"; + private static final String DEFAULT_ZIPKIN_ENDPOINT = "http://localhost:9411/api/v2/spans"; + + private static final String ZIPKIN_SERVICE_NAME = "zipkin.service.name"; + private static final String DEFAULT_ZIPKIN_SERVICE_NAME = "(unknown service)"; + + @Override + public SpanExporter fromConfig(Config config) { + final String zipkinEndpoint = config.getString(ZIPKIN_ENDPOINT, DEFAULT_ZIPKIN_ENDPOINT); + final String serviceName = config.getString(ZIPKIN_SERVICE_NAME, DEFAULT_ZIPKIN_SERVICE_NAME); + return ZipkinSpanExporter.create( + ZipkinExporterConfiguration.builder() + .setSender(OkHttpSender.create(zipkinEndpoint)) + .setServiceName(serviceName) + .build()); + } +} diff --git a/auto-exporters/zipkin/src/main/resources/META-INF/services/io.opentelemetry.sdk.contrib.auto.config.SpanExporterFactory b/auto-exporters/zipkin/src/main/resources/META-INF/services/io.opentelemetry.sdk.contrib.auto.config.SpanExporterFactory new file mode 100644 index 000000000000..975a26a2da95 --- /dev/null +++ b/auto-exporters/zipkin/src/main/resources/META-INF/services/io.opentelemetry.sdk.contrib.auto.config.SpanExporterFactory @@ -0,0 +1 @@ +io.opentelemetry.auto.exporters.zipkin.ZipkinExporterFactory diff --git a/auto-exporters/zipkin/zipkin.gradle b/auto-exporters/zipkin/zipkin.gradle new file mode 100644 index 000000000000..c3ccbec1777f --- /dev/null +++ b/auto-exporters/zipkin/zipkin.gradle @@ -0,0 +1,17 @@ +plugins { + id "com.github.johnrengelman.shadow" +} + +apply from: "${rootDir}/gradle/java.gradle" + +dependencies { + compile(deps.opentelemetryZipkin) { + exclude group: 'io.opentelemetry', module: 'opentelemetry-sdk' + } + compileOnly deps.opentelemetrySdkAutoConfig + compile group: 'io.zipkin.reporter2', name: 'zipkin-sender-okhttp3', version: '2.12.2' +} + +shadowJar { + archiveClassifier = '' +} diff --git a/benchmark/src/jmh/java/io/opentelemetry/benchmark/ClassRetransformingBenchmark.java b/benchmark/src/jmh/java/io/opentelemetry/benchmark/ClassRetransformingBenchmark.java index da9f189205b7..ec9c7be812e5 100644 --- a/benchmark/src/jmh/java/io/opentelemetry/benchmark/ClassRetransformingBenchmark.java +++ b/benchmark/src/jmh/java/io/opentelemetry/benchmark/ClassRetransformingBenchmark.java @@ -48,6 +48,6 @@ public static class WithAgentMaster extends ClassRetransformingBenchmark {} @Fork( jvmArgsAppend = - "-javaagent:/path/to/opentelemetry-auto-instr-java/java-agent/build/libs/opentelemetry-auto.jar") + "-javaagent:/path/to/opentelemetry-java-instrumentation/java-agent/build/libs/opentelemetry-auto.jar") public static class WithAgent extends ClassRetransformingBenchmark {} } diff --git a/benchmark/src/jmh/java/io/opentelemetry/benchmark/HttpBenchmark.java b/benchmark/src/jmh/java/io/opentelemetry/benchmark/HttpBenchmark.java index c1070418eb26..6f4fddaf4486 100644 --- a/benchmark/src/jmh/java/io/opentelemetry/benchmark/HttpBenchmark.java +++ b/benchmark/src/jmh/java/io/opentelemetry/benchmark/HttpBenchmark.java @@ -67,8 +67,8 @@ public void testMakingRequest(final BenchmarkState state) throws IOException { @Fork( jvmArgsAppend = { - "-javaagent:/path/to/opentelemetry-auto-instr-java/java-agent/build/libs/opentelemetry-auto.jar", - "-Dota.exporter.jar=/path/to/opentelemetry-auto-instr-java/auto-exporters/logging/build/libs/opentelemetry-auto-exporters-logging-or-other-exporter.jar" + "-javaagent:/path/to/opentelemetry-java-instrumentation/java-agent/build/libs/opentelemetry-auto.jar", + "-Dota.exporter.jar=/path/to/opentelemetry-java-instrumentation/auto-exporters/logging/build/libs/opentelemetry-auto-exporters-logging-or-other-exporter.jar" }) public static class WithAgent extends ClassRetransformingBenchmark {} } diff --git a/buildSrc/src/main/groovy/MuzzlePlugin.groovy b/buildSrc/src/main/groovy/MuzzlePlugin.groovy index 5eaa5d1321fa..bfe31a8fd811 100644 --- a/buildSrc/src/main/groovy/MuzzlePlugin.groovy +++ b/buildSrc/src/main/groovy/MuzzlePlugin.groovy @@ -16,7 +16,6 @@ import org.eclipse.aether.transport.http.HttpTransporterFactory import org.eclipse.aether.version.Version import org.gradle.api.Action import org.gradle.api.GradleException -import org.gradle.api.JavaVersion import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.Task @@ -31,11 +30,16 @@ import java.util.regex.Pattern * muzzle task plugin which runs muzzle validation against a range of dependencies. */ class MuzzlePlugin implements Plugin { + /** + * Select a random set of versions to test + */ + private static final int RANGE_COUNT_LIMIT = 10 /** * Remote repositories used to query version ranges and fetch dependencies */ private static final List MUZZLE_REPOS private static final AtomicReference TOOLING_LOADER = new AtomicReference<>() + static { RemoteRepository central = new RemoteRepository.Builder("central", "default", "https://repo1.maven.org/maven2/").build() RemoteRepository sonatype = new RemoteRepository.Builder("sonatype", "default", "https://oss.sonatype.org/content/repositories/releases/").build() @@ -205,20 +209,23 @@ class MuzzlePlugin implements Plugin { /** * Convert a muzzle directive to a list of artifacts */ - private static List muzzleDirectiveToArtifacts(MuzzleDirective muzzleDirective, RepositorySystem system, RepositorySystemSession session) { + private static Set muzzleDirectiveToArtifacts(MuzzleDirective muzzleDirective, RepositorySystem system, RepositorySystemSession session) { final Artifact directiveArtifact = new DefaultArtifact(muzzleDirective.group, muzzleDirective.module, "jar", muzzleDirective.versions) final VersionRangeRequest rangeRequest = new VersionRangeRequest() rangeRequest.setRepositories(MUZZLE_REPOS) rangeRequest.setArtifact(directiveArtifact) final VersionRangeResult rangeResult = system.resolveVersionRange(session, rangeRequest) + final Set versions = rangeResult.versions.toSet() + + limitLargeRanges(rangeResult, versions, muzzleDirective.skipVersions) // println "Range Request: " + rangeRequest // println "Range Result: " + rangeResult - final List allVersionArtifacts = filterVersion(rangeResult.versions, muzzleDirective.skipVersions).collect { version -> + final Set allVersionArtifacts = filterVersion(versions, muzzleDirective.skipVersions).collect { version -> new DefaultArtifact(muzzleDirective.group, muzzleDirective.module, "jar", version.toString()) - } + }.toSet() if (allVersionArtifacts.isEmpty()) { throw new GradleException("No muzzle artifacts found for $muzzleDirective.group:$muzzleDirective.module $muzzleDirective.versions") @@ -230,8 +237,8 @@ class MuzzlePlugin implements Plugin { /** * Create a list of muzzle directives which assert the opposite of the given MuzzleDirective. */ - private static List inverseOf(MuzzleDirective muzzleDirective, RepositorySystem system, RepositorySystemSession session) { - List inverseDirectives = new ArrayList<>() + private static Set inverseOf(MuzzleDirective muzzleDirective, RepositorySystem system, RepositorySystemSession session) { + Set inverseDirectives = new HashSet<>() final Artifact allVerisonsArtifact = new DefaultArtifact(muzzleDirective.group, muzzleDirective.module, "jar", "[,)") final Artifact directiveArtifact = new DefaultArtifact(muzzleDirective.group, muzzleDirective.module, "jar", muzzleDirective.versions) @@ -246,9 +253,10 @@ class MuzzlePlugin implements Plugin { rangeRequest.setRepositories(MUZZLE_REPOS) rangeRequest.setArtifact(directiveArtifact) final VersionRangeResult rangeResult = system.resolveVersionRange(session, rangeRequest) + final Set versions = rangeResult.versions.toSet() - filterVersion(allRangeResult.versions, muzzleDirective.skipVersions).collect { version -> - if (!rangeResult.versions.contains(version)) { + filterVersion(allRangeResult.versions.toSet(), muzzleDirective.skipVersions).collect { version -> + if (!versions.contains(version)) { final MuzzleDirective inverseDirective = new MuzzleDirective() inverseDirective.group = muzzleDirective.group inverseDirective.module = muzzleDirective.module @@ -261,6 +269,21 @@ class MuzzlePlugin implements Plugin { return inverseDirectives } + private static void limitLargeRanges(VersionRangeResult result, Set versions, Set skipVersions) { + List copy = new ArrayList<>(versions) + copy.removeAll(skipVersions) + Collections.shuffle(copy) + while (RANGE_COUNT_LIMIT <= copy.size()) { + Version version = copy.pop() + if (!(version.equals(result.lowestVersion) || version.equals(result.highestVersion))) { + skipVersions.add(version.toString()) + } + } + if (skipVersions.size() > 0) { + println "Muzzle skipping " + skipVersions.size() + " versions" + } + } + /** * Configure a muzzle task to pass or fail a given version. * @@ -361,7 +384,7 @@ class MuzzlePlugin implements Plugin { /** * Filter out snapshot-type builds from versions list. */ - private static filterVersion(List list, Set skipVersions) { + private static filterVersion(Set list, Set skipVersions) { list.removeIf { def version = it.toString().toLowerCase() return version.contains("rc") || diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle index 458309e6789e..5d00a0b24d75 100644 --- a/gradle/dependencies.gradle +++ b/gradle/dependencies.gradle @@ -12,7 +12,6 @@ ext { slf4j : "1.7.30", guava : "20.0", // Last version to support Java 7 - okhttp : "3.12.10", // 3.12.x is last version to support Java7 spock : "1.3-groovy-$spockGroovyVer", groovy : groovyVer, @@ -33,6 +32,7 @@ ext { opentelemetrySdkAutoConfig : dependencies.create(group: 'io.opentelemetry', name: 'opentelemetry-sdk-contrib-auto-config', version: versions.opentelemetry), opentelemetryJaeger : dependencies.create(group: 'io.opentelemetry', name: 'opentelemetry-exporters-jaeger', version: versions.opentelemetry), opentelemetryOtlp : dependencies.create(group: 'io.opentelemetry', name: 'opentelemetry-exporters-otlp', version: versions.opentelemetry), + opentelemetryZipkin : dependencies.create(group: 'io.opentelemetry', name: 'opentelemetry-exporters-zipkin', version: versions.opentelemetry), // General slf4j : "org.slf4j:slf4j-api:${versions.slf4j}", diff --git a/gradle/java.gradle b/gradle/java.gradle index 6ecec51a77bc..2d08d313ec53 100644 --- a/gradle/java.gradle +++ b/gradle/java.gradle @@ -1,6 +1,10 @@ import java.time.Duration -apply plugin: 'java' +if (project.path.startsWith(":instrumentation-core")) { + apply plugin: 'java-library' +} else { + apply plugin: 'java' +} apply plugin: 'groovy' apply from: "$rootDir/gradle/spotless.gradle" @@ -123,7 +127,7 @@ jar { "Implementation-Title": project.name, "Implementation-Version": project.version, "Implementation-Vendor": "OpenTelemetry", - "Implementation-URL": "https://github.com/open-telemetry/opentelemetry-auto-instr-java", + "Implementation-URL": "https://github.com/open-telemetry/opentelemetry-java-instrumentation", ) } } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index f3d88b1c2faf..490fda8577df 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 6623300bebd0..fd0c5a38e92d 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.4-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/instrumentation-core/README.md b/instrumentation-core/README.md new file mode 100644 index 000000000000..8b919c8b349a --- /dev/null +++ b/instrumentation-core/README.md @@ -0,0 +1,17 @@ +# Instrumentation Core + +These modules for the core logic for library instrumentation. [instrumentation](../instrumentation) +should add core logic here which can be set up manually by a user, and agent-specific code +for automatically setting up the instrumentation in that folder. + +Note, we are currently working on separating instrumentatin projects so that their core parts can +be accessed by users not using the agent. Due to the current Gradle setup, we have these two top-level +folders, instrumentation and instrumentation-core, but eventually we want to move to flattening them +into something like + +``` +instrumentation/ + aws-sdk-2.2/ + aws-sdk-2.2/ + aws-sdk-2.2-auto/ +``` diff --git a/instrumentation-core/aws-sdk/aws-sdk-2.2-core/aws-sdk-2.2-core.gradle b/instrumentation-core/aws-sdk/aws-sdk-2.2-core/aws-sdk-2.2-core.gradle new file mode 100644 index 000000000000..ab14ae9b22d0 --- /dev/null +++ b/instrumentation-core/aws-sdk/aws-sdk-2.2-core/aws-sdk-2.2-core.gradle @@ -0,0 +1,46 @@ +ext { + minJavaVersionForTests = JavaVersion.VERSION_1_8 +} + +apply from: "${rootDir}/gradle/java.gradle" +apply plugin: 'org.unbroken-dome.test-sets' + +testSets { + latestDepTest +} + +dependencies { + // TODO(anuraaga): We currently include common instrumentation logic like decorators in the + // bootstrap, but we need to move it out so manual instrumentation does not depend on code from + // the agent, like Agent. + api project(':auto-bootstrap') + + api deps.opentelemetryApi + + api group: 'software.amazon.awssdk', name: 'aws-core', version: '2.2.0' + + testCompile project(':testing') + + // Include httpclient instrumentation for testing because it is a dependency for aws-sdk. + testCompile project(':instrumentation:apache-httpclient:apache-httpclient-4.0') + // Also include netty instrumentation because it is used by aws async client + testCompile project(':instrumentation:netty:netty-4.1') + testCompile group: 'software.amazon.awssdk', name: 'apache-client', version: '2.2.0' + testCompile group: 'software.amazon.awssdk', name: 's3', version: '2.2.0' + testCompile group: 'software.amazon.awssdk', name: 'rds', version: '2.2.0' + testCompile group: 'software.amazon.awssdk', name: 'ec2', version: '2.2.0' + testCompile group: 'software.amazon.awssdk', name: 'sqs', version: '2.2.0' + testCompile group: 'software.amazon.awssdk', name: 'dynamodb', version: '2.2.0' + testCompile group: 'software.amazon.awssdk', name: 'kinesis', version: '2.2.0' + + latestDepTestCompile project(':instrumentation:apache-httpclient:apache-httpclient-4.0') + latestDepTestCompile project(':instrumentation:netty:netty-4.1') + + latestDepTestCompile group: 'software.amazon.awssdk', name: 'apache-client', version: '+' + latestDepTestCompile group: 'software.amazon.awssdk', name: 's3', version: '+' + latestDepTestCompile group: 'software.amazon.awssdk', name: 'rds', version: '+' + latestDepTestCompile group: 'software.amazon.awssdk', name: 'ec2', version: '+' + latestDepTestCompile group: 'software.amazon.awssdk', name: 'sqs', version: '+' + latestDepTestCompile group: 'software.amazon.awssdk', name: 'dynamodb', version: '+' + latestDepTestCompile group: 'software.amazon.awssdk', name: 'kinesis', version: '+' +} diff --git a/instrumentation-core/aws-sdk/aws-sdk-2.2-core/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AwsSdk.java b/instrumentation-core/aws-sdk/aws-sdk-2.2-core/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AwsSdk.java new file mode 100644 index 000000000000..92e5c6330fa2 --- /dev/null +++ b/instrumentation-core/aws-sdk/aws-sdk-2.2-core/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AwsSdk.java @@ -0,0 +1,77 @@ +/* + * Copyright The OpenTelemetry Authors + * + * 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. + */ +package io.opentelemetry.instrumentation.awssdk.v2_2; + +import static io.opentelemetry.instrumentation.awssdk.v2_2.TracingExecutionInterceptor.SPAN_ATTRIBUTE; + +import io.opentelemetry.OpenTelemetry; +import io.opentelemetry.trace.Span; +import io.opentelemetry.trace.Span.Kind; +import io.opentelemetry.trace.Tracer; +import software.amazon.awssdk.core.interceptor.ExecutionAttributes; +import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; + +/** + * Entrypoint to OpenTelemetry instrumentation of the AWS SDK. Register the {@link + * ExecutionInterceptor} returned by {@link #newInterceptor()} with an SDK client to have all + * requests traced. + * + *

{@code
+ * DynamoDbClient dynamoDb = DynamoDbClient.builder()
+ *     .overrideConfiguration(ClientOverrideConfiguration.builder()
+ *         .addExecutionInterceptor(AwsSdk.newInterceptor())
+ *         .build())
+ *     .build();
+ * }
+ */ +public class AwsSdk { + + private static final Tracer TRACER = + OpenTelemetry.getTracerProvider().get("io.opentelemetry.auto.aws-sdk-2.2"); + + /** Returns the {@link Tracer} used to instrument the AWS SDK. */ + public static Tracer tracer() { + return TRACER; + } + + /** + * Returns an {@link ExecutionInterceptor} that can be used with an {@link + * software.amazon.awssdk.http.SdkHttpClient} to trace SDK requests. Spans are created with the + * kind {@link Kind#CLIENT}. If you also instrument the HTTP calls made by the SDK, e.g., by + * adding Apache HTTP client or Netty instrumentation, you may want to use {@link + * #newInterceptor(Kind)} with {@link Kind#INTERNAL} instead. + */ + public static ExecutionInterceptor newInterceptor() { + return newInterceptor(Kind.CLIENT); + } + + /** + * Returns an {@link ExecutionInterceptor} that can be used with an {@link + * software.amazon.awssdk.http.SdkHttpClient} to trace SDK requests. Spans are created with the + * provided {@link Kind}. + */ + public static ExecutionInterceptor newInterceptor(Kind kind) { + return new TracingExecutionInterceptor(kind); + } + + /** + * Returns the {@link Span} stored in the {@link ExecutionAttributes}, or {@code null} if there is + * no span set. + */ + public static Span getSpanFromAttributes(ExecutionAttributes attributes) { + return attributes.getAttribute(SPAN_ATTRIBUTE); + } +} diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/src/main/java8/io/opentelemetry/auto/instrumentation/awssdk/v2_2/AwsSdkClientDecorator.java b/instrumentation-core/aws-sdk/aws-sdk-2.2-core/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AwsSdkClientDecorator.java similarity index 81% rename from instrumentation/aws-sdk/aws-sdk-2.2/src/main/java8/io/opentelemetry/auto/instrumentation/awssdk/v2_2/AwsSdkClientDecorator.java rename to instrumentation-core/aws-sdk/aws-sdk-2.2-core/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AwsSdkClientDecorator.java index 6e522c9d9539..3e3403075b63 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/src/main/java8/io/opentelemetry/auto/instrumentation/awssdk/v2_2/AwsSdkClientDecorator.java +++ b/instrumentation-core/aws-sdk/aws-sdk-2.2-core/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AwsSdkClientDecorator.java @@ -13,12 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.opentelemetry.auto.instrumentation.awssdk.v2_2; +package io.opentelemetry.instrumentation.awssdk.v2_2; -import io.opentelemetry.OpenTelemetry; import io.opentelemetry.auto.bootstrap.instrumentation.decorator.HttpClientDecorator; import io.opentelemetry.trace.Span; -import io.opentelemetry.trace.Tracer; import java.net.URI; import software.amazon.awssdk.awscore.AwsResponse; import software.amazon.awssdk.core.SdkRequest; @@ -28,15 +26,12 @@ import software.amazon.awssdk.http.SdkHttpRequest; import software.amazon.awssdk.http.SdkHttpResponse; -public class AwsSdkClientDecorator extends HttpClientDecorator { - public static final AwsSdkClientDecorator DECORATE = new AwsSdkClientDecorator(); - - public static final Tracer TRACER = - OpenTelemetry.getTracerProvider().get("io.opentelemetry.auto.aws-sdk-2.2"); +final class AwsSdkClientDecorator extends HttpClientDecorator { + static final AwsSdkClientDecorator DECORATE = new AwsSdkClientDecorator(); static final String COMPONENT_NAME = "java-aws-sdk"; - public Span onSdkRequest(final Span span, final SdkRequest request) { + Span onSdkRequest(final Span span, final SdkRequest request) { // S3 request .getValueForField("Bucket", String.class) @@ -59,14 +54,14 @@ public Span onSdkRequest(final Span span, final SdkRequest request) { return span; } - public String spanName(final ExecutionAttributes attributes) { + String spanName(final ExecutionAttributes attributes) { final String awsServiceName = attributes.getAttribute(SdkExecutionAttribute.SERVICE_NAME); final String awsOperation = attributes.getAttribute(SdkExecutionAttribute.OPERATION_NAME); return awsServiceName + "." + awsOperation; } - public Span onAttributes(final Span span, final ExecutionAttributes attributes) { + Span onAttributes(final Span span, final ExecutionAttributes attributes) { final String awsServiceName = attributes.getAttribute(SdkExecutionAttribute.SERVICE_NAME); final String awsOperation = attributes.getAttribute(SdkExecutionAttribute.OPERATION_NAME); @@ -79,7 +74,7 @@ public Span onAttributes(final Span span, final ExecutionAttributes attributes) } // Not overriding the super. Should call both with each type of response. - public Span onResponse(final Span span, final SdkResponse response) { + Span onSdkResponse(final Span span, final SdkResponse response) { if (response instanceof AwsResponse) { span.setAttribute("aws.requestId", ((AwsResponse) response).responseMetadata().requestId()); } diff --git a/instrumentation-core/aws-sdk/aws-sdk-2.2-core/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/TracingExecutionInterceptor.java b/instrumentation-core/aws-sdk/aws-sdk-2.2-core/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/TracingExecutionInterceptor.java new file mode 100644 index 000000000000..b322ab2a8d1a --- /dev/null +++ b/instrumentation-core/aws-sdk/aws-sdk-2.2-core/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/TracingExecutionInterceptor.java @@ -0,0 +1,94 @@ +/* + * Copyright The OpenTelemetry Authors + * + * 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. + */ +package io.opentelemetry.instrumentation.awssdk.v2_2; + +import static io.opentelemetry.instrumentation.awssdk.v2_2.AwsSdkClientDecorator.DECORATE; + +import io.opentelemetry.trace.Span; +import io.opentelemetry.trace.Span.Kind; +import software.amazon.awssdk.core.interceptor.Context; +import software.amazon.awssdk.core.interceptor.ExecutionAttribute; +import software.amazon.awssdk.core.interceptor.ExecutionAttributes; +import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; + +/** AWS request execution interceptor */ +final class TracingExecutionInterceptor implements ExecutionInterceptor { + + static final ExecutionAttribute SPAN_ATTRIBUTE = + new ExecutionAttribute<>("io.opentelemetry.auto.Span"); + + private final Kind kind; + + TracingExecutionInterceptor(Kind kind) { + this.kind = kind; + } + + @Override + public void beforeExecution( + final Context.BeforeExecution context, final ExecutionAttributes executionAttributes) { + final Span span = + AwsSdk.tracer() + .spanBuilder(DECORATE.spanName(executionAttributes)) + .setSpanKind(kind) + .startSpan(); + DECORATE.afterStart(span); + executionAttributes.putAttribute(SPAN_ATTRIBUTE, span); + } + + @Override + public void afterMarshalling( + final Context.AfterMarshalling context, final ExecutionAttributes executionAttributes) { + final Span span = executionAttributes.getAttribute(SPAN_ATTRIBUTE); + + if (span != null) { + DECORATE.onRequest(span, context.httpRequest()); + DECORATE.onSdkRequest(span, context.request()); + DECORATE.onAttributes(span, executionAttributes); + } + } + + @Override + public void afterExecution( + final Context.AfterExecution context, final ExecutionAttributes executionAttributes) { + final Span span = executionAttributes.getAttribute(SPAN_ATTRIBUTE); + if (span != null) { + try { + executionAttributes.putAttribute(SPAN_ATTRIBUTE, null); + // Call onResponse on both types of responses: + DECORATE.onSdkResponse(span, context.response()); + DECORATE.onResponse(span, context.httpResponse()); + DECORATE.beforeFinish(span); + } finally { + span.end(); + } + } + } + + @Override + public void onExecutionFailure( + final Context.FailedExecution context, final ExecutionAttributes executionAttributes) { + final Span span = executionAttributes.getAttribute(SPAN_ATTRIBUTE); + if (span != null) { + try { + executionAttributes.putAttribute(SPAN_ATTRIBUTE, null); + DECORATE.onError(span, context.exception()); + DECORATE.beforeFinish(span); + } finally { + span.end(); + } + } + } +} diff --git a/instrumentation-core/aws-sdk/aws-sdk-2.2-core/src/test/groovy/Aws2ClientCoreTest.groovy b/instrumentation-core/aws-sdk/aws-sdk-2.2-core/src/test/groovy/Aws2ClientCoreTest.groovy new file mode 100644 index 000000000000..9bc83b33fa50 --- /dev/null +++ b/instrumentation-core/aws-sdk/aws-sdk-2.2-core/src/test/groovy/Aws2ClientCoreTest.groovy @@ -0,0 +1,315 @@ +/* + * Copyright The OpenTelemetry Authors + * + * 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. + */ +import io.opentelemetry.auto.bootstrap.instrumentation.decorator.HttpClientDecorator +import io.opentelemetry.auto.instrumentation.api.MoreTags +import io.opentelemetry.auto.instrumentation.api.Tags +import io.opentelemetry.auto.test.InstrumentationTestRunner +import io.opentelemetry.instrumentation.awssdk.v2_2.AwsSdk +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider +import software.amazon.awssdk.core.ResponseInputStream +import software.amazon.awssdk.core.async.AsyncResponseTransformer +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration +import software.amazon.awssdk.core.exception.SdkClientException +import software.amazon.awssdk.http.apache.ApacheHttpClient +import software.amazon.awssdk.regions.Region +import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient +import software.amazon.awssdk.services.dynamodb.DynamoDbClient +import software.amazon.awssdk.services.dynamodb.model.CreateTableRequest +import software.amazon.awssdk.services.ec2.Ec2AsyncClient +import software.amazon.awssdk.services.ec2.Ec2Client +import software.amazon.awssdk.services.kinesis.KinesisClient +import software.amazon.awssdk.services.kinesis.model.DeleteStreamRequest +import software.amazon.awssdk.services.rds.RdsAsyncClient +import software.amazon.awssdk.services.rds.RdsClient +import software.amazon.awssdk.services.rds.model.DeleteOptionGroupRequest +import software.amazon.awssdk.services.s3.S3AsyncClient +import software.amazon.awssdk.services.s3.S3Client +import software.amazon.awssdk.services.s3.model.CreateBucketRequest +import software.amazon.awssdk.services.s3.model.GetObjectRequest +import software.amazon.awssdk.services.sqs.SqsAsyncClient +import software.amazon.awssdk.services.sqs.SqsClient +import software.amazon.awssdk.services.sqs.model.CreateQueueRequest +import software.amazon.awssdk.services.sqs.model.SendMessageRequest +import spock.lang.AutoCleanup +import spock.lang.Shared + +import java.time.Duration +import java.util.concurrent.Future +import java.util.concurrent.atomic.AtomicReference + +import static io.opentelemetry.auto.test.server.http.TestHttpServer.httpServer +import static io.opentelemetry.trace.Span.Kind.CLIENT + +class Aws2ClientCoreTest extends InstrumentationTestRunner { + + private static final StaticCredentialsProvider CREDENTIALS_PROVIDER = StaticCredentialsProvider + .create(AwsBasicCredentials.create("my-access-key", "my-secret-key")) + + @Shared + def responseBody = new AtomicReference() + + @AutoCleanup + @Shared + def server = httpServer { + handlers { + all { + response.status(200).send(responseBody.get()) + } + } + } + + def "send #operation request with builder {#builder.class.getName()} mocked response"() { + setup: + def client = builder + .endpointOverride(server.address) + .region(Region.AP_NORTHEAST_1) + .credentialsProvider(CREDENTIALS_PROVIDER) + .overrideConfiguration(ClientOverrideConfiguration.builder() + .addExecutionInterceptor(AwsSdk.newInterceptor()) + .build()) + .build() + responseBody.set(body) + def response = call.call(client) + + if (response instanceof Future) { + response = response.get() + } + + expect: + response != null + response.class.simpleName.startsWith(operation) || response instanceof ResponseInputStream + + assertTraces(1) { + trace(0, 1) { + span(0) { + operationName "$service.$operation" + spanKind CLIENT + errored false + parent() + tags { + "$MoreTags.NET_PEER_NAME" "localhost" + "$MoreTags.NET_PEER_PORT" server.address.port + "$Tags.HTTP_URL" { it.startsWith("${server.address}${path}") } + "$Tags.HTTP_METHOD" "$method" + "$Tags.HTTP_STATUS" 200 + "aws.service" "$service" + "aws.operation" "${operation}" + "aws.agent" "java-aws-sdk" + "aws.requestId" "$requestId" + if (service == "S3") { + "aws.bucket.name" "somebucket" + } else if (service == "Sqs" && operation == "CreateQueue") { + "aws.queue.name" "somequeue" + } else if (service == "Sqs" && operation == "SendMessage") { + "aws.queue.url" "someurl" + } else if (service == "DynamoDb") { + "aws.table.name" "sometable" + } else if (service == "Kinesis") { + "aws.stream.name" "somestream" + } + } + } + } + } + server.lastRequest.headers.get("traceparent") == null + + where: + service | operation | method | path | requestId | builder | call | body + "S3" | "CreateBucket" | "PUT" | "/somebucket" | "UNKNOWN" | S3Client.builder() | { c -> c.createBucket(CreateBucketRequest.builder().bucket("somebucket").build()) } | "" + "S3" | "GetObject" | "GET" | "/somebucket/somekey" | "UNKNOWN" | S3Client.builder() | { c -> c.getObject(GetObjectRequest.builder().bucket("somebucket").key("somekey").build()) } | "" + "DynamoDb" | "CreateTable" | "POST" | "/" | "UNKNOWN" | DynamoDbClient.builder() | { c -> c.createTable(CreateTableRequest.builder().tableName("sometable").build()) } | "" + "Kinesis" | "DeleteStream" | "POST" | "/" | "UNKNOWN" | KinesisClient.builder() | { c -> c.deleteStream(DeleteStreamRequest.builder().streamName("somestream").build()) } | "" + "Sqs" | "CreateQueue" | "POST" | "/" | "7a62c49f-347e-4fc4-9331-6e8e7a96aa73" | SqsClient.builder() | { c -> c.createQueue(CreateQueueRequest.builder().queueName("somequeue").build()) } | """ + + https://queue.amazonaws.com/123456789012/MyQueue + 7a62c49f-347e-4fc4-9331-6e8e7a96aa73 + + """ + "Sqs" | "SendMessage" | "POST" | "/" | "27daac76-34dd-47df-bd01-1f6e873584a0" | SqsClient.builder() | { c -> c.sendMessage(SendMessageRequest.builder().queueUrl("someurl").messageBody("").build()) } | """ + + + d41d8cd98f00b204e9800998ecf8427e + 3ae8f24a165a8cedc005670c81a27295 + 5fea7756-0ea4-451a-a703-a558b933e274 + + 27daac76-34dd-47df-bd01-1f6e873584a0 + + """ + "Ec2" | "AllocateAddress" | "POST" | "/" | "59dbff89-35bd-4eac-99ed-be587EXAMPLE" | Ec2Client.builder() | { c -> c.allocateAddress() } | """ + + 59dbff89-35bd-4eac-99ed-be587EXAMPLE + 192.0.2.1 + standard + + """ + "Rds" | "DeleteOptionGroup" | "POST" | "/" | "0ac9cda2-bbf4-11d3-f92b-31fa5e8dbc99" | RdsClient.builder() | { c -> c.deleteOptionGroup(DeleteOptionGroupRequest.builder().build()) } | """ + + 0ac9cda2-bbf4-11d3-f92b-31fa5e8dbc99 + + """ + } + + def "send #operation async request with builder {#builder.class.getName()} mocked response"() { + setup: + def client = builder + .endpointOverride(server.address) + .region(Region.AP_NORTHEAST_1) + .credentialsProvider(CREDENTIALS_PROVIDER) + .overrideConfiguration(ClientOverrideConfiguration.builder() + .addExecutionInterceptor(AwsSdk.newInterceptor()) + .build()) + .build() + responseBody.set(body) + def response = call.call(client) + + if (response instanceof Future) { + response = response.get() + } + + expect: + response != null + + assertTraces(1) { + trace(0, 1) { + span(0) { + operationName "$service.$operation" + spanKind CLIENT + errored false + parent() + tags { + "$MoreTags.NET_PEER_NAME" "localhost" + "$MoreTags.NET_PEER_PORT" server.address.port + "$Tags.HTTP_URL" { it.startsWith("${server.address}${path}") } + "$Tags.HTTP_METHOD" "$method" + "$Tags.HTTP_STATUS" 200 + "aws.service" "$service" + "aws.operation" "${operation}" + "aws.agent" "java-aws-sdk" + "aws.requestId" "$requestId" + if (service == "S3") { + "aws.bucket.name" "somebucket" + } else if (service == "Sqs" && operation == "CreateQueue") { + "aws.queue.name" "somequeue" + } else if (service == "Sqs" && operation == "SendMessage") { + "aws.queue.url" "someurl" + } else if (service == "DynamoDb") { + "aws.table.name" "sometable" + } else if (service == "Kinesis") { + "aws.stream.name" "somestream" + } + } + } + } + } + server.lastRequest.headers.get("traceparent") == null + + where: + service | operation | method | path | requestId | builder | call | body + "S3" | "CreateBucket" | "PUT" | "/somebucket" | "UNKNOWN" | S3AsyncClient.builder() | { c -> c.createBucket(CreateBucketRequest.builder().bucket("somebucket").build()) } | "" + "S3" | "GetObject" | "GET" | "/somebucket/somekey" | "UNKNOWN" | S3AsyncClient.builder() | { c -> c.getObject(GetObjectRequest.builder().bucket("somebucket").key("somekey").build(), AsyncResponseTransformer.toBytes()) } | "1234567890" + "DynamoDb" | "CreateTable" | "POST" | "/" | "UNKNOWN" | DynamoDbAsyncClient.builder() | { c -> c.createTable(CreateTableRequest.builder().tableName("sometable").build()) } | "" + // Kinesis seems to expect an http2 response which is incompatible with our test server. + // "Kinesis" | "DeleteStream" | "POST" | "/" | "UNKNOWN" | KinesisAsyncClient.builder() | { c -> c.deleteStream(DeleteStreamRequest.builder().streamName("somestream").build()) } | "" + "Sqs" | "CreateQueue" | "POST" | "/" | "7a62c49f-347e-4fc4-9331-6e8e7a96aa73" | SqsAsyncClient.builder() | { c -> c.createQueue(CreateQueueRequest.builder().queueName("somequeue").build()) } | """ + + https://queue.amazonaws.com/123456789012/MyQueue + 7a62c49f-347e-4fc4-9331-6e8e7a96aa73 + + """ + "Sqs" | "SendMessage" | "POST" | "/" | "27daac76-34dd-47df-bd01-1f6e873584a0" | SqsAsyncClient.builder() | { c -> c.sendMessage(SendMessageRequest.builder().queueUrl("someurl").messageBody("").build()) } | """ + + + d41d8cd98f00b204e9800998ecf8427e + 3ae8f24a165a8cedc005670c81a27295 + 5fea7756-0ea4-451a-a703-a558b933e274 + + 27daac76-34dd-47df-bd01-1f6e873584a0 + + """ + "Ec2" | "AllocateAddress" | "POST" | "/" | "59dbff89-35bd-4eac-99ed-be587EXAMPLE" | Ec2AsyncClient.builder() | { c -> c.allocateAddress() } | """ + + 59dbff89-35bd-4eac-99ed-be587EXAMPLE + 192.0.2.1 + standard + + """ + "Rds" | "DeleteOptionGroup" | "POST" | "/" | "0ac9cda2-bbf4-11d3-f92b-31fa5e8dbc99" | RdsAsyncClient.builder() | { c -> c.deleteOptionGroup(DeleteOptionGroupRequest.builder().build()) } | """ + + 0ac9cda2-bbf4-11d3-f92b-31fa5e8dbc99 + + """ + } + + // TODO(anuraaga): Without AOP instrumentation of the HTTP client, we cannot model retries as + // spans because of https://github.com/aws/aws-sdk-java-v2/issues/1741. We should at least tweak + // the instrumentation to add Events for retries instead. + def "timeout and retry errors not captured"() { + setup: + def server = httpServer { + handlers { + all { + Thread.sleep(500) + response.status(200).send() + } + } + } + def client = S3Client.builder() + .endpointOverride(server.address) + .region(Region.AP_NORTHEAST_1) + .credentialsProvider(CREDENTIALS_PROVIDER) + .overrideConfiguration(ClientOverrideConfiguration.builder() + .addExecutionInterceptor(AwsSdk.newInterceptor()) + .build()) + .httpClientBuilder(ApacheHttpClient.builder().socketTimeout(Duration.ofMillis(50))) + .build() + + when: + client.getObject(GetObjectRequest.builder().bucket("somebucket").key("somekey").build()) + + then: + thrown SdkClientException + + assertTraces(1) { + trace(0, 1) { + span(0) { + operationName "S3.GetObject" + spanKind CLIENT + errored true + parent() + tags { + "$MoreTags.NET_PEER_NAME" "localhost" + "$MoreTags.NET_PEER_PORT" server.address.port + "$Tags.HTTP_URL" "$server.address/somebucket/somekey" + "$Tags.HTTP_METHOD" "GET" + "aws.service" "S3" + "aws.operation" "GetObject" + "aws.agent" "java-aws-sdk" + "aws.bucket.name" "somebucket" + errorTags SdkClientException, "Unable to execute HTTP request: Read timed out" + } + } + } + } + + cleanup: + server.close() + } + + String expectedOperationName(String method) { + return method != null ? "HTTP $method" : HttpClientDecorator.DEFAULT_SPAN_NAME + } +} diff --git a/instrumentation/akka-http-10.0/akka-http-10.0.gradle b/instrumentation/akka-http-10.0/akka-http-10.0.gradle index ef4e70df9673..afa5fb12422c 100644 --- a/instrumentation/akka-http-10.0/akka-http-10.0.gradle +++ b/instrumentation/akka-http-10.0/akka-http-10.0.gradle @@ -112,6 +112,7 @@ compileLatestDepTestGroovy { // They all use the same constraints as the main test source set. lagomTest { onlyIf { - executable == toExecutable(findJavaHome(JavaVersion.VERSION_1_8)) + String java8Home = findJavaHome(JavaVersion.VERSION_1_8) + java8Home != null && executable == toExecutable(java8Home) } } diff --git a/instrumentation/apache-httpasyncclient-4.0/src/test/groovy/ApacheHttpAsyncClientNullCallbackTest.groovy b/instrumentation/apache-httpasyncclient-4.0/src/test/groovy/ApacheHttpAsyncClientNullCallbackTest.groovy index 0d01eb0fb045..0f39c5e5977c 100644 --- a/instrumentation/apache-httpasyncclient-4.0/src/test/groovy/ApacheHttpAsyncClientNullCallbackTest.groovy +++ b/instrumentation/apache-httpasyncclient-4.0/src/test/groovy/ApacheHttpAsyncClientNullCallbackTest.groovy @@ -55,7 +55,7 @@ class ApacheHttpAsyncClientNullCallbackTest extends HttpClientTest { if (callback != null) { callback() } - return 200 + return future.get().statusLine.statusCode } @Override diff --git a/instrumentation/apache-httpasyncclient-4.0/src/test/groovy/ApacheHttpAsyncClientTest.groovy b/instrumentation/apache-httpasyncclient-4.0/src/test/groovy/ApacheHttpAsyncClientTest.groovy index 32c4354debe9..024915efc56d 100644 --- a/instrumentation/apache-httpasyncclient-4.0/src/test/groovy/ApacheHttpAsyncClientTest.groovy +++ b/instrumentation/apache-httpasyncclient-4.0/src/test/groovy/ApacheHttpAsyncClientTest.groovy @@ -49,7 +49,8 @@ class ApacheHttpAsyncClientTest extends HttpClientTest { request.addHeader(new BasicHeader(it.key, it.value)) } - def latch = callback == null ? null : new CountDownLatch(1) + def latch = new CountDownLatch(callback == null ? 0 : 1) + def handler = callback == null ? null : new FutureCallback() { @Override @@ -72,10 +73,7 @@ class ApacheHttpAsyncClientTest extends HttpClientTest { def future = client.execute(request, handler) def response = future.get() response.entity?.content?.close() // Make sure the connection is closed. - if (callback != null) { - // need to wait for callback to complete in case test is expecting span from it - latch.await() - } + latch.await() response.statusLine.statusCode } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/aws-sdk-2.2.gradle b/instrumentation/aws-sdk/aws-sdk-2.2/aws-sdk-2.2.gradle index 17a418105f2b..8cd5b5158181 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/aws-sdk-2.2.gradle +++ b/instrumentation/aws-sdk/aws-sdk-2.2/aws-sdk-2.2.gradle @@ -10,7 +10,6 @@ muzzle { group = "software.amazon.awssdk" module = "aws-core" versions = "[2.2.0,)" - assertInverse = true } } @@ -19,6 +18,8 @@ testSets { } dependencies { + compile project(':instrumentation-core:aws-sdk:aws-sdk-2.2-core') + compileOnly group: 'software.amazon.awssdk', name: 'aws-core', version: '2.2.0' // Include httpclient instrumentation for testing because it is a dependency for aws-sdk. diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/src/main/java/io/opentelemetry/auto/instrumentation/awssdk/v2_2/AbstractAwsClientInstrumentation.java b/instrumentation/aws-sdk/aws-sdk-2.2/src/main/java/io/opentelemetry/auto/instrumentation/awssdk/v2_2/AbstractAwsClientInstrumentation.java index 5774b6633d80..89c7205f6d34 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/src/main/java/io/opentelemetry/auto/instrumentation/awssdk/v2_2/AbstractAwsClientInstrumentation.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/src/main/java/io/opentelemetry/auto/instrumentation/awssdk/v2_2/AbstractAwsClientInstrumentation.java @@ -27,9 +27,10 @@ public AbstractAwsClientInstrumentation() { @Override public String[] helperClassNames() { return new String[] { - packageName + ".AwsSdkClientDecorator", + "io.opentelemetry.instrumentation.awssdk.v2_2.AwsSdk", + "io.opentelemetry.instrumentation.awssdk.v2_2.AwsSdkClientDecorator", packageName + ".TracingExecutionInterceptor", - packageName + ".TracingExecutionInterceptor$ScopeHolder" + packageName + ".TracingExecutionInterceptor$ScopeHolder", }; } } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/src/main/java8/io/opentelemetry/auto/instrumentation/awssdk/v2_2/TracingExecutionInterceptor.java b/instrumentation/aws-sdk/aws-sdk-2.2/src/main/java8/io/opentelemetry/auto/instrumentation/awssdk/v2_2/TracingExecutionInterceptor.java index 81250106ff24..71c639bcce31 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/src/main/java8/io/opentelemetry/auto/instrumentation/awssdk/v2_2/TracingExecutionInterceptor.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/src/main/java8/io/opentelemetry/auto/instrumentation/awssdk/v2_2/TracingExecutionInterceptor.java @@ -15,21 +15,46 @@ */ package io.opentelemetry.auto.instrumentation.awssdk.v2_2; -import static io.opentelemetry.auto.instrumentation.awssdk.v2_2.AwsSdkClientDecorator.DECORATE; -import static io.opentelemetry.auto.instrumentation.awssdk.v2_2.AwsSdkClientDecorator.TRACER; import static io.opentelemetry.trace.TracingContextUtils.currentContextWith; import io.opentelemetry.context.Scope; +import io.opentelemetry.instrumentation.awssdk.v2_2.AwsSdk; import io.opentelemetry.trace.Span; +import io.opentelemetry.trace.Span.Kind; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.util.Optional; import java.util.function.Consumer; +import org.reactivestreams.Publisher; +import software.amazon.awssdk.core.SdkRequest; +import software.amazon.awssdk.core.SdkResponse; +import software.amazon.awssdk.core.async.AsyncRequestBody; import software.amazon.awssdk.core.client.builder.SdkClientBuilder; import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; -import software.amazon.awssdk.core.interceptor.Context; -import software.amazon.awssdk.core.interceptor.ExecutionAttribute; +import software.amazon.awssdk.core.interceptor.Context.AfterExecution; +import software.amazon.awssdk.core.interceptor.Context.AfterMarshalling; +import software.amazon.awssdk.core.interceptor.Context.AfterTransmission; +import software.amazon.awssdk.core.interceptor.Context.AfterUnmarshalling; +import software.amazon.awssdk.core.interceptor.Context.BeforeExecution; +import software.amazon.awssdk.core.interceptor.Context.BeforeMarshalling; +import software.amazon.awssdk.core.interceptor.Context.BeforeTransmission; +import software.amazon.awssdk.core.interceptor.Context.BeforeUnmarshalling; +import software.amazon.awssdk.core.interceptor.Context.FailedExecution; +import software.amazon.awssdk.core.interceptor.Context.ModifyHttpRequest; +import software.amazon.awssdk.core.interceptor.Context.ModifyHttpResponse; +import software.amazon.awssdk.core.interceptor.Context.ModifyRequest; +import software.amazon.awssdk.core.interceptor.Context.ModifyResponse; import software.amazon.awssdk.core.interceptor.ExecutionAttributes; import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; +import software.amazon.awssdk.core.sync.RequestBody; +import software.amazon.awssdk.http.SdkHttpRequest; +import software.amazon.awssdk.http.SdkHttpResponse; -/** AWS request execution interceptor */ +/** + * {@link ExecutionInterceptor} that delegates to {@link AwsSdk}, augmenting {@link + * #beforeTransmission(BeforeTransmission, ExecutionAttributes)} to make sure the span is set to the + * current context to allow downstream instrumentation like Netty to pick it up. + */ public class TracingExecutionInterceptor implements ExecutionInterceptor { public static class ScopeHolder { @@ -40,76 +65,135 @@ public static class ScopeHolder { // need to inject helper for it. private static final Consumer OVERRIDE_CONFIGURATION_CONSUMER = - builder -> builder.addExecutionInterceptor(new TracingExecutionInterceptor()); + builder -> + builder.addExecutionInterceptor( + // Agent will trace HTTP calls too so use INTERNAL kind. + new TracingExecutionInterceptor(AwsSdk.newInterceptor(Kind.INTERNAL))); + + private final ExecutionInterceptor delegate; + + private TracingExecutionInterceptor(ExecutionInterceptor delegate) { + this.delegate = delegate; + } + + /** + * We keep this method here because it references Java8 classes and we would like to avoid + * compiling this for instrumentation code that should load into Java7. + */ + public static void overrideConfiguration(final SdkClientBuilder client) { + client.overrideConfiguration(OVERRIDE_CONFIGURATION_CONSUMER); + } - private static final ExecutionAttribute SPAN_ATTRIBUTE = - new ExecutionAttribute<>("io.opentelemetry.auto.Span"); + public static void muzzleCheck() { + // Noop + } @Override - public void beforeExecution( - final Context.BeforeExecution context, final ExecutionAttributes executionAttributes) { - final Span span = TRACER.spanBuilder(DECORATE.spanName(executionAttributes)).startSpan(); - try (final Scope scope = currentContextWith(span)) { - DECORATE.afterStart(span); - executionAttributes.putAttribute(SPAN_ATTRIBUTE, span); - } + public void beforeExecution(BeforeExecution context, ExecutionAttributes executionAttributes) { + delegate.beforeExecution(context, executionAttributes); } @Override - public void afterMarshalling( - final Context.AfterMarshalling context, final ExecutionAttributes executionAttributes) { - final Span span = executionAttributes.getAttribute(SPAN_ATTRIBUTE); + public SdkRequest modifyRequest(ModifyRequest context, ExecutionAttributes executionAttributes) { + return delegate.modifyRequest(context, executionAttributes); + } - DECORATE.onRequest(span, context.httpRequest()); - DECORATE.onSdkRequest(span, context.request()); - DECORATE.onAttributes(span, executionAttributes); + @Override + public void beforeMarshalling( + BeforeMarshalling context, ExecutionAttributes executionAttributes) { + delegate.beforeMarshalling(context, executionAttributes); } @Override - public void beforeTransmission( - final Context.BeforeTransmission context, final ExecutionAttributes executionAttributes) { - final Span span = executionAttributes.getAttribute(SPAN_ATTRIBUTE); + public void afterMarshalling(AfterMarshalling context, ExecutionAttributes executionAttributes) { + delegate.afterMarshalling(context, executionAttributes); + } - // This scope will be closed by AwsHttpClientInstrumentation since ExecutionInterceptor API - // doesn't provide a way to run code in the same thread after transmission has been scheduled. - ScopeHolder.CURRENT.set(currentContextWith(span)); + @Override + public SdkHttpRequest modifyHttpRequest( + ModifyHttpRequest context, ExecutionAttributes executionAttributes) { + return delegate.modifyHttpRequest(context, executionAttributes); } @Override - public void afterExecution( - final Context.AfterExecution context, final ExecutionAttributes executionAttributes) { - final Span span = executionAttributes.getAttribute(SPAN_ATTRIBUTE); - if (span != null) { - executionAttributes.putAttribute(SPAN_ATTRIBUTE, null); - // Call onResponse on both types of responses: - DECORATE.onResponse(span, context.response()); - DECORATE.onResponse(span, context.httpResponse()); - DECORATE.beforeFinish(span); - span.end(); - } + public Optional modifyHttpContent( + ModifyHttpRequest context, ExecutionAttributes executionAttributes) { + return delegate.modifyHttpContent(context, executionAttributes); } @Override - public void onExecutionFailure( - final Context.FailedExecution context, final ExecutionAttributes executionAttributes) { - final Span span = executionAttributes.getAttribute(SPAN_ATTRIBUTE); + public Optional modifyAsyncHttpContent( + ModifyHttpRequest context, ExecutionAttributes executionAttributes) { + return delegate.modifyAsyncHttpContent(context, executionAttributes); + } + + @Override + public void beforeTransmission( + BeforeTransmission context, ExecutionAttributes executionAttributes) { + delegate.beforeTransmission(context, executionAttributes); + final Span span = AwsSdk.getSpanFromAttributes(executionAttributes); if (span != null) { - executionAttributes.putAttribute(SPAN_ATTRIBUTE, null); - DECORATE.onError(span, context.exception()); - DECORATE.beforeFinish(span); - span.end(); + // This scope will be closed by AwsHttpClientInstrumentation since ExecutionInterceptor API + // doesn't provide a way to run code in the same thread after transmission has been scheduled. + ScopeHolder.CURRENT.set(currentContextWith(span)); } } - /** - * We keep this method here because it references Java8 classes and we would like to avoid - * compiling this for instrumentation code that should load into Java7. - */ - public static void overrideConfiguration(final SdkClientBuilder client) { - client.overrideConfiguration(OVERRIDE_CONFIGURATION_CONSUMER); + @Override + public void afterTransmission( + AfterTransmission context, ExecutionAttributes executionAttributes) { + delegate.afterTransmission(context, executionAttributes); } - public static void muzzleCheck() { - // Noop + @Override + public SdkHttpResponse modifyHttpResponse( + ModifyHttpResponse context, ExecutionAttributes executionAttributes) { + return delegate.modifyHttpResponse(context, executionAttributes); + } + + @Override + public Optional> modifyAsyncHttpResponseContent( + ModifyHttpResponse context, ExecutionAttributes executionAttributes) { + return delegate.modifyAsyncHttpResponseContent(context, executionAttributes); + } + + @Override + public Optional modifyHttpResponseContent( + ModifyHttpResponse context, ExecutionAttributes executionAttributes) { + return delegate.modifyHttpResponseContent(context, executionAttributes); + } + + @Override + public void beforeUnmarshalling( + BeforeUnmarshalling context, ExecutionAttributes executionAttributes) { + delegate.beforeUnmarshalling(context, executionAttributes); + } + + @Override + public void afterUnmarshalling( + AfterUnmarshalling context, ExecutionAttributes executionAttributes) { + delegate.afterUnmarshalling(context, executionAttributes); + } + + @Override + public SdkResponse modifyResponse( + ModifyResponse context, ExecutionAttributes executionAttributes) { + return delegate.modifyResponse(context, executionAttributes); + } + + @Override + public void afterExecution(AfterExecution context, ExecutionAttributes executionAttributes) { + delegate.afterExecution(context, executionAttributes); + } + + @Override + public Throwable modifyException( + FailedExecution context, ExecutionAttributes executionAttributes) { + return delegate.modifyException(context, executionAttributes); + } + + @Override + public void onExecutionFailure(FailedExecution context, ExecutionAttributes executionAttributes) { + delegate.onExecutionFailure(context, executionAttributes); } } diff --git a/instrumentation/couchbase/couchbase-2.0/src/test/groovy/springdata/CouchbaseSpringTemplateTest.groovy b/instrumentation/couchbase/couchbase-2.0/src/test/groovy/springdata/CouchbaseSpringTemplateTest.groovy index ffa21aa8b93e..185ecfda3ed0 100644 --- a/instrumentation/couchbase/couchbase-2.0/src/test/groovy/springdata/CouchbaseSpringTemplateTest.groovy +++ b/instrumentation/couchbase/couchbase-2.0/src/test/groovy/springdata/CouchbaseSpringTemplateTest.groovy @@ -29,7 +29,7 @@ import util.AbstractCouchbaseTest import static io.opentelemetry.auto.test.utils.TraceUtils.basicSpan import static io.opentelemetry.auto.test.utils.TraceUtils.runUnderTrace -@Retry(count = 10, delay = 5) +@Retry(count = 10, delay = 500) @Unroll class CouchbaseSpringTemplateTest extends AbstractCouchbaseTest { diff --git a/instrumentation/dropwizard-testing/src/test/groovy/DropwizardTest.groovy b/instrumentation/dropwizard-testing/src/test/groovy/DropwizardTest.groovy index 244a00058cf5..38d11273ea84 100644 --- a/instrumentation/dropwizard-testing/src/test/groovy/DropwizardTest.groovy +++ b/instrumentation/dropwizard-testing/src/test/groovy/DropwizardTest.groovy @@ -44,7 +44,7 @@ import static io.opentelemetry.trace.Span.Kind.INTERNAL import static io.opentelemetry.trace.Span.Kind.SERVER // Work around for: address already in use -@Retry +@Retry(count = 5, delay = 100) class DropwizardTest extends HttpServerTest { @Override diff --git a/instrumentation/elasticsearch/elasticsearch-rest-5.0/src/latestDepTest/groovy/Elasticsearch6RestClientTest.groovy b/instrumentation/elasticsearch/elasticsearch-rest-5.0/src/latestDepTest/groovy/Elasticsearch6RestClientTest.groovy index 803401f2da58..e9009e8c0e77 100644 --- a/instrumentation/elasticsearch/elasticsearch-rest-5.0/src/latestDepTest/groovy/Elasticsearch6RestClientTest.groovy +++ b/instrumentation/elasticsearch/elasticsearch-rest-5.0/src/latestDepTest/groovy/Elasticsearch6RestClientTest.groovy @@ -49,6 +49,12 @@ class Elasticsearch6RestClientTest extends AgentTestRunner { RestClient client def setupSpec() { + withRetryOnBindException({ + setupSpecUnderRetry() + }) + } + + def setupSpecUnderRetry() { httpPort = PortUtils.randomOpenPort() tcpPort = PortUtils.randomOpenPort() diff --git a/instrumentation/elasticsearch/elasticsearch-rest-5.0/src/main/java/io/opentelemetry/auto/instrumentation/elasticsearch/rest/v5_0/Elasticsearch5RestClientInstrumentation.java b/instrumentation/elasticsearch/elasticsearch-rest-5.0/src/main/java/io/opentelemetry/auto/instrumentation/elasticsearch/rest/v5_0/Elasticsearch5RestClientInstrumentation.java index ddcaa5a0c9ea..fda3c894755e 100644 --- a/instrumentation/elasticsearch/elasticsearch-rest-5.0/src/main/java/io/opentelemetry/auto/instrumentation/elasticsearch/rest/v5_0/Elasticsearch5RestClientInstrumentation.java +++ b/instrumentation/elasticsearch/elasticsearch-rest-5.0/src/main/java/io/opentelemetry/auto/instrumentation/elasticsearch/rest/v5_0/Elasticsearch5RestClientInstrumentation.java @@ -94,6 +94,7 @@ public static void stopSpan( span.end(); } spanWithScope.closeScope(); + // span finished by RestResponseListener } } } diff --git a/instrumentation/elasticsearch/elasticsearch-rest-5.0/src/test/groovy/Elasticsearch5RestClientTest.groovy b/instrumentation/elasticsearch/elasticsearch-rest-5.0/src/test/groovy/Elasticsearch5RestClientTest.groovy index 24ca68d7f3cc..ebaecbf7408c 100644 --- a/instrumentation/elasticsearch/elasticsearch-rest-5.0/src/test/groovy/Elasticsearch5RestClientTest.groovy +++ b/instrumentation/elasticsearch/elasticsearch-rest-5.0/src/test/groovy/Elasticsearch5RestClientTest.groovy @@ -51,6 +51,12 @@ class Elasticsearch5RestClientTest extends AgentTestRunner { static RestClient client def setupSpec() { + withRetryOnBindException({ + setupSpecUnderRetry() + }) + } + + def setupSpecUnderRetry() { httpPort = PortUtils.randomOpenPort() tcpPort = PortUtils.randomOpenPort() diff --git a/instrumentation/elasticsearch/elasticsearch-rest-6.4/src/latestDepTest/groovy/Elasticsearch6RestClientTest.groovy b/instrumentation/elasticsearch/elasticsearch-rest-6.4/src/latestDepTest/groovy/Elasticsearch6RestClientTest.groovy index 48a97154ef98..ba4f2fc22b67 100644 --- a/instrumentation/elasticsearch/elasticsearch-rest-6.4/src/latestDepTest/groovy/Elasticsearch6RestClientTest.groovy +++ b/instrumentation/elasticsearch/elasticsearch-rest-6.4/src/latestDepTest/groovy/Elasticsearch6RestClientTest.groovy @@ -52,6 +52,12 @@ class Elasticsearch6RestClientTest extends AgentTestRunner { RestClient client def setupSpec() { + withRetryOnBindException({ + setupSpecUnderRetry() + }) + } + + def setupSpecUnderRetry() { httpPort = PortUtils.randomOpenPort() tcpPort = PortUtils.randomOpenPort() diff --git a/instrumentation/elasticsearch/elasticsearch-rest-6.4/src/main/java/io/opentelemetry/auto/instrumentation/elasticsearch/rest/v6_4/Elasticsearch6RestClientInstrumentation.java b/instrumentation/elasticsearch/elasticsearch-rest-6.4/src/main/java/io/opentelemetry/auto/instrumentation/elasticsearch/rest/v6_4/Elasticsearch6RestClientInstrumentation.java index 5cca2d48d6d5..df70e48c0efe 100644 --- a/instrumentation/elasticsearch/elasticsearch-rest-6.4/src/main/java/io/opentelemetry/auto/instrumentation/elasticsearch/rest/v6_4/Elasticsearch6RestClientInstrumentation.java +++ b/instrumentation/elasticsearch/elasticsearch-rest-6.4/src/main/java/io/opentelemetry/auto/instrumentation/elasticsearch/rest/v6_4/Elasticsearch6RestClientInstrumentation.java @@ -94,6 +94,7 @@ public static void stopSpan( span.end(); } spanWithScope.closeScope(); + // span finished by RestResponseListener } } } diff --git a/instrumentation/elasticsearch/elasticsearch-rest-6.4/src/test/groovy/Elasticsearch6RestClientTest.groovy b/instrumentation/elasticsearch/elasticsearch-rest-6.4/src/test/groovy/Elasticsearch6RestClientTest.groovy index 803401f2da58..e9009e8c0e77 100644 --- a/instrumentation/elasticsearch/elasticsearch-rest-6.4/src/test/groovy/Elasticsearch6RestClientTest.groovy +++ b/instrumentation/elasticsearch/elasticsearch-rest-6.4/src/test/groovy/Elasticsearch6RestClientTest.groovy @@ -49,6 +49,12 @@ class Elasticsearch6RestClientTest extends AgentTestRunner { RestClient client def setupSpec() { + withRetryOnBindException({ + setupSpecUnderRetry() + }) + } + + def setupSpecUnderRetry() { httpPort = PortUtils.randomOpenPort() tcpPort = PortUtils.randomOpenPort() diff --git a/instrumentation/elasticsearch/elasticsearch-transport-2.0/src/latestDepTest/groovy/Elasticsearch2NodeClientTest.groovy b/instrumentation/elasticsearch/elasticsearch-transport-2.0/src/latestDepTest/groovy/Elasticsearch2NodeClientTest.groovy index 87a6692c0901..01f9aea48d54 100644 --- a/instrumentation/elasticsearch/elasticsearch-transport-2.0/src/latestDepTest/groovy/Elasticsearch2NodeClientTest.groovy +++ b/instrumentation/elasticsearch/elasticsearch-transport-2.0/src/latestDepTest/groovy/Elasticsearch2NodeClientTest.groovy @@ -42,6 +42,12 @@ class Elasticsearch2NodeClientTest extends AgentTestRunner { def client = testNode.client() def setupSpec() { + withRetryOnBindException({ + setupSpecUnderRetry() + }) + } + + def setupSpecUnderRetry() { httpPort = PortUtils.randomOpenPort() tcpPort = PortUtils.randomOpenPort() diff --git a/instrumentation/elasticsearch/elasticsearch-transport-2.0/src/latestDepTest/groovy/Elasticsearch2TransportClientTest.groovy b/instrumentation/elasticsearch/elasticsearch-transport-2.0/src/latestDepTest/groovy/Elasticsearch2TransportClientTest.groovy index 3bf4930fc14b..166abd77b801 100644 --- a/instrumentation/elasticsearch/elasticsearch-transport-2.0/src/latestDepTest/groovy/Elasticsearch2TransportClientTest.groovy +++ b/instrumentation/elasticsearch/elasticsearch-transport-2.0/src/latestDepTest/groovy/Elasticsearch2TransportClientTest.groovy @@ -47,6 +47,12 @@ class Elasticsearch2TransportClientTest extends AgentTestRunner { TransportClient client def setupSpec() { + withRetryOnBindException({ + setupSpecUnderRetry() + }) + } + + def setupSpecUnderRetry() { httpPort = PortUtils.randomOpenPort() tcpPort = PortUtils.randomOpenPort() diff --git a/instrumentation/elasticsearch/elasticsearch-transport-2.0/src/latestDepTest/groovy/springdata/Elasticsearch2SpringTemplateTest.groovy b/instrumentation/elasticsearch/elasticsearch-transport-2.0/src/latestDepTest/groovy/springdata/Elasticsearch2SpringTemplateTest.groovy index dc9bc17411c5..d0de3aa5f69a 100644 --- a/instrumentation/elasticsearch/elasticsearch-transport-2.0/src/latestDepTest/groovy/springdata/Elasticsearch2SpringTemplateTest.groovy +++ b/instrumentation/elasticsearch/elasticsearch-transport-2.0/src/latestDepTest/groovy/springdata/Elasticsearch2SpringTemplateTest.groovy @@ -53,6 +53,12 @@ class Elasticsearch2SpringTemplateTest extends AgentTestRunner { ElasticsearchTemplate template def setupSpec() { + withRetryOnBindException({ + setupSpecUnderRetry() + }) + } + + def setupSpecUnderRetry() { httpPort = PortUtils.randomOpenPort() tcpPort = PortUtils.randomOpenPort() diff --git a/instrumentation/elasticsearch/elasticsearch-transport-2.0/src/main/java/io/opentelemetry/auto/instrumentation/elasticsearch/transport/v2_0/Elasticsearch2TransportClientInstrumentation.java b/instrumentation/elasticsearch/elasticsearch-transport-2.0/src/main/java/io/opentelemetry/auto/instrumentation/elasticsearch/transport/v2_0/Elasticsearch2TransportClientInstrumentation.java index 6c006c7f7772..f8044961893f 100644 --- a/instrumentation/elasticsearch/elasticsearch-transport-2.0/src/main/java/io/opentelemetry/auto/instrumentation/elasticsearch/transport/v2_0/Elasticsearch2TransportClientInstrumentation.java +++ b/instrumentation/elasticsearch/elasticsearch-transport-2.0/src/main/java/io/opentelemetry/auto/instrumentation/elasticsearch/transport/v2_0/Elasticsearch2TransportClientInstrumentation.java @@ -106,6 +106,7 @@ public static void stopSpan( span.end(); } spanWithScope.closeScope(); + // span finished by TransportActionListener } } } diff --git a/instrumentation/elasticsearch/elasticsearch-transport-2.0/src/test/groovy/Elasticsearch2NodeClientTest.groovy b/instrumentation/elasticsearch/elasticsearch-transport-2.0/src/test/groovy/Elasticsearch2NodeClientTest.groovy index df83ec76d62d..1206b6facdf8 100644 --- a/instrumentation/elasticsearch/elasticsearch-transport-2.0/src/test/groovy/Elasticsearch2NodeClientTest.groovy +++ b/instrumentation/elasticsearch/elasticsearch-transport-2.0/src/test/groovy/Elasticsearch2NodeClientTest.groovy @@ -43,6 +43,12 @@ class Elasticsearch2NodeClientTest extends AgentTestRunner { def client = testNode.client() def setupSpec() { + withRetryOnBindException({ + setupSpecUnderRetry() + }) + } + + def setupSpecUnderRetry() { httpPort = PortUtils.randomOpenPort() tcpPort = PortUtils.randomOpenPort() diff --git a/instrumentation/elasticsearch/elasticsearch-transport-2.0/src/test/groovy/Elasticsearch2TransportClientTest.groovy b/instrumentation/elasticsearch/elasticsearch-transport-2.0/src/test/groovy/Elasticsearch2TransportClientTest.groovy index 06c70d180cbf..84c64abac8f2 100644 --- a/instrumentation/elasticsearch/elasticsearch-transport-2.0/src/test/groovy/Elasticsearch2TransportClientTest.groovy +++ b/instrumentation/elasticsearch/elasticsearch-transport-2.0/src/test/groovy/Elasticsearch2TransportClientTest.groovy @@ -48,6 +48,12 @@ class Elasticsearch2TransportClientTest extends AgentTestRunner { TransportClient client def setupSpec() { + withRetryOnBindException({ + setupSpecUnderRetry() + }) + } + + def setupSpecUnderRetry() { httpPort = PortUtils.randomOpenPort() tcpPort = PortUtils.randomOpenPort() diff --git a/instrumentation/elasticsearch/elasticsearch-transport-2.0/src/test/groovy/springdata/Elasticsearch2SpringTemplateTest.groovy b/instrumentation/elasticsearch/elasticsearch-transport-2.0/src/test/groovy/springdata/Elasticsearch2SpringTemplateTest.groovy index a4b035a53678..b08e83609667 100644 --- a/instrumentation/elasticsearch/elasticsearch-transport-2.0/src/test/groovy/springdata/Elasticsearch2SpringTemplateTest.groovy +++ b/instrumentation/elasticsearch/elasticsearch-transport-2.0/src/test/groovy/springdata/Elasticsearch2SpringTemplateTest.groovy @@ -54,6 +54,12 @@ class Elasticsearch2SpringTemplateTest extends AgentTestRunner { ElasticsearchTemplate template def setupSpec() { + withRetryOnBindException({ + setupSpecUnderRetry() + }) + } + + def setupSpecUnderRetry() { httpPort = PortUtils.randomOpenPort() tcpPort = PortUtils.randomOpenPort() diff --git a/instrumentation/elasticsearch/elasticsearch-transport-5.0/src/main/java/io/opentelemetry/auto/instrumentation/elasticsearch/transport/v5_0/Elasticsearch5TransportClientInstrumentation.java b/instrumentation/elasticsearch/elasticsearch-transport-5.0/src/main/java/io/opentelemetry/auto/instrumentation/elasticsearch/transport/v5_0/Elasticsearch5TransportClientInstrumentation.java index fa4362260447..b32ab320412c 100644 --- a/instrumentation/elasticsearch/elasticsearch-transport-5.0/src/main/java/io/opentelemetry/auto/instrumentation/elasticsearch/transport/v5_0/Elasticsearch5TransportClientInstrumentation.java +++ b/instrumentation/elasticsearch/elasticsearch-transport-5.0/src/main/java/io/opentelemetry/auto/instrumentation/elasticsearch/transport/v5_0/Elasticsearch5TransportClientInstrumentation.java @@ -106,6 +106,7 @@ public static void stopSpan( span.end(); } spanWithScope.closeScope(); + // span finished by TransportActionListener } } } diff --git a/instrumentation/elasticsearch/elasticsearch-transport-5.0/src/test/groovy/Elasticsearch5NodeClientTest.groovy b/instrumentation/elasticsearch/elasticsearch-transport-5.0/src/test/groovy/Elasticsearch5NodeClientTest.groovy index 7d26ffe3aab3..c53b853cf531 100644 --- a/instrumentation/elasticsearch/elasticsearch-transport-5.0/src/test/groovy/Elasticsearch5NodeClientTest.groovy +++ b/instrumentation/elasticsearch/elasticsearch-transport-5.0/src/test/groovy/Elasticsearch5NodeClientTest.groovy @@ -45,6 +45,12 @@ class Elasticsearch5NodeClientTest extends AgentTestRunner { def client = testNode.client() def setupSpec() { + withRetryOnBindException({ + setupSpecUnderRetry() + }) + } + + def setupSpecUnderRetry() { httpPort = PortUtils.randomOpenPort() tcpPort = PortUtils.randomOpenPort() diff --git a/instrumentation/elasticsearch/elasticsearch-transport-5.0/src/test/groovy/Elasticsearch5TransportClientTest.groovy b/instrumentation/elasticsearch/elasticsearch-transport-5.0/src/test/groovy/Elasticsearch5TransportClientTest.groovy index 542f0f51365e..1e9175910d91 100644 --- a/instrumentation/elasticsearch/elasticsearch-transport-5.0/src/test/groovy/Elasticsearch5TransportClientTest.groovy +++ b/instrumentation/elasticsearch/elasticsearch-transport-5.0/src/test/groovy/Elasticsearch5TransportClientTest.groovy @@ -51,6 +51,12 @@ class Elasticsearch5TransportClientTest extends AgentTestRunner { TransportClient client def setupSpec() { + withRetryOnBindException({ + setupSpecUnderRetry() + }) + } + + def setupSpecUnderRetry() { httpPort = PortUtils.randomOpenPort() tcpPort = PortUtils.randomOpenPort() diff --git a/instrumentation/elasticsearch/elasticsearch-transport-5.3/src/main/java/io/opentelemetry/auto/instrumentation/elasticsearch/transport/v5_3/Elasticsearch53TransportClientInstrumentation.java b/instrumentation/elasticsearch/elasticsearch-transport-5.3/src/main/java/io/opentelemetry/auto/instrumentation/elasticsearch/transport/v5_3/Elasticsearch53TransportClientInstrumentation.java index 28e2454af548..9681347dc3b8 100644 --- a/instrumentation/elasticsearch/elasticsearch-transport-5.3/src/main/java/io/opentelemetry/auto/instrumentation/elasticsearch/transport/v5_3/Elasticsearch53TransportClientInstrumentation.java +++ b/instrumentation/elasticsearch/elasticsearch-transport-5.3/src/main/java/io/opentelemetry/auto/instrumentation/elasticsearch/transport/v5_3/Elasticsearch53TransportClientInstrumentation.java @@ -107,6 +107,7 @@ public static void stopSpan( span.end(); } spanWithScope.closeScope(); + // span finished by TransportActionListener } } } diff --git a/instrumentation/elasticsearch/elasticsearch-transport-5.3/src/test/groovy/Elasticsearch53NodeClientTest.groovy b/instrumentation/elasticsearch/elasticsearch-transport-5.3/src/test/groovy/Elasticsearch53NodeClientTest.groovy index 031df961b3e5..4d68341a448a 100644 --- a/instrumentation/elasticsearch/elasticsearch-transport-5.3/src/test/groovy/Elasticsearch53NodeClientTest.groovy +++ b/instrumentation/elasticsearch/elasticsearch-transport-5.3/src/test/groovy/Elasticsearch53NodeClientTest.groovy @@ -45,6 +45,12 @@ class Elasticsearch53NodeClientTest extends AgentTestRunner { def client = testNode.client() def setupSpec() { + withRetryOnBindException({ + setupSpecUnderRetry() + }) + } + + def setupSpecUnderRetry() { httpPort = PortUtils.randomOpenPort() tcpPort = PortUtils.randomOpenPort() diff --git a/instrumentation/elasticsearch/elasticsearch-transport-5.3/src/test/groovy/Elasticsearch53TransportClientTest.groovy b/instrumentation/elasticsearch/elasticsearch-transport-5.3/src/test/groovy/Elasticsearch53TransportClientTest.groovy index cddab114ad8c..0bb864555615 100644 --- a/instrumentation/elasticsearch/elasticsearch-transport-5.3/src/test/groovy/Elasticsearch53TransportClientTest.groovy +++ b/instrumentation/elasticsearch/elasticsearch-transport-5.3/src/test/groovy/Elasticsearch53TransportClientTest.groovy @@ -51,6 +51,12 @@ class Elasticsearch53TransportClientTest extends AgentTestRunner { TransportClient client def setupSpec() { + withRetryOnBindException({ + setupSpecUnderRetry() + }) + } + + def setupSpecUnderRetry() { httpPort = PortUtils.randomOpenPort() tcpPort = PortUtils.randomOpenPort() diff --git a/instrumentation/elasticsearch/elasticsearch-transport-5.3/src/test/groovy/springdata/Elasticsearch53SpringTemplateTest.groovy b/instrumentation/elasticsearch/elasticsearch-transport-5.3/src/test/groovy/springdata/Elasticsearch53SpringTemplateTest.groovy index ccc2b4bc63dc..f0cad530326c 100644 --- a/instrumentation/elasticsearch/elasticsearch-transport-5.3/src/test/groovy/springdata/Elasticsearch53SpringTemplateTest.groovy +++ b/instrumentation/elasticsearch/elasticsearch-transport-5.3/src/test/groovy/springdata/Elasticsearch53SpringTemplateTest.groovy @@ -63,6 +63,12 @@ class Elasticsearch53SpringTemplateTest extends AgentTestRunner { ElasticsearchTemplate template def setupSpec() { + withRetryOnBindException({ + setupSpecUnderRetry() + }) + } + + def setupSpecUnderRetry() { httpPort = PortUtils.randomOpenPort() tcpPort = PortUtils.randomOpenPort() diff --git a/instrumentation/elasticsearch/elasticsearch-transport-6.0/src/main/java/io/opentelemetry/auto/instrumentation/elasticsearch/transport/v6_0/Elasticsearch6TransportClientInstrumentation.java b/instrumentation/elasticsearch/elasticsearch-transport-6.0/src/main/java/io/opentelemetry/auto/instrumentation/elasticsearch/transport/v6_0/Elasticsearch6TransportClientInstrumentation.java index ceed6b9d8c98..2cf3224d704a 100644 --- a/instrumentation/elasticsearch/elasticsearch-transport-6.0/src/main/java/io/opentelemetry/auto/instrumentation/elasticsearch/transport/v6_0/Elasticsearch6TransportClientInstrumentation.java +++ b/instrumentation/elasticsearch/elasticsearch-transport-6.0/src/main/java/io/opentelemetry/auto/instrumentation/elasticsearch/transport/v6_0/Elasticsearch6TransportClientInstrumentation.java @@ -110,6 +110,7 @@ public static void stopSpan( span.end(); } spanWithScope.closeScope(); + // span finished by TransportActionListener } } } diff --git a/instrumentation/elasticsearch/elasticsearch-transport-6.0/src/test/groovy/Elasticsearch6NodeClientTest.groovy b/instrumentation/elasticsearch/elasticsearch-transport-6.0/src/test/groovy/Elasticsearch6NodeClientTest.groovy index 679131887c77..ffcb222c1f9f 100644 --- a/instrumentation/elasticsearch/elasticsearch-transport-6.0/src/test/groovy/Elasticsearch6NodeClientTest.groovy +++ b/instrumentation/elasticsearch/elasticsearch-transport-6.0/src/test/groovy/Elasticsearch6NodeClientTest.groovy @@ -44,6 +44,12 @@ class Elasticsearch6NodeClientTest extends AgentTestRunner { def client = testNode.client() def setupSpec() { + withRetryOnBindException({ + setupSpecUnderRetry() + }) + } + + def setupSpecUnderRetry() { httpPort = PortUtils.randomOpenPort() tcpPort = PortUtils.randomOpenPort() diff --git a/instrumentation/elasticsearch/elasticsearch-transport-6.0/src/test/groovy/Elasticsearch6TransportClientTest.groovy b/instrumentation/elasticsearch/elasticsearch-transport-6.0/src/test/groovy/Elasticsearch6TransportClientTest.groovy index 9cea840a7a95..4845959ee0cc 100644 --- a/instrumentation/elasticsearch/elasticsearch-transport-6.0/src/test/groovy/Elasticsearch6TransportClientTest.groovy +++ b/instrumentation/elasticsearch/elasticsearch-transport-6.0/src/test/groovy/Elasticsearch6TransportClientTest.groovy @@ -50,6 +50,12 @@ class Elasticsearch6TransportClientTest extends AgentTestRunner { TransportClient client def setupSpec() { + withRetryOnBindException({ + setupSpecUnderRetry() + }) + } + + def setupSpecUnderRetry() { httpPort = PortUtils.randomOpenPort() tcpPort = PortUtils.randomOpenPort() diff --git a/instrumentation/finatra-2.9/finatra-2.9.gradle b/instrumentation/finatra-2.9/finatra-2.9.gradle index 99b6fcd3fd73..0d37ca61a47e 100644 --- a/instrumentation/finatra-2.9/finatra-2.9.gradle +++ b/instrumentation/finatra-2.9/finatra-2.9.gradle @@ -35,14 +35,13 @@ dependencies { testCompile project(':instrumentation:netty:netty-4.1') testCompile group: 'com.twitter', name: 'finatra-http_2.11', version: '19.12.0' - testCompile(group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.9.10') { - force = true - } + testCompile(group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.9.10') // Required for older versions of finatra on JDKs >= 11 testCompile group: 'com.sun.activation', name: 'javax.activation', version: '1.2.0' latestDepTestCompile project(':instrumentation:netty:netty-4.1') latestDepTestCompile group: 'com.twitter', name: 'finatra-http_2.11', version: '+' + latestDepTestCompile(group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.11.0') } compileLatestDepTestGroovy { diff --git a/instrumentation/grizzly-2.0/src/main/java/io/opentelemetry/auto/instrumentation/grizzly/GrizzlyHttpHandlerInstrumentation.java b/instrumentation/grizzly-2.0/src/main/java/io/opentelemetry/auto/instrumentation/grizzly/GrizzlyHttpHandlerInstrumentation.java index fb7b61857b41..3616933c06e1 100644 --- a/instrumentation/grizzly-2.0/src/main/java/io/opentelemetry/auto/instrumentation/grizzly/GrizzlyHttpHandlerInstrumentation.java +++ b/instrumentation/grizzly-2.0/src/main/java/io/opentelemetry/auto/instrumentation/grizzly/GrizzlyHttpHandlerInstrumentation.java @@ -113,6 +113,7 @@ public static void methodExit( span.end(); } spanWithScope.closeScope(); + // span finished by SpanClosingListener } } diff --git a/instrumentation/grizzly-client-1.9/grizzly-client-1.9.gradle b/instrumentation/grizzly-client-1.9/grizzly-client-1.9.gradle new file mode 100644 index 000000000000..080b739fd1a4 --- /dev/null +++ b/instrumentation/grizzly-client-1.9/grizzly-client-1.9.gradle @@ -0,0 +1,35 @@ +ext { + minJavaVersionForTests = JavaVersion.VERSION_1_8 +} + +apply from: "${rootDir}/gradle/instrumentation.gradle" +apply plugin: 'org.unbroken-dome.test-sets' + +muzzle { + pass { + group = "org.glassfish.grizzly" + module = "grizzly-http-client" + versions = "[1.9,1.16]" + assertInverse = true + } + pass { + group = "com.ning" + module = "async-http-client" + versions = "[1.9.0,)" + assertInverse = true + } +} + +testSets { + latestDepTest { + dirName = 'test' + } +} + +dependencies { + compileOnly group: 'org.glassfish.grizzly', name: 'grizzly-http-client', version: '1.9' + // for some reason, the tests don't *load* until 1.12, but muzzles works as far back as 1.9 + testCompile group: 'org.glassfish.grizzly', name: 'grizzly-http-client', version: '1.12' + + latestDepTestCompile group: 'org.glassfish.grizzly', name: 'grizzly-http-client', version: '1.16' +} diff --git a/instrumentation/grizzly-client-1.9/src/main/java/io/opentelemetry/auto/instrumentation/grizzly/client/ClientDecorator.java b/instrumentation/grizzly-client-1.9/src/main/java/io/opentelemetry/auto/instrumentation/grizzly/client/ClientDecorator.java new file mode 100644 index 000000000000..0b50aeb1752f --- /dev/null +++ b/instrumentation/grizzly-client-1.9/src/main/java/io/opentelemetry/auto/instrumentation/grizzly/client/ClientDecorator.java @@ -0,0 +1,47 @@ +/* + * Copyright The OpenTelemetry Authors + * + * 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. + */ +package io.opentelemetry.auto.instrumentation.grizzly.client; + +import com.ning.http.client.Request; +import com.ning.http.client.Response; +import io.opentelemetry.OpenTelemetry; +import io.opentelemetry.auto.bootstrap.instrumentation.decorator.HttpClientDecorator; +import io.opentelemetry.trace.Tracer; +import java.net.URI; +import java.net.URISyntaxException; + +public class ClientDecorator extends HttpClientDecorator { + + public static final ClientDecorator DECORATE = new ClientDecorator(); + + public static final Tracer TRACER = + OpenTelemetry.getTracerProvider().get("io.opentelemetry.auto.grizzly-client-1.9"); + + @Override + protected String method(final Request request) { + return request.getMethod(); + } + + @Override + protected URI url(final Request request) throws URISyntaxException { + return request.getUri().toJavaNetURI(); + } + + @Override + protected Integer status(final Response response) { + return response.getStatusCode(); + } +} diff --git a/instrumentation/grizzly-client-1.9/src/main/java/io/opentelemetry/auto/instrumentation/grizzly/client/ClientRequestAdvice.java b/instrumentation/grizzly-client-1.9/src/main/java/io/opentelemetry/auto/instrumentation/grizzly/client/ClientRequestAdvice.java new file mode 100644 index 000000000000..e04283bf0365 --- /dev/null +++ b/instrumentation/grizzly-client-1.9/src/main/java/io/opentelemetry/auto/instrumentation/grizzly/client/ClientRequestAdvice.java @@ -0,0 +1,58 @@ +/* + * Copyright The OpenTelemetry Authors + * + * 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. + */ +package io.opentelemetry.auto.instrumentation.grizzly.client; + +import static io.opentelemetry.auto.instrumentation.grizzly.client.ClientDecorator.DECORATE; +import static io.opentelemetry.auto.instrumentation.grizzly.client.ClientDecorator.TRACER; +import static io.opentelemetry.auto.instrumentation.grizzly.client.InjectAdapter.SETTER; +import static io.opentelemetry.trace.Span.Kind.CLIENT; +import static io.opentelemetry.trace.TracingContextUtils.withSpan; + +import com.ning.http.client.AsyncHandler; +import com.ning.http.client.Request; +import io.grpc.Context; +import io.opentelemetry.OpenTelemetry; +import io.opentelemetry.auto.bootstrap.InstrumentationContext; +import io.opentelemetry.auto.bootstrap.instrumentation.api.Pair; +import io.opentelemetry.context.Scope; +import io.opentelemetry.trace.Span; +import net.bytebuddy.asm.Advice; + +public class ClientRequestAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static Scope onEnter( + @Advice.Argument(0) final Request request, + @Advice.Argument(1) final AsyncHandler handler) { + final Span parentSpan = TRACER.getCurrentSpan(); + final Span span = + TRACER.spanBuilder(DECORATE.spanNameForRequest(request)).setSpanKind(CLIENT).startSpan(); + DECORATE.afterStart(span); + DECORATE.onRequest(span, request); + + final Context context = withSpan(span, Context.current()); + OpenTelemetry.getPropagators().getHttpTextFormat().inject(context, request, SETTER); + InstrumentationContext.get(AsyncHandler.class, Pair.class) + .put(handler, Pair.of(parentSpan, span)); + return TRACER.withSpan(span); + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void onExit(@Advice.Enter final Scope scope) { + // span closed in ClientResponseAdvice + scope.close(); + } +} diff --git a/instrumentation/grizzly-client-1.9/src/main/java/io/opentelemetry/auto/instrumentation/grizzly/client/ClientRequestInstrumentation.java b/instrumentation/grizzly-client-1.9/src/main/java/io/opentelemetry/auto/instrumentation/grizzly/client/ClientRequestInstrumentation.java new file mode 100644 index 000000000000..027ee1b737d1 --- /dev/null +++ b/instrumentation/grizzly-client-1.9/src/main/java/io/opentelemetry/auto/instrumentation/grizzly/client/ClientRequestInstrumentation.java @@ -0,0 +1,67 @@ +/* + * Copyright The OpenTelemetry Authors + * + * 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. + */ +package io.opentelemetry.auto.instrumentation.grizzly.client; + +import static java.util.Collections.singletonMap; +import static net.bytebuddy.matcher.ElementMatchers.isPublic; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; + +import com.google.auto.service.AutoService; +import io.opentelemetry.auto.bootstrap.instrumentation.api.Pair; +import io.opentelemetry.auto.tooling.Instrumenter; +import java.util.Map; +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +@AutoService(Instrumenter.class) +public final class ClientRequestInstrumentation extends Instrumenter.Default { + + public ClientRequestInstrumentation() { + super("grizzly-client", "ning"); + } + + @Override + public Map contextStore() { + return singletonMap("com.ning.http.client.AsyncHandler", Pair.class.getName()); + } + + @Override + public ElementMatcher typeMatcher() { + return named("com.ning.http.client.AsyncHttpClient"); + } + + @Override + public String[] helperClassNames() { + return new String[] {packageName + ".ClientDecorator", packageName + ".InjectAdapter"}; + } + + @Override + protected boolean defaultEnabled() { + return false; + } + + @Override + public Map, String> transformers() { + return singletonMap( + named("executeRequest") + .and(takesArgument(0, named("com.ning.http.client.Request"))) + .and(takesArgument(1, named("com.ning.http.client.AsyncHandler"))) + .and(isPublic()), + packageName + ".ClientRequestAdvice"); + } +} diff --git a/instrumentation/grizzly-client-1.9/src/main/java/io/opentelemetry/auto/instrumentation/grizzly/client/ClientResponseAdvice.java b/instrumentation/grizzly-client-1.9/src/main/java/io/opentelemetry/auto/instrumentation/grizzly/client/ClientResponseAdvice.java new file mode 100644 index 000000000000..533eb412cd7a --- /dev/null +++ b/instrumentation/grizzly-client-1.9/src/main/java/io/opentelemetry/auto/instrumentation/grizzly/client/ClientResponseAdvice.java @@ -0,0 +1,57 @@ +/* + * Copyright The OpenTelemetry Authors + * + * 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. + */ +package io.opentelemetry.auto.instrumentation.grizzly.client; + +import static io.opentelemetry.auto.instrumentation.grizzly.client.ClientDecorator.DECORATE; +import static io.opentelemetry.auto.instrumentation.grizzly.client.ClientDecorator.TRACER; + +import com.ning.http.client.AsyncCompletionHandler; +import com.ning.http.client.AsyncHandler; +import com.ning.http.client.Response; +import io.opentelemetry.auto.bootstrap.ContextStore; +import io.opentelemetry.auto.bootstrap.InstrumentationContext; +import io.opentelemetry.auto.bootstrap.instrumentation.api.Pair; +import io.opentelemetry.context.Scope; +import io.opentelemetry.trace.Span; +import net.bytebuddy.asm.Advice; + +public class ClientResponseAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static Scope onEnter( + @Advice.This final AsyncCompletionHandler handler, + @Advice.Argument(0) final Response response) { + final ContextStore contextStore = + InstrumentationContext.get(AsyncHandler.class, Pair.class); + final Pair spanWithParent = contextStore.get(handler); + if (null != spanWithParent) { + contextStore.put(handler, null); + } + if (spanWithParent.hasRight()) { + DECORATE.onResponse(spanWithParent.getRight(), response); + DECORATE.beforeFinish(spanWithParent.getRight()); + spanWithParent.getRight().end(); + } + return spanWithParent.hasLeft() ? TRACER.withSpan(spanWithParent.getLeft()) : null; + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void onExit(@Advice.Enter final Scope scope) { + if (null != scope) { + scope.close(); + } + } +} diff --git a/instrumentation/grizzly-client-1.9/src/main/java/io/opentelemetry/auto/instrumentation/grizzly/client/ClientResponseInstrumentation.java b/instrumentation/grizzly-client-1.9/src/main/java/io/opentelemetry/auto/instrumentation/grizzly/client/ClientResponseInstrumentation.java new file mode 100644 index 000000000000..1c7a948736c8 --- /dev/null +++ b/instrumentation/grizzly-client-1.9/src/main/java/io/opentelemetry/auto/instrumentation/grizzly/client/ClientResponseInstrumentation.java @@ -0,0 +1,73 @@ +/* + * Copyright The OpenTelemetry Authors + * + * 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. + */ +package io.opentelemetry.auto.instrumentation.grizzly.client; + +import static io.opentelemetry.auto.tooling.ClassLoaderMatcher.hasClassesNamed; +import static java.util.Collections.singletonMap; +import static net.bytebuddy.matcher.ElementMatchers.hasSuperClass; +import static net.bytebuddy.matcher.ElementMatchers.isPublic; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; + +import com.google.auto.service.AutoService; +import io.opentelemetry.auto.bootstrap.instrumentation.api.Pair; +import io.opentelemetry.auto.tooling.Instrumenter; +import java.util.Map; +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +@AutoService(Instrumenter.class) +public final class ClientResponseInstrumentation extends Instrumenter.Default { + + public ClientResponseInstrumentation() { + super("grizzly-client", "ning"); + } + + @Override + public Map contextStore() { + return singletonMap("com.ning.http.client.AsyncHandler", Pair.class.getName()); + } + + @Override + public ElementMatcher classLoaderMatcher() { + return hasClassesNamed("com.ning.http.client.AsyncCompletionHandler"); + } + + @Override + protected boolean defaultEnabled() { + return false; + } + + @Override + public ElementMatcher typeMatcher() { + return hasSuperClass(named("com.ning.http.client.AsyncCompletionHandler")); + } + + @Override + public String[] helperClassNames() { + return new String[] {packageName + ".ClientDecorator"}; + } + + @Override + public Map, String> transformers() { + return singletonMap( + named("onCompleted") + .and(takesArgument(0, named("com.ning.http.client.Response"))) + .and(isPublic()), + packageName + ".ClientResponseAdvice"); + } +} diff --git a/instrumentation/grizzly-client-1.9/src/main/java/io/opentelemetry/auto/instrumentation/grizzly/client/InjectAdapter.java b/instrumentation/grizzly-client-1.9/src/main/java/io/opentelemetry/auto/instrumentation/grizzly/client/InjectAdapter.java new file mode 100644 index 000000000000..00e149cbb8dc --- /dev/null +++ b/instrumentation/grizzly-client-1.9/src/main/java/io/opentelemetry/auto/instrumentation/grizzly/client/InjectAdapter.java @@ -0,0 +1,29 @@ +/* + * Copyright The OpenTelemetry Authors + * + * 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. + */ +package io.opentelemetry.auto.instrumentation.grizzly.client; + +import com.ning.http.client.Request; +import io.opentelemetry.context.propagation.HttpTextFormat; + +public class InjectAdapter implements HttpTextFormat.Setter { + + public static final InjectAdapter SETTER = new InjectAdapter(); + + @Override + public void set(final Request carrier, final String key, final String value) { + carrier.getHeaders().replaceWith(key, value); + } +} diff --git a/instrumentation/grizzly-client-1.9/src/test/groovy/GrizzlyAsyncHttpClientTest.groovy b/instrumentation/grizzly-client-1.9/src/test/groovy/GrizzlyAsyncHttpClientTest.groovy new file mode 100644 index 000000000000..bc6758af3ab9 --- /dev/null +++ b/instrumentation/grizzly-client-1.9/src/test/groovy/GrizzlyAsyncHttpClientTest.groovy @@ -0,0 +1,85 @@ +/* + * Copyright The OpenTelemetry Authors + * + * 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. + */ +import com.ning.http.client.AsyncCompletionHandler +import com.ning.http.client.AsyncHttpClient +import com.ning.http.client.Request +import com.ning.http.client.RequestBuilder +import com.ning.http.client.Response +import com.ning.http.client.uri.Uri +import io.opentelemetry.auto.test.base.HttpClientTest +import spock.lang.AutoCleanup +import spock.lang.Shared + +class GrizzlyAsyncHttpClientTest extends HttpClientTest { + + static { + System.setProperty("ota.integration.grizzly-client.enabled", "true") + } + + @AutoCleanup + @Shared + def client = new AsyncHttpClient() + + @Override + int doRequest(String method, URI uri, Map headers, Closure callback) { + + RequestBuilder requestBuilder = new RequestBuilder(method) + .setUri(Uri.create(uri.toString())) + headers.entrySet().each { + requestBuilder.addHeader(it.key, it.value) + } + Request request = requestBuilder.build() + + def handler = new AsyncCompletionHandlerMock(callback) + + def response = client.executeRequest(request, handler).get() + response.statusCode + } + + class AsyncCompletionHandlerMock extends AsyncCompletionHandler { + + private Closure callback + + AsyncCompletionHandlerMock(Closure callback) { + this.callback = callback + } + + @Override + Response onCompleted(Response response) throws Exception { + if (callback != null) { + callback() + } + return response + } + } + + @Override + boolean testRedirects() { + false + } + + @Override + boolean testConnectionFailure() { + false + } + + @Override + boolean testRemoteConnection() { + return false + } +} + + diff --git a/instrumentation/grizzly-http-2.3/grizzly-http-2.3.gradle b/instrumentation/grizzly-http-2.3/grizzly-http-2.3.gradle new file mode 100644 index 000000000000..310ca9b06c8e --- /dev/null +++ b/instrumentation/grizzly-http-2.3/grizzly-http-2.3.gradle @@ -0,0 +1,28 @@ +apply from: "${rootDir}/gradle/instrumentation.gradle" +apply plugin: 'org.unbroken-dome.test-sets' + +muzzle { + pass { + group = "org.glassfish.grizzly" + module = 'grizzly-http' + versions = "[2.3,)" + } +} + +testSets { + latestDepTest { + dirName = 'test' + } +} + +dependencies { + + compileOnly group: 'org.glassfish.grizzly', name: 'grizzly-http', version: '2.3' + + testCompile group: 'javax.xml.bind', name: 'jaxb-api', version: '2.2.3' + testCompile group: 'javax.ws.rs', name: 'javax.ws.rs-api', version: '2.0' + testCompile group: 'org.glassfish.jersey.containers', name: 'jersey-container-grizzly2-http', version: '2.0' + + latestDepTestCompile group: 'org.glassfish.jersey.containers', name: 'jersey-container-grizzly2-http', version: '2.+' + latestDepTestCompile group: 'org.glassfish.jersey.inject', name: 'jersey-hk2', version: '2.+' +} diff --git a/instrumentation/servlet/request-2.3/src/main/java/io/opentelemetry/auto/instrumentation/servlet/v2_3/Servlet2ResponseRedirectAdvice.java b/instrumentation/grizzly-http-2.3/src/main/java/io/opentelemetry/auto/instrumentation/grizzly/http/v2_3/DefaultFilterChainAdvice.java similarity index 62% rename from instrumentation/servlet/request-2.3/src/main/java/io/opentelemetry/auto/instrumentation/servlet/v2_3/Servlet2ResponseRedirectAdvice.java rename to instrumentation/grizzly-http-2.3/src/main/java/io/opentelemetry/auto/instrumentation/grizzly/http/v2_3/DefaultFilterChainAdvice.java index 374818ca7460..0225b73051a2 100644 --- a/instrumentation/servlet/request-2.3/src/main/java/io/opentelemetry/auto/instrumentation/servlet/v2_3/Servlet2ResponseRedirectAdvice.java +++ b/instrumentation/grizzly-http-2.3/src/main/java/io/opentelemetry/auto/instrumentation/grizzly/http/v2_3/DefaultFilterChainAdvice.java @@ -13,16 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.opentelemetry.auto.instrumentation.servlet.v2_3; +package io.opentelemetry.auto.instrumentation.grizzly.http.v2_3; -import io.opentelemetry.auto.bootstrap.InstrumentationContext; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletResponse; import net.bytebuddy.asm.Advice; +import org.glassfish.grizzly.filterchain.FilterChainContext; + +public class DefaultFilterChainAdvice { -public class Servlet2ResponseRedirectAdvice { @Advice.OnMethodEnter(suppress = Throwable.class) - public static void onEnter(@Advice.This final HttpServletResponse response) { - InstrumentationContext.get(ServletResponse.class, Integer.class).put(response, 302); + public static void onFail( + @Advice.Argument(0) final FilterChainContext ctx, + @Advice.Argument(1) final Throwable throwable) { + GrizzlyDecorator.onFilterChainFail(ctx, throwable); } } diff --git a/instrumentation/grizzly-http-2.3/src/main/java/io/opentelemetry/auto/instrumentation/grizzly/http/v2_3/DefaultFilterChainInstrumentation.java b/instrumentation/grizzly-http-2.3/src/main/java/io/opentelemetry/auto/instrumentation/grizzly/http/v2_3/DefaultFilterChainInstrumentation.java new file mode 100644 index 000000000000..fdbae2b97e0e --- /dev/null +++ b/instrumentation/grizzly-http-2.3/src/main/java/io/opentelemetry/auto/instrumentation/grizzly/http/v2_3/DefaultFilterChainInstrumentation.java @@ -0,0 +1,63 @@ +/* + * Copyright The OpenTelemetry Authors + * + * 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. + */ +package io.opentelemetry.auto.instrumentation.grizzly.http.v2_3; + +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.isPrivate; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; + +import com.google.auto.service.AutoService; +import io.opentelemetry.auto.tooling.Instrumenter; +import java.util.Collections; +import java.util.Map; +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +@AutoService(Instrumenter.class) +public class DefaultFilterChainInstrumentation extends Instrumenter.Default { + + public DefaultFilterChainInstrumentation() { + super("grizzly-filterchain"); + } + + @Override + public ElementMatcher typeMatcher() { + return named("org.glassfish.grizzly.filterchain.DefaultFilterChain"); + } + + @Override + public String[] helperClassNames() { + return new String[] {packageName + ".GrizzlyDecorator", packageName + ".ExtractAdapter"}; + } + + @Override + protected boolean defaultEnabled() { + return false; + } + + @Override + public Map, String> transformers() { + return Collections.singletonMap( + isMethod() + .and(isPrivate()) + .and(named("notifyFailure")) + .and(takesArgument(0, named("org.glassfish.grizzly.filterchain.FilterChainContext"))) + .and(takesArgument(1, named("java.lang.Throwable"))), + packageName + ".DefaultFilterChainAdvice"); + } +} diff --git a/instrumentation/grizzly-http-2.3/src/main/java/io/opentelemetry/auto/instrumentation/grizzly/http/v2_3/ExtractAdapter.java b/instrumentation/grizzly-http-2.3/src/main/java/io/opentelemetry/auto/instrumentation/grizzly/http/v2_3/ExtractAdapter.java new file mode 100644 index 000000000000..2cf9c0946965 --- /dev/null +++ b/instrumentation/grizzly-http-2.3/src/main/java/io/opentelemetry/auto/instrumentation/grizzly/http/v2_3/ExtractAdapter.java @@ -0,0 +1,28 @@ +/* + * Copyright The OpenTelemetry Authors + * + * 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. + */ +package io.opentelemetry.auto.instrumentation.grizzly.http.v2_3; + +import io.opentelemetry.context.propagation.HttpTextFormat; +import org.glassfish.grizzly.http.HttpHeader; + +public class ExtractAdapter implements HttpTextFormat.Getter { + public static final ExtractAdapter GETTER = new ExtractAdapter(); + + @Override + public String get(final HttpHeader carrier, final String key) { + return carrier.getHeader(key); + } +} diff --git a/instrumentation/grizzly-http-2.3/src/main/java/io/opentelemetry/auto/instrumentation/grizzly/http/v2_3/FilterAdvice.java b/instrumentation/grizzly-http-2.3/src/main/java/io/opentelemetry/auto/instrumentation/grizzly/http/v2_3/FilterAdvice.java new file mode 100644 index 000000000000..d0c9085bca13 --- /dev/null +++ b/instrumentation/grizzly-http-2.3/src/main/java/io/opentelemetry/auto/instrumentation/grizzly/http/v2_3/FilterAdvice.java @@ -0,0 +1,48 @@ +/* + * Copyright The OpenTelemetry Authors + * + * 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. + */ +package io.opentelemetry.auto.instrumentation.grizzly.http.v2_3; + +import static io.opentelemetry.auto.bootstrap.instrumentation.decorator.HttpServerDecorator.SPAN_ATTRIBUTE; +import static io.opentelemetry.auto.instrumentation.grizzly.http.v2_3.GrizzlyDecorator.TRACER; + +import io.opentelemetry.context.Scope; +import io.opentelemetry.trace.Span; +import net.bytebuddy.asm.Advice; +import org.glassfish.grizzly.filterchain.BaseFilter; +import org.glassfish.grizzly.filterchain.FilterChainContext; + +public class FilterAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static Scope onEnter( + @Advice.This final BaseFilter it, @Advice.Argument(0) final FilterChainContext ctx) { + if (TRACER.getCurrentSpan().getContext().isValid()) { + return null; + } + final Span span = (Span) ctx.getAttributes().getAttribute(SPAN_ATTRIBUTE); + if (span == null) { + return null; + } + return TRACER.withSpan(span); + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void onExit(@Advice.This final BaseFilter it, @Advice.Enter final Scope scope) { + if (scope != null) { + scope.close(); + } + } +} diff --git a/instrumentation/grizzly-http-2.3/src/main/java/io/opentelemetry/auto/instrumentation/grizzly/http/v2_3/FilterInstrumentation.java b/instrumentation/grizzly-http-2.3/src/main/java/io/opentelemetry/auto/instrumentation/grizzly/http/v2_3/FilterInstrumentation.java new file mode 100644 index 000000000000..ac8f30762477 --- /dev/null +++ b/instrumentation/grizzly-http-2.3/src/main/java/io/opentelemetry/auto/instrumentation/grizzly/http/v2_3/FilterInstrumentation.java @@ -0,0 +1,73 @@ +/* + * Copyright The OpenTelemetry Authors + * + * 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. + */ +package io.opentelemetry.auto.instrumentation.grizzly.http.v2_3; + +import static io.opentelemetry.auto.tooling.ClassLoaderMatcher.hasClassesNamed; +import static java.util.Collections.singletonMap; +import static net.bytebuddy.matcher.ElementMatchers.hasSuperClass; +import static net.bytebuddy.matcher.ElementMatchers.isPublic; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.not; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; + +import com.google.auto.service.AutoService; +import io.opentelemetry.auto.tooling.Instrumenter; +import java.util.Map; +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; +import net.bytebuddy.matcher.ElementMatchers; + +@AutoService(Instrumenter.class) +public final class FilterInstrumentation extends Instrumenter.Default { + + public FilterInstrumentation() { + super("grizzly-filterchain"); + } + + @Override + public ElementMatcher classLoaderMatcher() { + return hasClassesNamed("org.glassfish.grizzly.filterchain.BaseFilter"); + } + + @Override + public ElementMatcher typeMatcher() { + return hasSuperClass(named("org.glassfish.grizzly.filterchain.BaseFilter")) + // HttpCodecFilter is instrumented in the server instrumentation + .and( + not( + ElementMatchers.named( + "org.glassfish.grizzly.http.HttpCodecFilter"))) + .and( + not( + ElementMatchers.named( + "org.glassfish.grizzly.http.HttpServerFilter"))); + } + + @Override + public String[] helperClassNames() { + return new String[] {packageName + ".GrizzlyDecorator", packageName + ".ExtractAdapter"}; + } + + @Override + public Map, String> transformers() { + return singletonMap( + named("handleRead") + .and(takesArgument(0, named("org.glassfish.grizzly.filterchain.FilterChainContext"))) + .and(isPublic()), + packageName + ".FilterAdvice"); + } +} diff --git a/instrumentation/grizzly-http-2.3/src/main/java/io/opentelemetry/auto/instrumentation/grizzly/http/v2_3/GrizzlyDecorator.java b/instrumentation/grizzly-http-2.3/src/main/java/io/opentelemetry/auto/instrumentation/grizzly/http/v2_3/GrizzlyDecorator.java new file mode 100644 index 000000000000..2cb718f0203f --- /dev/null +++ b/instrumentation/grizzly-http-2.3/src/main/java/io/opentelemetry/auto/instrumentation/grizzly/http/v2_3/GrizzlyDecorator.java @@ -0,0 +1,115 @@ +/* + * Copyright The OpenTelemetry Authors + * + * 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. + */ +package io.opentelemetry.auto.instrumentation.grizzly.http.v2_3; + +import static io.opentelemetry.trace.Span.Kind.SERVER; + +import io.opentelemetry.OpenTelemetry; +import io.opentelemetry.auto.bootstrap.instrumentation.decorator.HttpServerDecorator; +import io.opentelemetry.context.Scope; +import io.opentelemetry.trace.Span; +import io.opentelemetry.trace.Tracer; +import java.net.URI; +import java.net.URISyntaxException; +import org.glassfish.grizzly.filterchain.FilterChainContext; +import org.glassfish.grizzly.http.HttpHeader; +import org.glassfish.grizzly.http.HttpRequestPacket; +import org.glassfish.grizzly.http.HttpResponsePacket; + +public class GrizzlyDecorator + extends HttpServerDecorator { + + public static final GrizzlyDecorator DECORATE = new GrizzlyDecorator(); + + public static final Tracer TRACER = + OpenTelemetry.getTracerProvider().get("io.opentelemetry.auto.grizzly-http-2.3"); + + @Override + protected String method(final HttpRequestPacket httpRequest) { + return httpRequest.getMethod().getMethodString(); + } + + @Override + protected URI url(final HttpRequestPacket httpRequest) throws URISyntaxException { + return new URI( + (httpRequest.isSecure() ? "https://" : "http://") + + httpRequest.getRemoteHost() + + ":" + + httpRequest.getLocalPort() + + httpRequest.getRequestURI() + + (httpRequest.getQueryString() != null ? "?" + httpRequest.getQueryString() : "")); + } + + @Override + protected String peerHostIP(final HttpRequestPacket httpRequest) { + return httpRequest.getLocalHost(); + } + + @Override + protected Integer peerPort(final HttpRequestPacket httpRequest) { + return httpRequest.getLocalPort(); + } + + @Override + protected Integer status(final HttpResponsePacket httpResponse) { + return httpResponse.getStatus(); + } + + public static void onHttpServerFilterPrepareResponseExit( + final FilterChainContext ctx, final HttpResponsePacket responsePacket) { + final Span span = (Span) ctx.getAttributes().getAttribute(SPAN_ATTRIBUTE); + DECORATE.onResponse(span, responsePacket); + span.end(); + ctx.getAttributes().removeAttribute(SPAN_ATTRIBUTE); + ctx.getAttributes().removeAttribute(RESPONSE_ATTRIBUTE); + } + + public static void onHttpCodecFilterExit( + final FilterChainContext ctx, final HttpHeader httpHeader) { + // only create a span if there isn't another one attached to the current ctx + // and if the httpHeader has been parsed into a HttpRequestPacket + if (ctx.getAttributes().getAttribute(SPAN_ATTRIBUTE) != null + || !(httpHeader instanceof HttpRequestPacket)) { + return; + } + final HttpRequestPacket httpRequest = (HttpRequestPacket) httpHeader; + final HttpResponsePacket httpResponse = httpRequest.getResponse(); + final Span span = + TRACER + .spanBuilder(DECORATE.spanNameForRequest(httpRequest)) + .setSpanKind(SERVER) + .setParent(extract(httpHeader, ExtractAdapter.GETTER)) + .startSpan(); + try (final Scope ignored = TRACER.withSpan(span)) { + DECORATE.afterStart(span); + ctx.getAttributes().setAttribute(SPAN_ATTRIBUTE, span); + ctx.getAttributes().setAttribute(RESPONSE_ATTRIBUTE, httpResponse); + DECORATE.onConnection(span, httpRequest); + DECORATE.onRequest(span, httpRequest); + } + } + + public static void onFilterChainFail(final FilterChainContext ctx, final Throwable throwable) { + final Span span = (Span) ctx.getAttributes().getAttribute(SPAN_ATTRIBUTE); + if (null != span) { + DECORATE.onError(span, throwable); + DECORATE.beforeFinish(span); + span.end(); + } + ctx.getAttributes().removeAttribute(SPAN_ATTRIBUTE); + ctx.getAttributes().removeAttribute(RESPONSE_ATTRIBUTE); + } +} diff --git a/instrumentation/servlet/request-2.3/src/main/java/io/opentelemetry/auto/instrumentation/servlet/v2_3/Servlet2ResponseStatusAdvice.java b/instrumentation/grizzly-http-2.3/src/main/java/io/opentelemetry/auto/instrumentation/grizzly/http/v2_3/HttpCodecFilterAdvice.java similarity index 55% rename from instrumentation/servlet/request-2.3/src/main/java/io/opentelemetry/auto/instrumentation/servlet/v2_3/Servlet2ResponseStatusAdvice.java rename to instrumentation/grizzly-http-2.3/src/main/java/io/opentelemetry/auto/instrumentation/grizzly/http/v2_3/HttpCodecFilterAdvice.java index 7f32a45da7cf..4733e7225d26 100644 --- a/instrumentation/servlet/request-2.3/src/main/java/io/opentelemetry/auto/instrumentation/servlet/v2_3/Servlet2ResponseStatusAdvice.java +++ b/instrumentation/grizzly-http-2.3/src/main/java/io/opentelemetry/auto/instrumentation/grizzly/http/v2_3/HttpCodecFilterAdvice.java @@ -13,17 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.opentelemetry.auto.instrumentation.servlet.v2_3; +package io.opentelemetry.auto.instrumentation.grizzly.http.v2_3; -import io.opentelemetry.auto.bootstrap.InstrumentationContext; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletResponse; import net.bytebuddy.asm.Advice; +import org.glassfish.grizzly.filterchain.FilterChainContext; +import org.glassfish.grizzly.http.HttpHeader; -public class Servlet2ResponseStatusAdvice { - @Advice.OnMethodEnter(suppress = Throwable.class) - public static void onEnter( - @Advice.This final HttpServletResponse response, @Advice.Argument(0) final Integer status) { - InstrumentationContext.get(ServletResponse.class, Integer.class).put(response, status); +public class HttpCodecFilterAdvice { + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void onExit( + @Advice.Argument(0) final FilterChainContext ctx, + @Advice.Argument(1) final HttpHeader httpHeader) { + GrizzlyDecorator.onHttpCodecFilterExit(ctx, httpHeader); } } diff --git a/instrumentation/grizzly-http-2.3/src/main/java/io/opentelemetry/auto/instrumentation/grizzly/http/v2_3/HttpCodecFilterInstrumentation.java b/instrumentation/grizzly-http-2.3/src/main/java/io/opentelemetry/auto/instrumentation/grizzly/http/v2_3/HttpCodecFilterInstrumentation.java new file mode 100644 index 000000000000..c5cd077a901b --- /dev/null +++ b/instrumentation/grizzly-http-2.3/src/main/java/io/opentelemetry/auto/instrumentation/grizzly/http/v2_3/HttpCodecFilterInstrumentation.java @@ -0,0 +1,71 @@ +/* + * Copyright The OpenTelemetry Authors + * + * 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. + */ +package io.opentelemetry.auto.instrumentation.grizzly.http.v2_3; + +import static net.bytebuddy.matcher.ElementMatchers.isPublic; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; + +import com.google.auto.service.AutoService; +import io.opentelemetry.auto.tooling.Instrumenter; +import java.util.HashMap; +import java.util.Map; +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +@AutoService(Instrumenter.class) +public final class HttpCodecFilterInstrumentation extends Instrumenter.Default { + + public HttpCodecFilterInstrumentation() { + super("grizzly-filterchain"); + } + + @Override + public ElementMatcher typeMatcher() { + return named("org.glassfish.grizzly.http.HttpCodecFilter"); + } + + @Override + protected boolean defaultEnabled() { + return false; + } + + @Override + public String[] helperClassNames() { + return new String[] {packageName + ".GrizzlyDecorator", packageName + ".ExtractAdapter"}; + } + + @Override + public Map, String> transformers() { + final Map, String> transformers = new HashMap<>(); + // this is for 2.3 through 2.3.19 + transformers.put( + named("handleRead") + .and(takesArgument(0, named("org.glassfish.grizzly.filterchain.FilterChainContext"))) + .and(takesArgument(1, named("org.glassfish.grizzly.http.HttpPacketParsing"))) + .and(isPublic()), + packageName + ".HttpCodecFilterOldAdvice"); + // this is for 2.3.20+ + transformers.put( + named("handleRead") + .and(takesArgument(0, named("org.glassfish.grizzly.filterchain.FilterChainContext"))) + .and(takesArgument(1, named("org.glassfish.grizzly.http.HttpHeader"))) + .and(isPublic()), + packageName + ".HttpCodecFilterAdvice"); + return transformers; + } +} diff --git a/instrumentation/grizzly-http-2.3/src/main/java/io/opentelemetry/auto/instrumentation/grizzly/http/v2_3/HttpCodecFilterOldAdvice.java b/instrumentation/grizzly-http-2.3/src/main/java/io/opentelemetry/auto/instrumentation/grizzly/http/v2_3/HttpCodecFilterOldAdvice.java new file mode 100644 index 000000000000..4baf8662f0b4 --- /dev/null +++ b/instrumentation/grizzly-http-2.3/src/main/java/io/opentelemetry/auto/instrumentation/grizzly/http/v2_3/HttpCodecFilterOldAdvice.java @@ -0,0 +1,31 @@ +/* + * Copyright The OpenTelemetry Authors + * + * 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. + */ +package io.opentelemetry.auto.instrumentation.grizzly.http.v2_3; + +import net.bytebuddy.asm.Advice; +import org.glassfish.grizzly.filterchain.FilterChainContext; +import org.glassfish.grizzly.http.HttpHeader; +import org.glassfish.grizzly.http.HttpPacketParsing; + +public class HttpCodecFilterOldAdvice { + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void onExit( + @Advice.Argument(0) final FilterChainContext ctx, + @Advice.Argument(1) final HttpPacketParsing httpHeader) { + GrizzlyDecorator.onHttpCodecFilterExit(ctx, (HttpHeader) httpHeader); + } +} diff --git a/instrumentation/grizzly-http-2.3/src/main/java/io/opentelemetry/auto/instrumentation/grizzly/http/v2_3/HttpServerFilterAdvice.java b/instrumentation/grizzly-http-2.3/src/main/java/io/opentelemetry/auto/instrumentation/grizzly/http/v2_3/HttpServerFilterAdvice.java new file mode 100644 index 000000000000..ba95845c1038 --- /dev/null +++ b/instrumentation/grizzly-http-2.3/src/main/java/io/opentelemetry/auto/instrumentation/grizzly/http/v2_3/HttpServerFilterAdvice.java @@ -0,0 +1,29 @@ +/* + * Copyright The OpenTelemetry Authors + * + * 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. + */ +package io.opentelemetry.auto.instrumentation.grizzly.http.v2_3; + +import net.bytebuddy.asm.Advice; +import org.glassfish.grizzly.filterchain.FilterChainContext; +import org.glassfish.grizzly.http.HttpResponsePacket; + +public class HttpServerFilterAdvice { + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void onExit( + @Advice.Argument(0) final FilterChainContext ctx, + @Advice.Argument(2) final HttpResponsePacket response) { + GrizzlyDecorator.onHttpServerFilterPrepareResponseExit(ctx, response); + } +} diff --git a/instrumentation/grizzly-http-2.3/src/main/java/io/opentelemetry/auto/instrumentation/grizzly/http/v2_3/HttpServerFilterInstrumentation.java b/instrumentation/grizzly-http-2.3/src/main/java/io/opentelemetry/auto/instrumentation/grizzly/http/v2_3/HttpServerFilterInstrumentation.java new file mode 100644 index 000000000000..d7a5f99dd770 --- /dev/null +++ b/instrumentation/grizzly-http-2.3/src/main/java/io/opentelemetry/auto/instrumentation/grizzly/http/v2_3/HttpServerFilterInstrumentation.java @@ -0,0 +1,63 @@ +/* + * Copyright The OpenTelemetry Authors + * + * 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. + */ +package io.opentelemetry.auto.instrumentation.grizzly.http.v2_3; + +import static net.bytebuddy.matcher.ElementMatchers.isPrivate; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; + +import com.google.auto.service.AutoService; +import io.opentelemetry.auto.tooling.Instrumenter; +import java.util.Collections; +import java.util.Map; +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +@AutoService(Instrumenter.class) +public class HttpServerFilterInstrumentation extends Instrumenter.Default { + + public HttpServerFilterInstrumentation() { + super("grizzly-filterchain"); + } + + @Override + public ElementMatcher typeMatcher() { + return named("org.glassfish.grizzly.http.HttpServerFilter"); + } + + @Override + protected boolean defaultEnabled() { + return false; + } + + @Override + public String[] helperClassNames() { + return new String[] {packageName + ".GrizzlyDecorator", packageName + ".ExtractAdapter"}; + } + + @Override + public Map, String> transformers() { + return Collections.singletonMap( + named("prepareResponse") + .and(takesArgument(0, named("org.glassfish.grizzly.filterchain.FilterChainContext"))) + .and(takesArgument(1, named("org.glassfish.grizzly.http.HttpRequestPacket"))) + .and(takesArgument(2, named("org.glassfish.grizzly.http.HttpResponsePacket"))) + .and(takesArgument(3, named("org.glassfish.grizzly.http.HttpContent"))) + .and(isPrivate()), + packageName + ".HttpServerFilterAdvice"); + } +} diff --git a/instrumentation/grizzly-http-2.3/src/test/groovy/GrizzlyAsyncTest.groovy b/instrumentation/grizzly-http-2.3/src/test/groovy/GrizzlyAsyncTest.groovy new file mode 100644 index 000000000000..0eb0aaab56eb --- /dev/null +++ b/instrumentation/grizzly-http-2.3/src/test/groovy/GrizzlyAsyncTest.groovy @@ -0,0 +1,101 @@ +/* + * Copyright The OpenTelemetry Authors + * + * 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. + */ +import org.glassfish.grizzly.http.server.HttpServer +import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory +import org.glassfish.jersey.server.ResourceConfig + +import javax.ws.rs.GET +import javax.ws.rs.Path +import javax.ws.rs.QueryParam +import javax.ws.rs.container.AsyncResponse +import javax.ws.rs.container.Suspended +import javax.ws.rs.core.Response +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors + +import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.ERROR +import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.EXCEPTION +import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.QUERY_PARAM +import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.REDIRECT +import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.SUCCESS + +class GrizzlyAsyncTest extends GrizzlyTest { + + @Override + HttpServer startServer(int port) { + ResourceConfig rc = new ResourceConfig() + rc.register(SimpleExceptionMapper) + rc.register(AsyncServiceResource) + GrizzlyHttpServerFactory.createHttpServer(new URI("http://localhost:$port"), rc) + } + + @Path("/") + static class AsyncServiceResource { + private ExecutorService executor = Executors.newSingleThreadExecutor() + + @GET + @Path("success") + void success(@Suspended AsyncResponse ar) { + executor.execute { + controller(SUCCESS) { + ar.resume(Response.status(SUCCESS.status).entity(SUCCESS.body).build()) + } + } + } + + @GET + @Path("query") + Response query_param(@QueryParam("some") String param, @Suspended AsyncResponse ar) { + controller(QUERY_PARAM) { + ar.resume(Response.status(QUERY_PARAM.status).entity("some=$param".toString()).build()) + } + } + + @GET + @Path("redirect") + void redirect(@Suspended AsyncResponse ar) { + executor.execute { + controller(REDIRECT) { + ar.resume(Response.status(REDIRECT.status).location(new URI(REDIRECT.body)).build()) + } + } + } + + @GET + @Path("error-status") + void error(@Suspended AsyncResponse ar) { + executor.execute { + controller(ERROR) { + ar.resume(Response.status(ERROR.status).entity(ERROR.body).build()) + } + } + } + + @GET + @Path("exception") + void exception(@Suspended AsyncResponse ar) { + executor.execute { + try { + controller(EXCEPTION) { + throw new Exception(EXCEPTION.body) + } + } catch (Exception e) { + ar.resume(e) + } + } + } + } +} diff --git a/instrumentation/grizzly-http-2.3/src/test/groovy/GrizzlyFilterchainServerTest.groovy b/instrumentation/grizzly-http-2.3/src/test/groovy/GrizzlyFilterchainServerTest.groovy new file mode 100644 index 000000000000..70d4c2d6570c --- /dev/null +++ b/instrumentation/grizzly-http-2.3/src/test/groovy/GrizzlyFilterchainServerTest.groovy @@ -0,0 +1,219 @@ +/* + * Copyright The OpenTelemetry Authors + * + * 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. + */ +import io.opentelemetry.auto.test.base.HttpServerTest +import org.glassfish.grizzly.filterchain.BaseFilter +import org.glassfish.grizzly.filterchain.FilterChain +import org.glassfish.grizzly.filterchain.FilterChainBuilder +import org.glassfish.grizzly.filterchain.FilterChainContext +import org.glassfish.grizzly.filterchain.NextAction +import org.glassfish.grizzly.filterchain.TransportFilter +import org.glassfish.grizzly.http.HttpContent +import org.glassfish.grizzly.http.HttpHeader +import org.glassfish.grizzly.http.HttpRequestPacket +import org.glassfish.grizzly.http.HttpResponsePacket +import org.glassfish.grizzly.http.HttpServerFilter +import org.glassfish.grizzly.http.server.HttpServer +import org.glassfish.grizzly.nio.transport.TCPNIOServerConnection +import org.glassfish.grizzly.nio.transport.TCPNIOTransport +import org.glassfish.grizzly.nio.transport.TCPNIOTransportBuilder +import org.glassfish.grizzly.utils.DelayedExecutor +import org.glassfish.grizzly.utils.IdleTimeoutFilter + +import java.util.concurrent.Executors + +import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.AUTH_REQUIRED +import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.ERROR +import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.EXCEPTION +import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.NOT_FOUND +import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.PATH_PARAM +import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.QUERY_PARAM +import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.REDIRECT +import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.SUCCESS +import static java.lang.String.valueOf +import static java.nio.charset.Charset.defaultCharset +import static java.util.concurrent.TimeUnit.MILLISECONDS +import static org.glassfish.grizzly.memory.Buffers.wrap + +class GrizzlyFilterchainServerTest extends HttpServerTest { + + static { + System.setProperty("ota.integration.grizzly-filterchain.enabled", "true") + } + + private TCPNIOTransport transport + private TCPNIOServerConnection serverConnection + + @Override + HttpServer startServer(int port) { + FilterChain filterChain = setUpFilterChain() + setUpTransport(filterChain) + + serverConnection = transport.bind("127.0.0.1", port) + transport.start() + return null + } + + @Override + void stopServer(HttpServer httpServer) { + transport.shutdownNow() + } + + @Override + boolean testException() { + // justification: grizzly async closes the channel which + // looks like a ConnectException to the client when this happens + false + } + + void setUpTransport(FilterChain filterChain) { + TCPNIOTransportBuilder transportBuilder = TCPNIOTransportBuilder.newInstance() + .setOptimizedForMultiplexing(true) + + transportBuilder.setTcpNoDelay(true) + transportBuilder.setKeepAlive(false) + transportBuilder.setReuseAddress(true) + transportBuilder.setServerConnectionBackLog(50) + transportBuilder.setServerSocketSoTimeout(80000) + + transport = transportBuilder.build() + transport.setProcessor(filterChain) + } + + FilterChain setUpFilterChain() { + return FilterChainBuilder.stateless() + .add(createTransportFilter()) + .add(createIdleTimeoutFilter()) + .add(new HttpServerFilter()) + .add(new LastFilter()) + .build() + } + + TransportFilter createTransportFilter() { + return new TransportFilter() + } + + IdleTimeoutFilter createIdleTimeoutFilter() { + return new IdleTimeoutFilter(new DelayedExecutor(Executors.newCachedThreadPool()), 80000, MILLISECONDS) + } + + class LastFilter extends BaseFilter { + + @Override + NextAction handleRead(final FilterChainContext ctx) throws IOException { + if (ctx.getMessage() instanceof HttpContent) { + final HttpContent httpContent = ctx.getMessage() + final HttpHeader httpHeader = httpContent.getHttpHeader() + if (httpHeader instanceof HttpRequestPacket) { + HttpRequestPacket request = (HttpRequestPacket) httpContent.getHttpHeader() + ResponseParameters responseParameters = buildResponse(request) + HttpResponsePacket.Builder builder = HttpResponsePacket.builder(request) + .status(responseParameters.getStatus()) + .header("Content-Length", valueOf(responseParameters.getResponseBody().length)) + responseParameters.fillHeaders(builder) + HttpResponsePacket responsePacket = builder.build() + controller(responseParameters.getEndpoint()) { + ctx.write(HttpContent.builder(responsePacket) + .content(wrap(ctx.getMemoryManager(), responseParameters.getResponseBody())) + .build()) + } + } + } + return ctx.getStopAction() + } + + ResponseParameters buildResponse(HttpRequestPacket request) { + final String uri = request.getRequestURI() + final String requestParams = request.getQueryString() + final String fullPath = uri + (requestParams != null ? "?" + requestParams : "") + + Map headers = new HashMap<>() + + HttpServerTest.ServerEndpoint endpoint + switch (fullPath) { + case "/success": + endpoint = SUCCESS + break + case "/redirect": + endpoint = REDIRECT + headers.put("location", REDIRECT.body) + break + case "/error-status": + endpoint = ERROR + break + case "/exception": + throw new Exception(EXCEPTION.body) + case "/notFound": + endpoint = NOT_FOUND + break + case "/query?some=query": + endpoint = QUERY_PARAM + break + case "/path/123/param": + endpoint = PATH_PARAM + break + case "/authRequired": + endpoint = AUTH_REQUIRED + break + default: + endpoint = NOT_FOUND + break + } + + int status = endpoint.status + String responseBody = endpoint == REDIRECT ? "" : endpoint.body + + final byte[] responseBodyBytes = responseBody.getBytes(defaultCharset()) + return new ResponseParameters(endpoint, status, responseBodyBytes, headers) + } + + class ResponseParameters { + Map headers + HttpServerTest.ServerEndpoint endpoint + int status + byte[] responseBody + + ResponseParameters(HttpServerTest.ServerEndpoint endpoint, + int status, + byte[] responseBody, + Map headers) { + this.endpoint = endpoint + this.status = status + this.responseBody = responseBody + this.headers = headers + } + + int getStatus() { + return status + } + + byte[] getResponseBody() { + return responseBody + } + + HttpServerTest.ServerEndpoint getEndpoint() { + return endpoint + } + + void fillHeaders(HttpResponsePacket.Builder builder) { + for (Map.Entry header : headers.entrySet()) { + builder.header(header.getKey(), header.getValue()) + } + } + } + } +} + + diff --git a/instrumentation/grizzly-http-2.3/src/test/groovy/GrizzlyFilterchainServerTestInstrumentation.java b/instrumentation/grizzly-http-2.3/src/test/groovy/GrizzlyFilterchainServerTestInstrumentation.java new file mode 100644 index 000000000000..b69a8c393023 --- /dev/null +++ b/instrumentation/grizzly-http-2.3/src/test/groovy/GrizzlyFilterchainServerTestInstrumentation.java @@ -0,0 +1,40 @@ +/* + * Copyright The OpenTelemetry Authors + * + * 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. + */ +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; + +import com.google.auto.service.AutoService; +import io.opentelemetry.auto.test.base.HttpServerTestAdvice; +import io.opentelemetry.auto.tooling.Instrumenter; +import net.bytebuddy.agent.builder.AgentBuilder; + +@AutoService(Instrumenter.class) +public class GrizzlyFilterchainServerTestInstrumentation implements Instrumenter { + + @Override + public AgentBuilder instrument(final AgentBuilder agentBuilder) { + return agentBuilder + .type(named("org.glassfish.grizzly.http.HttpCodecFilter")) + .transform( + new AgentBuilder.Transformer.ForAdvice() + .advice( + named("handleRead") + .and( + takesArgument( + 0, named("org.glassfish.grizzly.filterchain.FilterChainContext"))), + HttpServerTestAdvice.ServerEntryAdvice.class.getName())); + } +} diff --git a/instrumentation/grizzly-http-2.3/src/test/groovy/GrizzlyIOStrategyTest.groovy b/instrumentation/grizzly-http-2.3/src/test/groovy/GrizzlyIOStrategyTest.groovy new file mode 100644 index 000000000000..60fe605c08e6 --- /dev/null +++ b/instrumentation/grizzly-http-2.3/src/test/groovy/GrizzlyIOStrategyTest.groovy @@ -0,0 +1,66 @@ +/* + * Copyright The OpenTelemetry Authors + * + * 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. + */ +import org.glassfish.grizzly.IOStrategy +import org.glassfish.grizzly.http.server.HttpServer +import org.glassfish.grizzly.strategies.LeaderFollowerNIOStrategy +import org.glassfish.grizzly.strategies.SameThreadIOStrategy +import org.glassfish.grizzly.strategies.SimpleDynamicNIOStrategy +import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory +import org.glassfish.jersey.server.ResourceConfig + +abstract class GrizzlyIOStrategyTest extends GrizzlyTest { + + static { + System.setProperty("ota.integration.grizzly-http.enabled", "true") + } + + @Override + HttpServer startServer(int port) { + ResourceConfig rc = new ResourceConfig() + rc.register(SimpleExceptionMapper) + rc.register(ServiceResource) + def server = GrizzlyHttpServerFactory.createHttpServer(new URI("http://localhost:$port"), rc) + server.getListener("grizzly").getTransport().setIOStrategy(strategy()) + // Default in NIOTransportBuilder is WorkerThreadIOStrategy, so don't need to retest that.s + return server + } + + abstract IOStrategy strategy() +} + +class LeaderFollowerTest extends GrizzlyIOStrategyTest { + + @Override + IOStrategy strategy() { + return LeaderFollowerNIOStrategy.instance + } +} + +class SameThreadTest extends GrizzlyIOStrategyTest { + + @Override + IOStrategy strategy() { + return SameThreadIOStrategy.instance + } +} + +class SimpleDynamicTest extends GrizzlyIOStrategyTest { + + @Override + IOStrategy strategy() { + return SimpleDynamicNIOStrategy.instance + } +} diff --git a/instrumentation/grizzly-http-2.3/src/test/groovy/GrizzlyTest.groovy b/instrumentation/grizzly-http-2.3/src/test/groovy/GrizzlyTest.groovy new file mode 100644 index 000000000000..f4f72c28314e --- /dev/null +++ b/instrumentation/grizzly-http-2.3/src/test/groovy/GrizzlyTest.groovy @@ -0,0 +1,108 @@ +/* + * Copyright The OpenTelemetry Authors + * + * 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. + */ +import io.opentelemetry.auto.test.base.HttpServerTest +import org.glassfish.grizzly.http.server.HttpServer +import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory +import org.glassfish.jersey.server.ResourceConfig + +import javax.ws.rs.GET +import javax.ws.rs.NotFoundException +import javax.ws.rs.Path +import javax.ws.rs.QueryParam +import javax.ws.rs.core.Response +import javax.ws.rs.ext.ExceptionMapper + +import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.ERROR +import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.EXCEPTION +import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.QUERY_PARAM +import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.REDIRECT +import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.SUCCESS + +class GrizzlyTest extends HttpServerTest { + + static { + System.setProperty("ota.integration.grizzly-filterchain.enabled", "true") + } + + @Override + HttpServer startServer(int port) { + ResourceConfig rc = new ResourceConfig() + rc.register(SimpleExceptionMapper) + rc.register(ServiceResource) + GrizzlyHttpServerFactory.createHttpServer(new URI("http://localhost:$port"), rc) + } + + @Override + void stopServer(HttpServer server) { + server.stop() + } + + static class SimpleExceptionMapper implements ExceptionMapper { + + @Override + Response toResponse(Throwable exception) { + if (exception instanceof NotFoundException) { + return exception.getResponse() + } + Response.status(500).entity(exception.message).build() + } + } + + @Path("/") + static class ServiceResource { + + @GET + @Path("success") + Response success() { + controller(SUCCESS) { + Response.status(SUCCESS.status).entity(SUCCESS.body).build() + } + } + + @GET + @Path("query") + Response query_param(@QueryParam("some") String param) { + controller(QUERY_PARAM) { + Response.status(QUERY_PARAM.status).entity("some=$param".toString()).build() + } + } + + @GET + @Path("redirect") + Response redirect() { + controller(REDIRECT) { + Response.status(REDIRECT.status).location(new URI(REDIRECT.body)).build() + } + } + + @GET + @Path("error-status") + Response error() { + controller(ERROR) { + Response.status(ERROR.status).entity(ERROR.body).build() + } + } + + @GET + @Path("exception") + Response exception() { + controller(EXCEPTION) { + throw new Exception(EXCEPTION.body) + } + return null + } + } +} diff --git a/instrumentation/grpc-1.5/src/main/java/io/opentelemetry/auto/instrumentation/grpc/server/TracingServerInterceptor.java b/instrumentation/grpc-1.5/src/main/java/io/opentelemetry/auto/instrumentation/grpc/server/TracingServerInterceptor.java index a1f21448d112..ff91e55fbcf2 100644 --- a/instrumentation/grpc-1.5/src/main/java/io/opentelemetry/auto/instrumentation/grpc/server/TracingServerInterceptor.java +++ b/instrumentation/grpc-1.5/src/main/java/io/opentelemetry/auto/instrumentation/grpc/server/TracingServerInterceptor.java @@ -81,6 +81,7 @@ public ServerCall.Listener interceptCall( throw e; } } + // span finished by TracingServerCall // This ensures the server implementation can see the span in scope return new TracingServerCallListener<>(span, result); diff --git a/instrumentation/grpc-1.5/src/test/groovy/GrpcTest.groovy b/instrumentation/grpc-1.5/src/test/groovy/GrpcTest.groovy index c3456c584f33..0c3f9f3c3d59 100644 --- a/instrumentation/grpc-1.5/src/test/groovy/GrpcTest.groovy +++ b/instrumentation/grpc-1.5/src/test/groovy/GrpcTest.groovy @@ -45,11 +45,6 @@ class GrpcTest extends AgentTestRunner { final Helloworld.Request req, final StreamObserver responseObserver) { final Helloworld.Response reply = Helloworld.Response.newBuilder().setMessage("Hello $req.name").build() CommonTaskExecutor.INSTANCE.execute { - println "==================================" - println "==================================" - println "VALID????????? " + testTracer.getCurrentSpan().getContext().isValid() - println "==================================" - println "==================================" if (!testTracer.getCurrentSpan().getContext().isValid()) { responseObserver.onError(new IllegalStateException("no active span")) } else { diff --git a/instrumentation/instrumentation.gradle b/instrumentation/instrumentation.gradle index c86928a39b1a..bad5d9852f15 100644 --- a/instrumentation/instrumentation.gradle +++ b/instrumentation/instrumentation.gradle @@ -81,6 +81,8 @@ shadowJar { // Prevents conflict with other SLF4J instances. Important for premain. relocate 'org.slf4j', 'io.opentelemetry.auto.slf4j' + duplicatesStrategy = DuplicatesStrategy.FAIL + dependencies { exclude(project(':auto-bootstrap')) } diff --git a/instrumentation/java-concurrent/java-concurrent.gradle b/instrumentation/java-concurrent/java-concurrent.gradle index be5d476c19da..afae81659fe0 100644 --- a/instrumentation/java-concurrent/java-concurrent.gradle +++ b/instrumentation/java-concurrent/java-concurrent.gradle @@ -40,6 +40,7 @@ test.dependsOn slickTest // force this one test to run on java8 only slickTest { onlyIf { - executable == toExecutable(findJavaHome(JavaVersion.VERSION_1_8)) + String java8Home = findJavaHome(JavaVersion.VERSION_1_8) + java8Home != null && executable == toExecutable(java8Home) } } diff --git a/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0.gradle b/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0.gradle index 64a2c6ba7843..74cc42506e42 100644 --- a/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0.gradle +++ b/instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0.gradle @@ -56,6 +56,7 @@ test.dependsOn resteasy31Test // force this one test to run on java8 only resteasy31Test { onlyIf { - executable == toExecutable(findJavaHome(JavaVersion.VERSION_1_8)) + String java8Home = findJavaHome(JavaVersion.VERSION_1_8) + java8Home != null && executable == toExecutable(java8Home) } } diff --git a/instrumentation/jaxrs/jaxrs-2.0/src/main/java/io/opentelemetry/auto/instrumentation/jaxrs/v2_0/JaxRsAnnotationsInstrumentation.java b/instrumentation/jaxrs/jaxrs-2.0/src/main/java/io/opentelemetry/auto/instrumentation/jaxrs/v2_0/JaxRsAnnotationsInstrumentation.java index 7ea0707dc686..ff15f56ed189 100644 --- a/instrumentation/jaxrs/jaxrs-2.0/src/main/java/io/opentelemetry/auto/instrumentation/jaxrs/v2_0/JaxRsAnnotationsInstrumentation.java +++ b/instrumentation/jaxrs/jaxrs-2.0/src/main/java/io/opentelemetry/auto/instrumentation/jaxrs/v2_0/JaxRsAnnotationsInstrumentation.java @@ -168,6 +168,7 @@ public static void stopSpan( span.end(); } spanWithScope.closeScope(); + // else span finished by AsyncResponseAdvice } } } diff --git a/instrumentation/jetty-8.0/src/main/java/io/opentelemetry/auto/instrumentation/jetty/JettyHandlerAdvice.java b/instrumentation/jetty-8.0/src/main/java/io/opentelemetry/auto/instrumentation/jetty/JettyHandlerAdvice.java index 528e9568f457..3f7cfad44e91 100644 --- a/instrumentation/jetty-8.0/src/main/java/io/opentelemetry/auto/instrumentation/jetty/JettyHandlerAdvice.java +++ b/instrumentation/jetty-8.0/src/main/java/io/opentelemetry/auto/instrumentation/jetty/JettyHandlerAdvice.java @@ -97,8 +97,9 @@ public static void stopSpan( if (!req.isAsyncStarted() && activated.compareAndSet(false, true)) { DECORATE.onResponse(span, resp); DECORATE.beforeFinish(span); - span.end(); // Finish the span manually since finishSpanOnClose was false + span.end(); } + // else span finished by TagSettingAsyncListener } spanWithScope.closeScope(); } diff --git a/instrumentation/jsp-2.3/src/test/groovy/JSPInstrumentationBasicTests.groovy b/instrumentation/jsp-2.3/src/test/groovy/JSPInstrumentationBasicTests.groovy index 722aeddbdd73..42afee3f57d1 100644 --- a/instrumentation/jsp-2.3/src/test/groovy/JSPInstrumentationBasicTests.groovy +++ b/instrumentation/jsp-2.3/src/test/groovy/JSPInstrumentationBasicTests.groovy @@ -14,7 +14,6 @@ * limitations under the License. */ import com.google.common.io.Files -import io.opentelemetry.auto.bootstrap.instrumentation.decorator.HttpServerDecorator import io.opentelemetry.auto.instrumentation.api.MoreTags import io.opentelemetry.auto.instrumentation.api.Tags import io.opentelemetry.auto.test.AgentTestRunner @@ -34,6 +33,7 @@ import spock.lang.Unroll import static io.opentelemetry.trace.Span.Kind.SERVER +//TODO should this be HttpServerTest? class JSPInstrumentationBasicTests extends AgentTestRunner { static { @@ -106,7 +106,7 @@ class JSPInstrumentationBasicTests extends AgentTestRunner { trace(0, 3) { span(0) { parent() - operationName expectedOperationName("GET") + operationName expectedOperationName() spanKind SERVER errored false tags { @@ -168,7 +168,7 @@ class JSPInstrumentationBasicTests extends AgentTestRunner { trace(0, 3) { span(0) { parent() - operationName expectedOperationName("GET") + operationName expectedOperationName() spanKind SERVER errored false tags { @@ -227,7 +227,7 @@ class JSPInstrumentationBasicTests extends AgentTestRunner { trace(0, 3) { span(0) { parent() - operationName expectedOperationName("POST") + operationName expectedOperationName() spanKind SERVER errored false tags { @@ -283,7 +283,7 @@ class JSPInstrumentationBasicTests extends AgentTestRunner { trace(0, 3) { span(0) { parent() - operationName expectedOperationName("GET") + operationName expectedOperationName() spanKind SERVER errored true tags { @@ -358,7 +358,7 @@ class JSPInstrumentationBasicTests extends AgentTestRunner { trace(0, 3) { span(0) { parent() - operationName expectedOperationName("GET") + operationName expectedOperationName() spanKind SERVER errored false tags { @@ -413,7 +413,7 @@ class JSPInstrumentationBasicTests extends AgentTestRunner { trace(0, 7) { span(0) { parent() - operationName expectedOperationName("GET") + operationName expectedOperationName() spanKind SERVER errored false tags { @@ -508,7 +508,7 @@ class JSPInstrumentationBasicTests extends AgentTestRunner { trace(0, 2) { span(0) { parent() - operationName expectedOperationName("GET") + operationName expectedOperationName() spanKind SERVER errored true tags { @@ -562,7 +562,7 @@ class JSPInstrumentationBasicTests extends AgentTestRunner { span(0) { parent() // serviceName jspWebappContext - operationName expectedOperationName("GET") + operationName expectedOperationName() spanKind SERVER // FIXME: this is not a great span name for serving static content. // spanName "GET /$jspWebappContext/$staticFile" @@ -588,7 +588,10 @@ class JSPInstrumentationBasicTests extends AgentTestRunner { staticFile = "common/hello.html" } - String expectedOperationName(String method) { - return method != null ? "HTTP $method" : HttpServerDecorator.DEFAULT_SPAN_NAME + //Simple class name plus method name of the entry point of the given servlet container. + //"Entry point" here means the first filter or servlet that accepts incoming requests. + //This will serve as a default name of the SERVER span created for this request. + protected String expectedOperationName() { + 'ApplicationFilterChain.doFilter' } } diff --git a/instrumentation/jsp-2.3/src/test/groovy/JSPInstrumentationForwardTests.groovy b/instrumentation/jsp-2.3/src/test/groovy/JSPInstrumentationForwardTests.groovy index 2b8590d4a4cd..5ad61d543725 100644 --- a/instrumentation/jsp-2.3/src/test/groovy/JSPInstrumentationForwardTests.groovy +++ b/instrumentation/jsp-2.3/src/test/groovy/JSPInstrumentationForwardTests.groovy @@ -14,7 +14,6 @@ * limitations under the License. */ import com.google.common.io.Files -import io.opentelemetry.auto.bootstrap.instrumentation.decorator.HttpServerDecorator import io.opentelemetry.auto.instrumentation.api.MoreTags import io.opentelemetry.auto.instrumentation.api.Tags import io.opentelemetry.auto.test.AgentTestRunner @@ -105,7 +104,7 @@ class JSPInstrumentationForwardTests extends AgentTestRunner { trace(0, 5) { span(0) { parent() - operationName expectedOperationName("GET") + operationName expectedOperationName() spanKind SERVER errored false tags { @@ -186,7 +185,7 @@ class JSPInstrumentationForwardTests extends AgentTestRunner { trace(0, 3) { span(0) { parent() - operationName expectedOperationName("GET") + operationName expectedOperationName() spanKind SERVER errored false tags { @@ -241,7 +240,7 @@ class JSPInstrumentationForwardTests extends AgentTestRunner { trace(0, 9) { span(0) { parent() - operationName expectedOperationName("GET") + operationName expectedOperationName() spanKind SERVER errored false tags { @@ -359,7 +358,7 @@ class JSPInstrumentationForwardTests extends AgentTestRunner { trace(0, 7) { span(0) { parent() - operationName expectedOperationName("GET") + operationName expectedOperationName() spanKind SERVER errored false tags { @@ -456,7 +455,7 @@ class JSPInstrumentationForwardTests extends AgentTestRunner { trace(0, 4) { span(0) { parent() - operationName expectedOperationName("GET") + operationName expectedOperationName() spanKind SERVER errored true tags { @@ -524,7 +523,7 @@ class JSPInstrumentationForwardTests extends AgentTestRunner { trace(0, 3) { span(0) { parent() - operationName expectedOperationName("GET") + operationName expectedOperationName() spanKind SERVER errored false tags { @@ -566,7 +565,10 @@ class JSPInstrumentationForwardTests extends AgentTestRunner { res.close() } - String expectedOperationName(String method) { - return method != null ? "HTTP $method" : HttpServerDecorator.DEFAULT_SPAN_NAME + //Simple class name plus method name of the entry point of the given servlet container. + //"Entry point" here means the first filter or servlet that accepts incoming requests. + //This will serve as a default name of the SERVER span created for this request. + protected String expectedOperationName() { + 'ApplicationFilterChain.doFilter' } } diff --git a/instrumentation/kafka-clients-0.11/src/main/java/io/opentelemetry/auto/instrumentation/kafkaclients/KafkaProducerInstrumentation.java b/instrumentation/kafka-clients-0.11/src/main/java/io/opentelemetry/auto/instrumentation/kafkaclients/KafkaProducerInstrumentation.java index 7244d676b04f..bbcbe4a14718 100644 --- a/instrumentation/kafka-clients-0.11/src/main/java/io/opentelemetry/auto/instrumentation/kafkaclients/KafkaProducerInstrumentation.java +++ b/instrumentation/kafka-clients-0.11/src/main/java/io/opentelemetry/auto/instrumentation/kafkaclients/KafkaProducerInstrumentation.java @@ -30,6 +30,7 @@ import com.google.auto.service.AutoService; import io.grpc.Context; import io.opentelemetry.OpenTelemetry; +import io.opentelemetry.auto.config.Config; import io.opentelemetry.auto.instrumentation.api.SpanWithScope; import io.opentelemetry.auto.tooling.Instrumenter; import io.opentelemetry.context.Scope; @@ -94,7 +95,12 @@ public static SpanWithScope onEnter( // Do not inject headers for batch versions below 2 // This is how similar check is being done in Kafka client itself: // https://github.com/apache/kafka/blob/05fcfde8f69b0349216553f711fdfc3f0259c601/clients/src/main/java/org/apache/kafka/common/record/MemoryRecordsBuilder.java#L411-L412 - if (apiVersions.maxUsableProduceMagic() >= RecordBatch.MAGIC_VALUE_V2) { + // Also, do not inject headers if specified by JVM option or environment variable + // This can help in mixed client environments where clients < 0.11 that do not support + // headers attempt to read messages that were produced by clients > 0.11 and the magic + // value of the broker(s) is >= 2 + if (apiVersions.maxUsableProduceMagic() >= RecordBatch.MAGIC_VALUE_V2 + && Config.get().isKafkaClientPropagationEnabled()) { final Context context = withSpan(span, Context.current()); try { OpenTelemetry.getPropagators() @@ -126,8 +132,8 @@ public static void stopSpan( final Span span = spanWithScope.getSpan(); DECORATE.onError(span, throwable); DECORATE.beforeFinish(span); - span.end(); spanWithScope.closeScope(); + // span finished by ProducerCallback } } diff --git a/instrumentation/kafka-clients-0.11/src/test/groovy/KafkaClientTest.groovy b/instrumentation/kafka-clients-0.11/src/test/groovy/KafkaClientTest.groovy index 371579d5c59e..1f85d963458e 100644 --- a/instrumentation/kafka-clients-0.11/src/test/groovy/KafkaClientTest.groovy +++ b/instrumentation/kafka-clients-0.11/src/test/groovy/KafkaClientTest.groovy @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import io.opentelemetry.auto.config.Config import io.opentelemetry.auto.test.AgentTestRunner import org.apache.kafka.clients.consumer.ConsumerConfig import org.apache.kafka.clients.consumer.ConsumerRecord @@ -29,10 +30,12 @@ import org.springframework.kafka.listener.MessageListener import org.springframework.kafka.test.rule.KafkaEmbedded import org.springframework.kafka.test.utils.ContainerTestUtils import org.springframework.kafka.test.utils.KafkaTestUtils +import spock.lang.Unroll import java.util.concurrent.LinkedBlockingQueue import java.util.concurrent.TimeUnit +import static io.opentelemetry.auto.test.utils.ConfigUtils.withConfigOverride import static io.opentelemetry.trace.Span.Kind.CONSUMER import static io.opentelemetry.trace.Span.Kind.PRODUCER @@ -187,4 +190,72 @@ class KafkaClientTest extends AgentTestRunner { } + @Unroll + def "test kafka client header propagation manual config"() { + setup: + def senderProps = KafkaTestUtils.senderProps(embeddedKafka.getBrokersAsString()) + def producerFactory = new DefaultKafkaProducerFactory(senderProps) + def kafkaTemplate = new KafkaTemplate(producerFactory) + + // set up the Kafka consumer properties + def consumerProperties = KafkaTestUtils.consumerProps("sender", "false", embeddedKafka) + + // create a Kafka consumer factory + def consumerFactory = new DefaultKafkaConsumerFactory(consumerProperties) + + // set the topic that needs to be consumed + def containerProperties + try { + // Different class names for test and latestDepTest. + containerProperties = Class.forName("org.springframework.kafka.listener.config.ContainerProperties").newInstance(SHARED_TOPIC) + } catch (ClassNotFoundException | NoClassDefFoundError e) { + containerProperties = Class.forName("org.springframework.kafka.listener.ContainerProperties").newInstance(SHARED_TOPIC) + } + + // create a Kafka MessageListenerContainer + def container = new KafkaMessageListenerContainer<>(consumerFactory, containerProperties) + + // create a thread safe queue to store the received message + def records = new LinkedBlockingQueue>() + + // setup a Kafka message listener + container.setupMessageListener(new MessageListener() { + @Override + void onMessage(ConsumerRecord record) { + TEST_WRITER.waitForTraces(1) // ensure consistent ordering of traces + records.add(record) + } + }) + + // start the container and underlying message listener + container.start() + + // wait until the container has the required number of assigned partitions + ContainerTestUtils.waitForAssignment(container, embeddedKafka.getPartitionsPerTopic()) + + when: + String message = "Testing without headers" + withConfigOverride(Config.KAFKA_CLIENT_PROPAGATION_ENABLED, value) { + kafkaTemplate.send(SHARED_TOPIC, message) + } + + then: + // check that the message was received + def received = records.poll(5, TimeUnit.SECONDS) + + received.headers().iterator().hasNext() == expected + + cleanup: + producerFactory.stop() + container?.stop() + + where: + value | expected + "false" | false + "true" | true + String.valueOf(Config.DEFAULT_KAFKA_CLIENT_PROPAGATION_ENABLED) | true + + } + } + diff --git a/instrumentation/khttp-0.1/src/main/java/io/opentelemetry/auto/instrumentation/khttp/KHttpAdvice.java b/instrumentation/khttp-0.1/src/main/java/io/opentelemetry/auto/instrumentation/khttp/KHttpAdvice.java index d04bc8d65fbf..31a7999b7930 100644 --- a/instrumentation/khttp-0.1/src/main/java/io/opentelemetry/auto/instrumentation/khttp/KHttpAdvice.java +++ b/instrumentation/khttp-0.1/src/main/java/io/opentelemetry/auto/instrumentation/khttp/KHttpAdvice.java @@ -39,7 +39,7 @@ public class KHttpAdvice { public static SpanWithScope methodEnter( @Advice.Argument(value = 0) String method, @Advice.Argument(value = 1) String uri, - @Advice.Argument(value = 2) Map headers) { + @Advice.Argument(value = 2, readOnly = false) Map headers) { final int callDepth = CallDepthThreadLocalMap.incrementCallDepth(KHttp.class); if (callDepth > 0) { @@ -53,7 +53,8 @@ public static SpanWithScope methodEnter( final Context context = withSpan(span, Context.current()); - OpenTelemetry.getPropagators().getHttpTextFormat().inject(context, asWritable(headers), SETTER); + headers = asWritable(headers); + OpenTelemetry.getPropagators().getHttpTextFormat().inject(context, headers, SETTER); return new SpanWithScope(span, withScopedContext(context)); } diff --git a/instrumentation/khttp-0.1/src/main/java/io/opentelemetry/auto/instrumentation/khttp/KHttpHeadersInjectAdapter.java b/instrumentation/khttp-0.1/src/main/java/io/opentelemetry/auto/instrumentation/khttp/KHttpHeadersInjectAdapter.java index 1809f7127c90..ce321eba37d9 100644 --- a/instrumentation/khttp-0.1/src/main/java/io/opentelemetry/auto/instrumentation/khttp/KHttpHeadersInjectAdapter.java +++ b/instrumentation/khttp-0.1/src/main/java/io/opentelemetry/auto/instrumentation/khttp/KHttpHeadersInjectAdapter.java @@ -21,23 +21,9 @@ public class KHttpHeadersInjectAdapter implements HttpTextFormat.Setter> { - private static Class emptyMap; - - static { - try { - emptyMap = Class.forName("kotlin.collections.EmptyMap"); - } catch (ClassNotFoundException e) { - } - } - public static Map asWritable(Map headers) { - // EmptyMap is read-only so we have to substitute it with writable instance to be able to inject - // headers - if (emptyMap != null && emptyMap.isInstance(headers)) { - return new HashMap<>(); - } else { - return headers; - } + // Kotlin likes to use read-only data structures, so wrap into new writable map + return new HashMap<>(headers); } public static final KHttpHeadersInjectAdapter SETTER = new KHttpHeadersInjectAdapter(); diff --git a/instrumentation/lettuce/lettuce-4.0/src/main/java/io/opentelemetry/auto/instrumentation/lettuce/v4_0/LettuceAsyncCommandsInstrumentation.java b/instrumentation/lettuce/lettuce-4.0/src/main/java/io/opentelemetry/auto/instrumentation/lettuce/v4_0/LettuceAsyncCommandsInstrumentation.java index 83e066f53a4a..3b35c66b64f5 100644 --- a/instrumentation/lettuce/lettuce-4.0/src/main/java/io/opentelemetry/auto/instrumentation/lettuce/v4_0/LettuceAsyncCommandsInstrumentation.java +++ b/instrumentation/lettuce/lettuce-4.0/src/main/java/io/opentelemetry/auto/instrumentation/lettuce/v4_0/LettuceAsyncCommandsInstrumentation.java @@ -31,7 +31,7 @@ public class LettuceAsyncCommandsInstrumentation extends Instrumenter.Default { public LettuceAsyncCommandsInstrumentation() { - super("lettuce", "lettuce-4-async"); + super("lettuce", "lettuce-4", "lettuce-4-async"); } @Override diff --git a/instrumentation/lettuce/lettuce-4.0/src/main/java/io/opentelemetry/auto/instrumentation/lettuce/v4_0/LettuceClientInstrumentation.java b/instrumentation/lettuce/lettuce-4.0/src/main/java/io/opentelemetry/auto/instrumentation/lettuce/v4_0/LettuceClientInstrumentation.java index 1d6cd36a0636..a03e2488230c 100644 --- a/instrumentation/lettuce/lettuce-4.0/src/main/java/io/opentelemetry/auto/instrumentation/lettuce/v4_0/LettuceClientInstrumentation.java +++ b/instrumentation/lettuce/lettuce-4.0/src/main/java/io/opentelemetry/auto/instrumentation/lettuce/v4_0/LettuceClientInstrumentation.java @@ -30,7 +30,7 @@ public final class LettuceClientInstrumentation extends Instrumenter.Default { public LettuceClientInstrumentation() { - super("lettuce"); + super("lettuce", "lettuce-4"); } @Override diff --git a/instrumentation/lettuce/lettuce-4.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/v4_0/InstrumentationPoints.java b/instrumentation/lettuce/lettuce-4.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/v4_0/InstrumentationPoints.java index 24edf3a1bf2a..c3826e8d9e62 100644 --- a/instrumentation/lettuce/lettuce-4.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/v4_0/InstrumentationPoints.java +++ b/instrumentation/lettuce/lettuce-4.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/v4_0/InstrumentationPoints.java @@ -54,9 +54,7 @@ public static void afterCommand( LettuceClientDecorator.DECORATE.onError(span, throwable); LettuceClientDecorator.DECORATE.beforeFinish(span); span.end(); - } else if (finishSpanEarly(command)) { - span.end(); - } else { + } else if (expectsResponse(command)) { asyncCommand.handleAsync( (value, ex) -> { if (ex instanceof CancellationException) { @@ -68,8 +66,13 @@ public static void afterCommand( span.end(); return null; }); + } else { + // No response is expected, so we must finish the span now. + LettuceClientDecorator.DECORATE.beforeFinish(span); + span.end(); } spanWithScope.closeScope(); + // span may be finished by handleAsync call above. } public static SpanWithScope beforeConnect(final RedisURI redisURI) { @@ -96,11 +99,11 @@ public static void afterConnect(final SpanWithScope spanWithScope, final Throwab * we must close the span early in order to provide info for the users * * @param command - * @return true if finish the span early (the command will not have a return value) + * @return false if the span should finish early (the command will not have a return value) */ - public static boolean finishSpanEarly(final RedisCommand command) { + public static boolean expectsResponse(final RedisCommand command) { final ProtocolKeyword keyword = command.getType(); - return isNonInstrumentingCommand(keyword) || isNonInstrumentingKeyword(keyword); + return !(isNonInstrumentingCommand(keyword) || isNonInstrumentingKeyword(keyword)); } private static boolean isNonInstrumentingCommand(final ProtocolKeyword keyword) { diff --git a/instrumentation/lettuce/lettuce-5.0/src/main/java/io/opentelemetry/auto/instrumentation/lettuce/v5_0/LettuceAsyncCommandsInstrumentation.java b/instrumentation/lettuce/lettuce-5.0/src/main/java/io/opentelemetry/auto/instrumentation/lettuce/v5_0/LettuceAsyncCommandsInstrumentation.java index 0979d53ce7bc..8a35bf25f95c 100644 --- a/instrumentation/lettuce/lettuce-5.0/src/main/java/io/opentelemetry/auto/instrumentation/lettuce/v5_0/LettuceAsyncCommandsInstrumentation.java +++ b/instrumentation/lettuce/lettuce-5.0/src/main/java/io/opentelemetry/auto/instrumentation/lettuce/v5_0/LettuceAsyncCommandsInstrumentation.java @@ -31,7 +31,7 @@ public class LettuceAsyncCommandsInstrumentation extends Instrumenter.Default { public LettuceAsyncCommandsInstrumentation() { - super("lettuce", "lettuce-5-async"); + super("lettuce", "lettuce-5", "lettuce-5-async"); } @Override diff --git a/instrumentation/lettuce/lettuce-5.0/src/main/java/io/opentelemetry/auto/instrumentation/lettuce/v5_0/LettuceClientInstrumentation.java b/instrumentation/lettuce/lettuce-5.0/src/main/java/io/opentelemetry/auto/instrumentation/lettuce/v5_0/LettuceClientInstrumentation.java index be04be7e8fba..e7b1310ba3cf 100644 --- a/instrumentation/lettuce/lettuce-5.0/src/main/java/io/opentelemetry/auto/instrumentation/lettuce/v5_0/LettuceClientInstrumentation.java +++ b/instrumentation/lettuce/lettuce-5.0/src/main/java/io/opentelemetry/auto/instrumentation/lettuce/v5_0/LettuceClientInstrumentation.java @@ -35,7 +35,7 @@ public final class LettuceClientInstrumentation extends Instrumenter.Default { public LettuceClientInstrumentation() { - super("lettuce"); + super("lettuce", "lettuce-5"); } @Override diff --git a/instrumentation/lettuce/lettuce-5.0/src/main/java/io/opentelemetry/auto/instrumentation/lettuce/v5_0/LettuceReactiveCommandsInstrumentation.java b/instrumentation/lettuce/lettuce-5.0/src/main/java/io/opentelemetry/auto/instrumentation/lettuce/v5_0/LettuceReactiveCommandsInstrumentation.java index 98344301491e..c56b259293ad 100644 --- a/instrumentation/lettuce/lettuce-5.0/src/main/java/io/opentelemetry/auto/instrumentation/lettuce/v5_0/LettuceReactiveCommandsInstrumentation.java +++ b/instrumentation/lettuce/lettuce-5.0/src/main/java/io/opentelemetry/auto/instrumentation/lettuce/v5_0/LettuceReactiveCommandsInstrumentation.java @@ -35,7 +35,7 @@ public class LettuceReactiveCommandsInstrumentation extends Instrumenter.Default { public LettuceReactiveCommandsInstrumentation() { - super("lettuce", "lettuce-5-rx"); + super("lettuce", "lettuce-5", "lettuce-5-rx"); } @Override diff --git a/instrumentation/lettuce/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/v5_0/ConnectionFutureAdvice.java b/instrumentation/lettuce/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/v5_0/ConnectionFutureAdvice.java index 2aa2ab37eea8..a236cd60a36d 100644 --- a/instrumentation/lettuce/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/v5_0/ConnectionFutureAdvice.java +++ b/instrumentation/lettuce/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/v5_0/ConnectionFutureAdvice.java @@ -50,5 +50,6 @@ public static void stopSpan( } connectionFuture.handleAsync(new LettuceAsyncBiFunction<>(span)); spanWithScope.closeScope(); + // span finished by LettuceAsyncBiFunction } } diff --git a/instrumentation/lettuce/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/v5_0/LettuceAsyncCommandsAdvice.java b/instrumentation/lettuce/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/v5_0/LettuceAsyncCommandsAdvice.java index eaa1bada7922..af9a48cc7af6 100644 --- a/instrumentation/lettuce/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/v5_0/LettuceAsyncCommandsAdvice.java +++ b/instrumentation/lettuce/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/v5_0/LettuceAsyncCommandsAdvice.java @@ -15,6 +15,7 @@ */ package io.opentelemetry.auto.instrumentation.lettuce.v5_0; +import static io.opentelemetry.auto.instrumentation.lettuce.v5_0.LettuceInstrumentationUtil.expectsResponse; import static io.opentelemetry.trace.Span.Kind.CLIENT; import static io.opentelemetry.trace.TracingContextUtils.currentContextWith; @@ -56,11 +57,13 @@ public static void stopSpan( } // close spans on error or normal completion - if (LettuceInstrumentationUtil.doFinishSpanEarly(command)) { - span.end(); - } else { + if (expectsResponse(command)) { asyncCommand.handleAsync(new LettuceAsyncBiFunction<>(span)); + } else { + LettuceClientDecorator.DECORATE.beforeFinish(span); + span.end(); } spanWithScope.closeScope(); + // span may be finished by LettuceAsyncBiFunction } } diff --git a/instrumentation/lettuce/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/v5_0/LettuceInstrumentationUtil.java b/instrumentation/lettuce/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/v5_0/LettuceInstrumentationUtil.java index 46f13f7fcb45..44a551a5c1c0 100644 --- a/instrumentation/lettuce/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/v5_0/LettuceInstrumentationUtil.java +++ b/instrumentation/lettuce/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/v5_0/LettuceInstrumentationUtil.java @@ -34,11 +34,11 @@ public class LettuceInstrumentationUtil { * we must close the span early in order to provide info for the users * * @param command - * @return true if finish the span early (the command will not have a return value) + * @return false if the span should finish early (the command will not have a return value) */ - public static boolean doFinishSpanEarly(final RedisCommand command) { + public static boolean expectsResponse(final RedisCommand command) { final String commandName = LettuceInstrumentationUtil.getCommandName(command); - return nonInstrumentingCommands.contains(commandName); + return !nonInstrumentingCommands.contains(commandName); } /** diff --git a/instrumentation/lettuce/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/v5_0/rx/LettuceFluxCreationAdvice.java b/instrumentation/lettuce/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/v5_0/rx/LettuceFluxCreationAdvice.java index 6cd60bf31f64..f4162f6e3234 100644 --- a/instrumentation/lettuce/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/v5_0/rx/LettuceFluxCreationAdvice.java +++ b/instrumentation/lettuce/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/v5_0/rx/LettuceFluxCreationAdvice.java @@ -15,7 +15,7 @@ */ package io.opentelemetry.auto.instrumentation.lettuce.v5_0.rx; -import static io.opentelemetry.auto.instrumentation.lettuce.v5_0.LettuceInstrumentationUtil.doFinishSpanEarly; +import static io.opentelemetry.auto.instrumentation.lettuce.v5_0.LettuceInstrumentationUtil.expectsResponse; import io.lettuce.core.protocol.RedisCommand; import java.util.function.Supplier; @@ -36,7 +36,7 @@ public static void monitorSpan( @Advice.Enter final RedisCommand command, @Advice.Return(readOnly = false) Flux publisher) { - final boolean finishSpanOnClose = doFinishSpanEarly(command); + final boolean finishSpanOnClose = !expectsResponse(command); final LettuceFluxTerminationRunnable handler = new LettuceFluxTerminationRunnable(command, finishSpanOnClose); publisher = publisher.doOnSubscribe(handler.getOnSubscribeConsumer()); diff --git a/instrumentation/lettuce/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/v5_0/rx/LettuceMonoCreationAdvice.java b/instrumentation/lettuce/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/v5_0/rx/LettuceMonoCreationAdvice.java index e76431266fe7..71df1f04b684 100644 --- a/instrumentation/lettuce/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/v5_0/rx/LettuceMonoCreationAdvice.java +++ b/instrumentation/lettuce/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/v5_0/rx/LettuceMonoCreationAdvice.java @@ -15,8 +15,9 @@ */ package io.opentelemetry.auto.instrumentation.lettuce.v5_0.rx; +import static io.opentelemetry.auto.instrumentation.lettuce.v5_0.LettuceInstrumentationUtil.expectsResponse; + import io.lettuce.core.protocol.RedisCommand; -import io.opentelemetry.auto.instrumentation.lettuce.v5_0.LettuceInstrumentationUtil; import java.util.function.Supplier; import net.bytebuddy.asm.Advice; import reactor.core.publisher.Mono; @@ -35,7 +36,7 @@ public static RedisCommand extractCommandName( public static void monitorSpan( @Advice.Enter final RedisCommand command, @Advice.Return(readOnly = false) Mono publisher) { - final boolean finishSpanOnClose = LettuceInstrumentationUtil.doFinishSpanEarly(command); + final boolean finishSpanOnClose = !expectsResponse(command); final LettuceMonoDualConsumer mdc = new LettuceMonoDualConsumer(command, finishSpanOnClose); publisher = publisher.doOnSubscribe(mdc); // register the call back to close the span only if necessary diff --git a/instrumentation/lettuce/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/v5_0/rx/LettuceMonoDualConsumer.java b/instrumentation/lettuce/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/v5_0/rx/LettuceMonoDualConsumer.java index 05b25b43b205..f51865d2323e 100644 --- a/instrumentation/lettuce/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/v5_0/rx/LettuceMonoDualConsumer.java +++ b/instrumentation/lettuce/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/v5_0/rx/LettuceMonoDualConsumer.java @@ -48,6 +48,7 @@ public void accept(final R r) { .startSpan(); DECORATE.afterStart(span); if (finishSpanOnClose) { + DECORATE.beforeFinish(span); span.end(); } } diff --git a/instrumentation/play/play-2.3/src/main/java8/io/opentelemetry/auto/instrumentation/play/v2_3/PlayAdvice.java b/instrumentation/play/play-2.3/src/main/java8/io/opentelemetry/auto/instrumentation/play/v2_3/PlayAdvice.java index 429c3e7be547..973cde94a5aa 100644 --- a/instrumentation/play/play-2.3/src/main/java8/io/opentelemetry/auto/instrumentation/play/v2_3/PlayAdvice.java +++ b/instrumentation/play/play-2.3/src/main/java8/io/opentelemetry/auto/instrumentation/play/v2_3/PlayAdvice.java @@ -74,6 +74,7 @@ public static void stopTraceOnResponse( playControllerSpan.end(); } playControllerScope.closeScope(); + // span finished in RequestCompleteCallback final Span rootSpan = TRACER.getCurrentSpan(); // set the span name on the upstream akka/netty span diff --git a/instrumentation/play/play-2.4/src/main/java8/io/opentelemetry/auto/instrumentation/play/v2_4/PlayAdvice.java b/instrumentation/play/play-2.4/src/main/java8/io/opentelemetry/auto/instrumentation/play/v2_4/PlayAdvice.java index a283af68e3b2..7d6276832061 100644 --- a/instrumentation/play/play-2.4/src/main/java8/io/opentelemetry/auto/instrumentation/play/v2_4/PlayAdvice.java +++ b/instrumentation/play/play-2.4/src/main/java8/io/opentelemetry/auto/instrumentation/play/v2_4/PlayAdvice.java @@ -72,6 +72,7 @@ public static void stopTraceOnResponse( playControllerSpan.end(); } playControllerScope.closeScope(); + // span finished in RequestCompleteCallback final Span rootSpan = TRACER.getCurrentSpan(); // set the span name on the upstream akka/netty span diff --git a/instrumentation/play/play-2.6/src/main/java8/io/opentelemetry/auto/instrumentation/play/v2_6/PlayAdvice.java b/instrumentation/play/play-2.6/src/main/java8/io/opentelemetry/auto/instrumentation/play/v2_6/PlayAdvice.java index 8cf14a372db3..15ad0a2cf40b 100644 --- a/instrumentation/play/play-2.6/src/main/java8/io/opentelemetry/auto/instrumentation/play/v2_6/PlayAdvice.java +++ b/instrumentation/play/play-2.6/src/main/java8/io/opentelemetry/auto/instrumentation/play/v2_6/PlayAdvice.java @@ -72,6 +72,7 @@ public static void stopTraceOnResponse( playControllerSpan.end(); } playControllerScope.closeScope(); + // span finished in RequestCompleteCallback final Span rootSpan = TRACER.getCurrentSpan(); // set the span name on the upstream akka/netty span diff --git a/instrumentation/rediscala-1.8/src/main/java/io/opentelemetry/auto/instrumentation/rediscala/RediscalaInstrumentation.java b/instrumentation/rediscala-1.8/src/main/java/io/opentelemetry/auto/instrumentation/rediscala/RediscalaInstrumentation.java index 70f97af19d26..fbcd8e80fa9a 100644 --- a/instrumentation/rediscala-1.8/src/main/java/io/opentelemetry/auto/instrumentation/rediscala/RediscalaInstrumentation.java +++ b/instrumentation/rediscala-1.8/src/main/java/io/opentelemetry/auto/instrumentation/rediscala/RediscalaInstrumentation.java @@ -107,6 +107,7 @@ public static void stopSpan( span.end(); } scope.closeScope(); + // span finished in OnCompleteHandler } } diff --git a/instrumentation/servlet/README.md b/instrumentation/servlet/README.md new file mode 100644 index 000000000000..9c8f5170442d --- /dev/null +++ b/instrumentation/servlet/README.md @@ -0,0 +1,74 @@ +# Instrumentation for Java Servlets + +In order to fully understand how java servlet instrumentation work, +let us first take a look at the following stacktrace from Spring PetClinic application. +Unimportant frames are redacted, points of interests are highlighted and discussed below. + +
+at org.springframework.samples.petclinic.owner.OwnerController.initCreationForm(OwnerController.java:60)
+...
+at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
+at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040)
+at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943)
+at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
+at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)
+at javax.servlet.http.HttpServlet.service(HttpServlet.java:634)
+at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
+at javax.servlet.http.HttpServlet.service(HttpServlet.java:741)
+...
+at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
+...
+at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
+at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
+at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
+at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
+at java.base/java.lang.Thread.run(Thread.java:834)
+
+ +Everything starts when HTTP request processing reaches the first class from Servlet specification. +In the example above this is `ApplicationFilterChain.doFilter(ServletRequest, ServletResponse)` method. +Let us call this first servlet specific method an "entry point". +This is the main target for `Servlet3Instrumentation` and `Servlet2Instrumentation` instrumenters: + +`public void javax.servlet.FilterChain#doFilter(ServletRequest, ServletResponse)` + +`public void javax.servlet.http.HttpServlet#service(ServletRequest, ServletResponse)`. + +For example, Jetty Servlet container does not have default filter chain and in many cases will have +the second method as instrumentation entry point. +These instrumentations are located in two separate submodules `request-3.0` and `request-2.3`, respectively, +because they and corresponding tests depend on different versions of servlet specification. + +Next, request passes several other methods from Servlet specification, such as + +`protected void javax.servlet.http.HttpServlet#service(HttpServletRequest, HttpServletResponse)` or + +`protected void org.springframework.web.servlet.FrameworkServlet#doGet(HttpServletRequest, HttpServletResponse)`. + +They are the targets for `HttpServletInstrumentation`. +From the observability point of view nothing of interest usually happens inside these methods. +Thus it usually does not make sense to create spans from them, as they would only add useless noise. +For this reason `HttpServletInstrumentation` is disabled by default. +In rare cases when you need it, you can enable it using configuration property `ota.integration.servlet-service.enabled`. + +In exactly the same situation are all other Servlet filters beyond the initial entry point. +Usually unimportant, they may be sometimes of interest during troubleshooting. +They are instrumented by `FilterInstrumentation` which is too disabled by default. +You can enable it with the configuration property `ota.integration.servlet-filter.enabled`. +At last, request processing may reach the specific framework that you application uses. +In this case Spring MVC and `OwnerController.initCreationForm`. + +If all instrumentations are enabled, then a new span will be created for every highlighted frame. +All spans from Servlet API will have `kind=SERVER` and name based on corresponding class ana method names, +such as `ApplicationFilterChain.doFilter` or `FrameworkServlet.doGet`. +Span created by Spring MVC instrumentation will have `kind=INTERNAL` and named `OwnerController.initCreationForm`. + +The state described above has one significant problem. +Observability backends usually aggregate traces based on their root spans. +This means that ALL traces from any application deployed to Servlet container will be grouped together. +Becaue their root spans will all have the same named based on common entry point. +In order to alleviate this problem, instrumentations for specific frameworks, such as Spring MVC here, +_update_ name of the span corresponding to the entry point. +Each framework instrumentation can decide what is the best span name based on framework implementation details. +Of course, still adhering to OpenTelemetry +[semantic conventions](https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/http.md). \ No newline at end of file diff --git a/instrumentation/servlet/glassfish-testing/src/test/groovy/GlassFishServerTest.groovy b/instrumentation/servlet/glassfish-testing/src/test/groovy/GlassFishServerTest.groovy index 7275beeaea1c..96805043f83e 100644 --- a/instrumentation/servlet/glassfish-testing/src/test/groovy/GlassFishServerTest.groovy +++ b/instrumentation/servlet/glassfish-testing/src/test/groovy/GlassFishServerTest.groovy @@ -36,10 +36,6 @@ import static io.opentelemetry.trace.Span.Kind.SERVER // TODO: Figure out a better way to test with OSGi included. class GlassFishServerTest extends HttpServerTest { -// static { -// System.setProperty("ota.integration.grizzly.enabled", "true") -// } - @Override URI buildAddress() { return new URI("http://localhost:$port/$context/") @@ -88,7 +84,7 @@ class GlassFishServerTest extends HttpServerTest { @Override void serverSpan(TraceAssert trace, int index, String traceID = null, String parentID = null, String method = "GET", ServerEndpoint endpoint = SUCCESS) { trace.span(index) { - operationName expectedOperationName(method) + operationName entryPointName() spanKind SERVER errored endpoint.errored if (parentID != null) { @@ -117,5 +113,12 @@ class GlassFishServerTest extends HttpServerTest { } } } + + //Simple class name plus method name of the entry point of the given servlet container. + //"Entry point" here means the first filter or servlet that accepts incoming requests. + //This will serve as a default name of the SERVER span created for this request. + protected String entryPointName() { + 'HttpServlet.service' + } } diff --git a/instrumentation/servlet/request-2.3/src/main/java/io/opentelemetry/auto/instrumentation/servlet/v2_3/Servlet2Advice.java b/instrumentation/servlet/request-2.3/src/main/java/io/opentelemetry/auto/instrumentation/servlet/v2_3/Servlet2Advice.java index 7f0fba512b54..d06683c8c60a 100644 --- a/instrumentation/servlet/request-2.3/src/main/java/io/opentelemetry/auto/instrumentation/servlet/v2_3/Servlet2Advice.java +++ b/instrumentation/servlet/request-2.3/src/main/java/io/opentelemetry/auto/instrumentation/servlet/v2_3/Servlet2Advice.java @@ -29,31 +29,35 @@ import io.opentelemetry.auto.instrumentation.api.Tags; import io.opentelemetry.trace.Span; import io.opentelemetry.trace.Status; +import java.lang.reflect.Method; import java.security.Principal; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import net.bytebuddy.asm.Advice; -import net.bytebuddy.implementation.bytecode.assign.Assigner; public class Servlet2Advice { @Advice.OnMethodEnter(suppress = Throwable.class) public static SpanWithScope onEnter( @Advice.This final Object servlet, + @Advice.Origin final Method method, @Advice.Argument(0) final ServletRequest request, - @Advice.Argument(value = 1, typing = Assigner.Typing.DYNAMIC) - final ServletResponse response) { + @Advice.Argument(1) final ServletResponse response) { + if (!(request instanceof HttpServletRequest)) { + return null; + } + final boolean hasServletTrace = request.getAttribute(SPAN_ATTRIBUTE) instanceof Span; - final boolean invalidRequest = !(request instanceof HttpServletRequest); - if (invalidRequest || hasServletTrace) { + if (hasServletTrace) { // Tracing might already be applied by the FilterChain or a parent request (forward/include). return null; } final HttpServletRequest httpServletRequest = (HttpServletRequest) request; + // TODO this logic should be moved to Servlet2 specific Decorator if (response instanceof HttpServletResponse) { // For use by HttpServletResponseInstrumentation: InstrumentationContext.get(HttpServletResponse.class, HttpServletRequest.class) @@ -64,9 +68,8 @@ public static SpanWithScope onEnter( } final Span.Builder builder = - TRACER.spanBuilder(DECORATE.spanNameForRequest(httpServletRequest)).setSpanKind(SERVER); + TRACER.spanBuilder(DECORATE.spanNameForMethod(method)).setSpanKind(SERVER); builder.setParent(extract(httpServletRequest, GETTER)); - final Span span = builder.setAttribute("span.origin.type", servlet.getClass().getName()).startSpan(); @@ -99,6 +102,7 @@ public static void stopSpan( if (spanWithScope == null) { return; } + final Span span = spanWithScope.getSpan(); if (response instanceof HttpServletResponse) { diff --git a/instrumentation/servlet/request-2.3/src/main/java/io/opentelemetry/auto/instrumentation/servlet/v2_3/Servlet2ResponseStatusInstrumentation.java b/instrumentation/servlet/request-2.3/src/main/java/io/opentelemetry/auto/instrumentation/servlet/v2_3/Servlet2ResponseStatusInstrumentation.java index b326e882af65..714e565af91f 100644 --- a/instrumentation/servlet/request-2.3/src/main/java/io/opentelemetry/auto/instrumentation/servlet/v2_3/Servlet2ResponseStatusInstrumentation.java +++ b/instrumentation/servlet/request-2.3/src/main/java/io/opentelemetry/auto/instrumentation/servlet/v2_3/Servlet2ResponseStatusInstrumentation.java @@ -22,13 +22,30 @@ import static net.bytebuddy.matcher.ElementMatchers.not; import com.google.auto.service.AutoService; +import io.opentelemetry.auto.bootstrap.InstrumentationContext; +import io.opentelemetry.auto.instrumentation.api.SpanWithScope; import io.opentelemetry.auto.tooling.Instrumenter; import java.util.HashMap; import java.util.Map; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletResponse; +import net.bytebuddy.asm.Advice; import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.matcher.ElementMatcher; +/** + * Class javax.servlet.http.HttpServletResponse got method getStatus only + * in Servlet specification version 3.0. This means that we cannot set {@link + * io.opentelemetry.auto.instrumentation.api.Tags#HTTP_STATUS} attribute on the created span using + * just response object. + * + *

This instrumentation intercepts status setting methods from Servlet 2.0 specification and + * stores that status into context store. Then {@link Servlet2Advice#stopSpan(ServletRequest, + * ServletResponse, SpanWithScope, Throwable)} can get it from context and set required span + * attribute. + */ @AutoService(Instrumenter.class) public final class Servlet2ResponseStatusInstrumentation extends Instrumenter.Default { public Servlet2ResponseStatusInstrumentation() { @@ -51,16 +68,27 @@ public Map contextStore() { return singletonMap("javax.servlet.ServletResponse", Integer.class.getName()); } - /** - * Unlike Servlet2Instrumentation it doesn't matter if the HttpServletResponseInstrumentation - * applies first - */ @Override public Map, String> transformers() { final Map, String> transformers = new HashMap<>(); transformers.put( - named("sendError").or(named("setStatus")), packageName + ".Servlet2ResponseStatusAdvice"); - transformers.put(named("sendRedirect"), packageName + ".Servlet2ResponseRedirectAdvice"); + named("sendError").or(named("setStatus")), Servlet2ResponseStatusAdvice.class.getName()); + transformers.put(named("sendRedirect"), Servlet2ResponseRedirectAdvice.class.getName()); return transformers; } + + public static class Servlet2ResponseRedirectAdvice { + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void onEnter(@Advice.This final HttpServletResponse response) { + InstrumentationContext.get(ServletResponse.class, Integer.class).put(response, 302); + } + } + + public static class Servlet2ResponseStatusAdvice { + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void onEnter( + @Advice.This final HttpServletResponse response, @Advice.Argument(0) final Integer status) { + InstrumentationContext.get(ServletResponse.class, Integer.class).put(response, status); + } + } } diff --git a/instrumentation/servlet/request-2.3/src/test/groovy/JettyServlet2Test.groovy b/instrumentation/servlet/request-2.3/src/test/groovy/JettyServlet2Test.groovy index 3033a8903fde..31224ad321fd 100644 --- a/instrumentation/servlet/request-2.3/src/test/groovy/JettyServlet2Test.groovy +++ b/instrumentation/servlet/request-2.3/src/test/groovy/JettyServlet2Test.groovy @@ -85,7 +85,7 @@ class JettyServlet2Test extends HttpServerTest { // parent span must be cast otherwise it breaks debugging classloading (junit loads it early) void serverSpan(TraceAssert trace, int index, String traceID = null, String parentID = null, String method = "GET", ServerEndpoint endpoint = SUCCESS) { trace.span(index) { - operationName expectedOperationName(method) + operationName 'HttpServlet.service' spanKind SERVER errored endpoint.errored if (parentID != null) { diff --git a/instrumentation/servlet/request-3.0/src/main/java/io/opentelemetry/auto/instrumentation/servlet/v3_0/Servlet3Advice.java b/instrumentation/servlet/request-3.0/src/main/java/io/opentelemetry/auto/instrumentation/servlet/v3_0/Servlet3Advice.java index 8f1cca4f87fb..5842a6402ad3 100644 --- a/instrumentation/servlet/request-3.0/src/main/java/io/opentelemetry/auto/instrumentation/servlet/v3_0/Servlet3Advice.java +++ b/instrumentation/servlet/request-3.0/src/main/java/io/opentelemetry/auto/instrumentation/servlet/v3_0/Servlet3Advice.java @@ -29,6 +29,7 @@ import io.opentelemetry.auto.instrumentation.api.Tags; import io.opentelemetry.trace.Span; import io.opentelemetry.trace.Status; +import java.lang.reflect.Method; import java.security.Principal; import java.util.concurrent.atomic.AtomicBoolean; import javax.servlet.ServletRequest; @@ -42,6 +43,7 @@ public class Servlet3Advice { @Advice.OnMethodEnter(suppress = Throwable.class) public static SpanWithScope onEnter( @Advice.This final Object servlet, + @Advice.Origin final Method method, @Advice.Argument(0) final ServletRequest request, @Advice.Argument(1) final ServletResponse response) { if (!(request instanceof HttpServletRequest)) { @@ -75,7 +77,7 @@ public static SpanWithScope onEnter( .put((HttpServletResponse) response, httpServletRequest); final Span.Builder builder = - TRACER.spanBuilder(DECORATE.spanNameForRequest(httpServletRequest)).setSpanKind(SERVER); + TRACER.spanBuilder(DECORATE.spanNameForMethod(method)).setSpanKind(SERVER); builder.setParent(extract(httpServletRequest, GETTER)); final Span span = builder.setAttribute("span.origin.type", servlet.getClass().getName()).startSpan(); @@ -148,6 +150,7 @@ public static void stopSpan( DECORATE.beforeFinish(span); span.end(); // Finish the span manually since finishSpanOnClose was false } + // else span finished in TagSettingAsyncListener } spanWithScope.closeScope(); } diff --git a/instrumentation/servlet/request-3.0/src/test/groovy/AbstractServlet3Test.groovy b/instrumentation/servlet/request-3.0/src/test/groovy/AbstractServlet3Test.groovy index 94806895cd1d..b0cd736993ea 100644 --- a/instrumentation/servlet/request-3.0/src/test/groovy/AbstractServlet3Test.groovy +++ b/instrumentation/servlet/request-3.0/src/test/groovy/AbstractServlet3Test.groovy @@ -71,7 +71,7 @@ abstract class AbstractServlet3Test extends HttpServerTest extends HttpServerTest return "tomcat-context" } + @Override + protected String entryPointName() { + return 'ApplicationFilterChain.doFilter' + } + @Override void addServlet(Context servletContext, String path, Class servlet) { String name = UUID.randomUUID() diff --git a/instrumentation/servlet/src/main/java/io/opentelemetry/auto/instrumentation/servlet/filter/FilterInstrumentation.java b/instrumentation/servlet/src/main/java/io/opentelemetry/auto/instrumentation/servlet/filter/FilterInstrumentation.java index 3d54f7c75e7c..66a3db9503dc 100644 --- a/instrumentation/servlet/src/main/java/io/opentelemetry/auto/instrumentation/servlet/filter/FilterInstrumentation.java +++ b/instrumentation/servlet/src/main/java/io/opentelemetry/auto/instrumentation/servlet/filter/FilterInstrumentation.java @@ -36,6 +36,11 @@ import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.matcher.ElementMatcher; +/** + * Instruments all filter invocations in filter chain. + * + *

See README.md for more information about different servlet instrumentations. + */ @AutoService(Instrumenter.class) public final class FilterInstrumentation extends Instrumenter.Default { public FilterInstrumentation() { diff --git a/instrumentation/servlet/src/main/java/io/opentelemetry/auto/instrumentation/servlet/http/HttpServletInstrumentation.java b/instrumentation/servlet/src/main/java/io/opentelemetry/auto/instrumentation/servlet/http/HttpServletInstrumentation.java index a2d4c9cf153a..1afddbb3bc42 100644 --- a/instrumentation/servlet/src/main/java/io/opentelemetry/auto/instrumentation/servlet/http/HttpServletInstrumentation.java +++ b/instrumentation/servlet/src/main/java/io/opentelemetry/auto/instrumentation/servlet/http/HttpServletInstrumentation.java @@ -38,6 +38,7 @@ import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.matcher.ElementMatcher; +// Please read README.md of this subproject to understand what is this instrumentation. @AutoService(Instrumenter.class) public final class HttpServletInstrumentation extends Instrumenter.Default { public HttpServletInstrumentation() { diff --git a/instrumentation/slf4j-mdc/src/main/java/io/opentelemetry/auto/instrumentation/slf4j/mdc/MDCInjectionInstrumentation.java b/instrumentation/slf4j-mdc/src/main/java/io/opentelemetry/auto/instrumentation/slf4j/mdc/MDCInjectionInstrumentation.java index 60b9c3927151..49cac0c6e231 100644 --- a/instrumentation/slf4j-mdc/src/main/java/io/opentelemetry/auto/instrumentation/slf4j/mdc/MDCInjectionInstrumentation.java +++ b/instrumentation/slf4j-mdc/src/main/java/io/opentelemetry/auto/instrumentation/slf4j/mdc/MDCInjectionInstrumentation.java @@ -15,6 +15,7 @@ import net.bytebuddy.asm.Advice; import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.BooleanMatcher; import net.bytebuddy.matcher.ElementMatcher; import net.bytebuddy.utility.JavaModule; @@ -26,6 +27,8 @@ public class MDCInjectionInstrumentation extends Instrumenter.Default { // mdcClassName = org.slf4j.MDC private static final String mdcClassName = "org.TMP.MDC".replaceFirst("TMP", "slf4j"); + private boolean initialized = false; + public MDCInjectionInstrumentation() { super(MDC_INSTRUMENTATION_NAME); } @@ -47,15 +50,22 @@ public void postMatch( final JavaModule module, final Class classBeingRedefined, final ProtectionDomain protectionDomain) { - if (classBeingRedefined != null) { + if (classBeingRedefined != null && !initialized) { MDCAdvice.mdcClassInitialized(classBeingRedefined); } + initialized = true; } @Override public Map, String> transformers() { return singletonMap( - isTypeInitializer(), MDCInjectionInstrumentation.class.getName() + "$MDCAdvice"); + new BooleanMatcher(false) { + @Override + public boolean matches(final MethodDescription target) { + return initialized; + } + }.and(isTypeInitializer()), + MDCInjectionInstrumentation.class.getName() + "$MDCAdvice"); } @Override diff --git a/instrumentation/spring-webflux-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/springwebflux/server/DispatcherHandlerAdvice.java b/instrumentation/spring-webflux-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/springwebflux/server/DispatcherHandlerAdvice.java index 7c229d16bb79..0f5bf480baa6 100644 --- a/instrumentation/spring-webflux-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/springwebflux/server/DispatcherHandlerAdvice.java +++ b/instrumentation/spring-webflux-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/springwebflux/server/DispatcherHandlerAdvice.java @@ -60,5 +60,6 @@ public static void methodExit( AdviceUtils.finishSpanIfPresent(exchange, throwable); } spanWithScope.closeScope(); + // span finished in SpanFinishingSubscriber } } diff --git a/instrumentation/spring-webflux-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/springwebflux/server/HandlerAdapterAdvice.java b/instrumentation/spring-webflux-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/springwebflux/server/HandlerAdapterAdvice.java index 08533bf11db8..68d7b202b147 100644 --- a/instrumentation/spring-webflux-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/springwebflux/server/HandlerAdapterAdvice.java +++ b/instrumentation/spring-webflux-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/springwebflux/server/HandlerAdapterAdvice.java @@ -76,6 +76,7 @@ public static void methodExit( } if (spanWithScope != null) { spanWithScope.closeScope(); + // span finished in SpanFinishingSubscriber } } } diff --git a/instrumentation/trace-annotation/src/test/groovy/WithSpanInstrumentationTest.groovy b/instrumentation/trace-annotation/src/test/groovy/WithSpanInstrumentationTest.groovy index 052cd3b50ce9..c1e7bd10d4ce 100644 --- a/instrumentation/trace-annotation/src/test/groovy/WithSpanInstrumentationTest.groovy +++ b/instrumentation/trace-annotation/src/test/groovy/WithSpanInstrumentationTest.groovy @@ -18,7 +18,7 @@ import io.opentelemetry.auto.test.utils.ConfigUtils import io.opentelemetry.test.annotation.TracedWithSpan /** - * This test verifies that auto-instr supports {@link io.opentelemetry.contrib.auto.annotations.WithSpan} contrib annotation. + * This test verifies that auto instrumentation supports {@link io.opentelemetry.contrib.auto.annotations.WithSpan} contrib annotation. */ class WithSpanInstrumentationTest extends AgentTestRunner { diff --git a/instrumentation/twilio-6.6/src/main/java/io/opentelemetry/auto/instrumentation/twilio/TwilioAsyncInstrumentation.java b/instrumentation/twilio-6.6/src/main/java/io/opentelemetry/auto/instrumentation/twilio/TwilioAsyncInstrumentation.java index accc7502cdf1..10975ecfdd4c 100644 --- a/instrumentation/twilio-6.6/src/main/java/io/opentelemetry/auto/instrumentation/twilio/TwilioAsyncInstrumentation.java +++ b/instrumentation/twilio-6.6/src/main/java/io/opentelemetry/auto/instrumentation/twilio/TwilioAsyncInstrumentation.java @@ -160,6 +160,7 @@ public static void methodExit( } } finally { spanWithScope.closeScope(); + // span finished in SpanFinishingCallback } } } diff --git a/settings.gradle b/settings.gradle index 436d84641925..a7eb8c0f194a 100644 --- a/settings.gradle +++ b/settings.gradle @@ -65,6 +65,8 @@ include ':instrumentation:finatra-2.9' include ':instrumentation:geode-1.4' include ':instrumentation:google-http-client-1.19' include ':instrumentation:grizzly-2.0' +include ':instrumentation:grizzly-client-1.9' +include ':instrumentation:grizzly-http-2.3' include ':instrumentation:grpc-1.5' include ':instrumentation:hibernate:hibernate-3.3' include ':instrumentation:hibernate:hibernate-4.0' @@ -144,6 +146,8 @@ include ':instrumentation:trace-annotation' include ':instrumentation:twilio-6.6' include ':instrumentation:vertx-testing' +include ':instrumentation-core:aws-sdk:aws-sdk-2.2-core' + // exporter support include ':exporter-support' @@ -152,6 +156,7 @@ include ":auto-exporters" include ":auto-exporters:jaeger" include ":auto-exporters:logging" include ":auto-exporters:otlp" +include ":auto-exporters:zipkin" // benchmark include ':benchmark' @@ -174,3 +179,4 @@ project(':java-agent').name = 'opentelemetry-auto' project(':auto-exporters:jaeger').name = 'opentelemetry-auto-exporters-jaeger' project(':auto-exporters:logging').name = 'opentelemetry-auto-exporters-logging' project(':auto-exporters:otlp').name = 'opentelemetry-auto-exporters-otlp' +project(':auto-exporters:zipkin').name = 'opentelemetry-auto-exporters-zipkin' diff --git a/smoke-tests/wildfly/src/test/groovy/io/opentelemetry/smoketest/WildflySmokeTest.groovy b/smoke-tests/wildfly/src/test/groovy/io/opentelemetry/smoketest/WildflySmokeTest.groovy index a37a20e77d45..9e08aa121360 100644 --- a/smoke-tests/wildfly/src/test/groovy/io/opentelemetry/smoketest/WildflySmokeTest.groovy +++ b/smoke-tests/wildfly/src/test/groovy/io/opentelemetry/smoketest/WildflySmokeTest.groovy @@ -20,7 +20,7 @@ import okhttp3.Request import spock.lang.Retry import spock.lang.Shared -@Retry(delay = 20) +@Retry(delay = 1000) class WildflySmokeTest extends AbstractServerSmokeTest { @Shared diff --git a/testing/src/main/groovy/io/opentelemetry/auto/test/AgentTestRunner.java b/testing/src/main/groovy/io/opentelemetry/auto/test/AgentTestRunner.java index 166aec68ebda..ecf1829910e4 100644 --- a/testing/src/main/groovy/io/opentelemetry/auto/test/AgentTestRunner.java +++ b/testing/src/main/groovy/io/opentelemetry/auto/test/AgentTestRunner.java @@ -19,6 +19,7 @@ import ch.qos.logback.classic.Logger; import com.google.common.base.Predicate; import com.google.common.base.Predicates; +import com.google.common.base.Throwables; import com.google.common.collect.Sets; import groovy.lang.Closure; import groovy.lang.DelegatesTo; @@ -35,6 +36,7 @@ import io.opentelemetry.trace.Tracer; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.Instrumentation; +import java.net.BindException; import java.util.ArrayList; import java.util.List; import java.util.ServiceLoader; @@ -177,6 +179,30 @@ public void cleanUpAfterTests() { TEST_LISTENER.deactivateTest(this); } + /** + * This is used by setupSpec() methods to auto-retry setup that depends on finding and then using + * an available free port, because that kind of setup can fail sporadically with + * "java.net.BindException: Address already in use" if the available port gets re-used between + * when we find the available port and when we use it. + * + * @param closure the groovy closure to run with retry + */ + public static void withRetryOnBindException(final Closure closure) { + withRetryOnBindException(closure, 3); + } + + private static void withRetryOnBindException(final Closure closure, final int numRetries) { + try { + closure.call(); + } catch (final Throwable t) { + if (numRetries == 0 || !(Throwables.getRootCause(t) instanceof BindException)) { + throw t; + } + log.debug("retrying due to bind exception: {}", t.getMessage(), t); + withRetryOnBindException(closure, numRetries - 1); + } + } + @AfterClass public static synchronized void agentCleanup() { if (null != activeTransformer) { diff --git a/testing/src/main/groovy/io/opentelemetry/auto/test/InstrumentationTestRunner.groovy b/testing/src/main/groovy/io/opentelemetry/auto/test/InstrumentationTestRunner.groovy new file mode 100644 index 000000000000..7771eb629f05 --- /dev/null +++ b/testing/src/main/groovy/io/opentelemetry/auto/test/InstrumentationTestRunner.groovy @@ -0,0 +1,66 @@ +/* + * Copyright The OpenTelemetry Authors + * + * 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. + */ +package io.opentelemetry.auto.test + +import com.google.common.base.Predicate +import com.google.common.base.Predicates +import groovy.transform.stc.ClosureParams +import groovy.transform.stc.SimpleType +import io.opentelemetry.auto.test.asserts.InMemoryExporterAssert +import io.opentelemetry.sdk.OpenTelemetrySdk +import io.opentelemetry.sdk.trace.data.SpanData +import org.junit.Before +import spock.lang.Specification +/** + * A spock test runner which automatically initializes an in-memory exporter that can be used to + * verify traces. + */ +abstract class InstrumentationTestRunner extends Specification { + + protected static final InMemoryExporter TEST_WRITER + + static { + TEST_WRITER = new InMemoryExporter() + OpenTelemetrySdk.getTracerProvider().addSpanProcessor(TEST_WRITER) + } + + @Before + void beforeTest() { + TEST_WRITER.clear() + } + + protected void assertTraces( + final int size, + @ClosureParams( + value = SimpleType, + options = "io.opentelemetry.auto.test.asserts.ListWriterAssert") + @DelegatesTo(value = InMemoryExporterAssert, strategy = Closure.DELEGATE_FIRST) + final Closure spec) { + InMemoryExporterAssert.assertTraces( + TEST_WRITER, size, Predicates.>alwaysFalse(), spec) + } + + protected void assertTracesWithFilter( + final int size, + final Predicate> excludes, + @ClosureParams( + value = SimpleType, + options = "io.opentelemetry.auto.test.asserts.ListWriterAssert") + @DelegatesTo(value = InMemoryExporterAssert, strategy = Closure.DELEGATE_FIRST) + final Closure spec) { + InMemoryExporterAssert.assertTraces(TEST_WRITER, size, excludes, spec) + } +} diff --git a/testing/src/main/groovy/io/opentelemetry/auto/test/base/HttpServerTest.groovy b/testing/src/main/groovy/io/opentelemetry/auto/test/base/HttpServerTest.groovy index 1826c210476c..7fa6920ce865 100644 --- a/testing/src/main/groovy/io/opentelemetry/auto/test/base/HttpServerTest.groovy +++ b/testing/src/main/groovy/io/opentelemetry/auto/test/base/HttpServerTest.groovy @@ -61,19 +61,27 @@ abstract class HttpServerTest extends AgentTestRunner { @Shared OkHttpClient client = OkHttpUtils.client() @Shared - int port = PortUtils.randomOpenPort() + int port @Shared - URI address = buildAddress() + URI address - URI buildAddress() { - return new URI("http://localhost:$port/") + def setupSpec() { + withRetryOnBindException({ + setupSpecUnderRetry() + }) } - def setupSpec() { + def setupSpecUnderRetry() { + port = PortUtils.randomOpenPort() + address = buildAddress() server = startServer(port) println getClass().name + " http server started at: http://localhost:$port/" } + URI buildAddress() { + return new URI("http://localhost:$port/") + } + abstract SERVER startServer(int port) def cleanupSpec() { @@ -124,6 +132,10 @@ abstract class HttpServerTest extends AgentTestRunner { true } + boolean testException() { + true + } + enum ServerEndpoint { SUCCESS("success", 200, "success"), REDIRECT("redirect", 302, "/redirected"), @@ -304,6 +316,7 @@ abstract class HttpServerTest extends AgentTestRunner { def "test exception"() { setup: + assumeTrue(testException()) def request = request(EXCEPTION, method, body).build() def response = client.newCall(request).execute() diff --git a/testing/testing.gradle b/testing/testing.gradle index c7a64e67a9e3..9bdff05800ae 100644 --- a/testing/testing.gradle +++ b/testing/testing.gradle @@ -7,6 +7,7 @@ excludedClassesCoverage += [ 'io.opentelemetry.auto.test.base.*', 'io.opentelemetry.auto.test.log.*', 'io.opentelemetry.auto.test.AgentTestRunner', + 'io.opentelemetry.auto.test.InstrumentationTestRunner', 'io.opentelemetry.auto.test.InMemoryExporter.*', 'io.opentelemetry.auto.test.utils.*', // Avoid applying jacoco instrumentation to classes instrumented by tested agent @@ -23,10 +24,11 @@ dependencies { compile deps.testLogging compile deps.guava - compile group: 'com.squareup.okhttp3', name: 'okhttp', version: '3.11.0' // Last version to support Java7 + // okhttp 3.12.x is the last version to support Java7 + compile group: 'com.squareup.okhttp3', name: 'okhttp', version: '3.12.12' + compile group: 'com.squareup.okhttp3', name: 'logging-interceptor', version: '3.12.12' compile group: 'org.eclipse.jetty', name: 'jetty-server', version: '8.0.0.v20110901' - compile group: 'com.squareup.okhttp3', name: 'logging-interceptor', version: versions.okhttp compile(project(':auto-tooling')) { // including :opentelemetry-sdk-shaded-for-testing above instead diff --git a/trace-java.gradle b/trace-java.gradle index 2a7e90a928ee..f41cea676923 100644 --- a/trace-java.gradle +++ b/trace-java.gradle @@ -24,7 +24,7 @@ def isCI = System.getenv("CI") != null allprojects { group = 'io.opentelemetry.auto' - version = '0.2.3-SNAPSHOT' + version = '0.4.0-SNAPSHOT' if (isCI) { buildDir = "${rootDir}/workspace/${projectDir.path.replace(rootDir.path, '')}/build/"