Docs for writing new instrumentation... (#652)
* Docs for writing new instrumentation
This commit is contained in:
parent
f3421e91a0
commit
d8355c672d
120
CONTRIBUTING.md
120
CONTRIBUTING.md
|
@ -74,6 +74,126 @@ following "clusters" of classes:
|
|||
- `io/opentelemetry/auto/shaded/` - contains OpenTelemetry API and its dependencies.
|
||||
Shaded during creation of `javaagent` jar file by Shadow Gradle plugin.
|
||||
|
||||
### Writing instrumentation
|
||||
|
||||
**Warning**: The repository is still in the process of migrating to the structure described here.
|
||||
|
||||
Any time we want to add OpenTelemetry support for a new Java library, e.g., so usage
|
||||
of that library has tracing, we must write new instrumentation for that library. Let's
|
||||
go over some terms first.
|
||||
|
||||
**Manual Instrumentation**: This is logic that creates spans and enriches them with data
|
||||
using library-specific monitoring APIs. For example, when instrumenting an RPC library,
|
||||
the instrumentation will use some library-specific functionality to listen to events such
|
||||
as the start and end of a request and will execute code to start and end spans in these
|
||||
listeners. Many of these libraries will provide interception type APIs such as the gRPC
|
||||
`ClientInterceptor` or servlet's `Filter`. Others will provide a Java interface whose methods
|
||||
correspond to a request, and instrumentation can define an implementation which delegates
|
||||
to the standard, wrapping methods with the logic to manage spans. Users will add code to their
|
||||
apps that initialize the classes provided by manual instrumentation libraries and the libraries
|
||||
can be found inside the user's app itself.
|
||||
|
||||
Some libraries will have no way of intercepting requests because they only expose static APIs
|
||||
and no interception hooks. For these libraries it is not possible to create manual
|
||||
instrumentation.
|
||||
|
||||
**Auto Instrumentation**: This is logic that is similar to manual instrumentation, but instead
|
||||
of a user initializing classes themselves, a Java agent automatically initializes them during
|
||||
class loading by manipulating byte code. This allows a user to develop their apps without thinking
|
||||
about instrumentation and get it "for free". Often, the auto instrumentation will generate bytecode
|
||||
that is more or less identical to what a user would have written themselves in their app.
|
||||
|
||||
In addition to automatically initializing manual instrumentation, auto instrumentation can be used
|
||||
for libraries where manual instrumentation is not possible, such as `URLConnection`, because it can
|
||||
intercept even the JDK's classes. Such libraries will not have manual instrumentation but will have
|
||||
auto instrumentation.
|
||||
|
||||
#### Folder Structure
|
||||
|
||||
Please also refer to some of our existing instrumentation for examples of our structure, for example,
|
||||
[aws-sdk-2.2](./instrumentation/aws-sdk/aws-sdk-2.2).
|
||||
|
||||
When writing new instrumentation, create a new subfolder of `instrumentation` to correspond to the
|
||||
instrumented library and the oldest version being targeted. Ideally an old version of the library is
|
||||
targeted in a way that the instrumentation applies to a large range of versions, but this may be
|
||||
restricted by the interception APIs provided by the library.
|
||||
|
||||
Within the subfolder, create three folders `library` (skip if manual instrumentation is not possible),
|
||||
`auto`, and `testing`.
|
||||
|
||||
For example, if we are targeting an RPC framework `yarpc` at version `1.0` we would have a tree like
|
||||
|
||||
```
|
||||
instrumentation ->
|
||||
...
|
||||
yarpc-1.0 ->
|
||||
auto
|
||||
yarpc-1.0-auto.gradle
|
||||
library
|
||||
yarpc-1.0-library.gradle
|
||||
testing
|
||||
yarpc-1.0-testing.gradle
|
||||
```
|
||||
|
||||
and in the top level `settings.gradle`
|
||||
|
||||
```groovy
|
||||
|
||||
include 'instrumentation:yarpc-1.0:agent'
|
||||
include 'instrumentation:yarpc-1.0:library'
|
||||
include 'instrumentation:yarpc-1.0:testing'
|
||||
```
|
||||
|
||||
#### Writing manual instrumentation
|
||||
|
||||
Begin by writing the instrumentation for the library in `library`. This generally involves defining a
|
||||
`Tracer` and using the typed tracers in our `instrumentation-common` library to create and annotate
|
||||
spans as part of the implementation of an interceptor for the library. The module should generally
|
||||
only depend on the OpenTelemetry API, `instrumentation-common`, and the instrumented library itself.
|
||||
[instrumentation-library.gradle](./gradle/instrumentation-library.gradle) needs to be applied to
|
||||
configure build tooling for the library, e.g., to prevent conflict between manual instrumentation
|
||||
loaded by the user and by the agent, we make sure to create a shaded version with no dependencies
|
||||
for use from the auto instrumentation at a separate package. To configure this, you must define
|
||||
`ext.javaSubPackage` with the name of the sub package under `io.opentelemetry.auto` that the code
|
||||
lives in. In this example, we would use `yarpc.v1_0`.
|
||||
|
||||
#### Writing unit tests
|
||||
|
||||
Once the instrumentation is completed, we add unit tests to the `testing` module. Tests will
|
||||
generally apply to both manual and auto instrumentation, with the only difference being how a client
|
||||
or server is initialized. In a manual test, there will be code calling into the instrumentation API
|
||||
while in an auto test, it will generally just use the library's API as is. Create unit tests in an
|
||||
abstract class with an abstract method that returns an instrumented object like a client. The class
|
||||
should itself extend from `InstrumentationSpecification` to be recognized by Spock and include helper
|
||||
methods for assertions.
|
||||
|
||||
After writing a test or two, go back to the `library` package, make sure it has a test dependency on the
|
||||
`testing` submodule and add a test that inherits from the abstract test class. You should implement
|
||||
the method to initialize the client using the library's mechanism to register interceptors, perhaps
|
||||
a method like `registerInterceptor` or wrapping the result of a library factory when delegating. The
|
||||
test should implement the `InstrumentationTestRunner` trait for common setup logic. If the tests
|
||||
pass, manual instrumentation is working OK.
|
||||
|
||||
#### Writing auto instrumentation
|
||||
|
||||
Now that we have working instrumentation, we can implement auto instrumentation so users of the agent
|
||||
do not have to modify their apps to use it. Make sure the `auto` submodule has a dependency on the
|
||||
`library` submodule and a test dependency on the `testing` submodule. Auto instrumentation defines
|
||||
classes to match against to generate bytecode for. You will often match against the class you used
|
||||
in the unit test for manual instrumentation, for example the builder of a client. And then you could
|
||||
match against the method that creates the builder, for example its constructor. Auto instrumentation
|
||||
can inject byte code to be run after the constructor returns, which would invoke e.g.,
|
||||
`registerInterceptor` and initialize the instrumentation. Often, the code inside the byte code
|
||||
decorator will be identical to the one in the unit test you wrote above - the agent does the work for
|
||||
initializing the instrumentation library, so a user doesn't have to.
|
||||
|
||||
With that written, let's add tests for the auto instrumentation. We basically want to ensure that
|
||||
the instrumentation works without the user knowing about the instrumentation. Add a test that extends
|
||||
the base class you wrote earlier, but in this, create a client using none of the APIs in our project,
|
||||
only the ones offered by the library. Implement the `AgentTestRunner` trait for common setup logic,
|
||||
add `@RunWith(SpockRunner.class)` for a bit more bytecode initialization needed for agent tests
|
||||
and try running. All of the tests should pass for auto instrumentation too.
|
||||
|
||||
### Building
|
||||
|
||||
#### Snapshot builds
|
||||
|
|
|
@ -12,7 +12,7 @@ dependencies {
|
|||
implementation group: 'org.slf4j', name: 'slf4j-simple', version: versions.slf4j
|
||||
// ^ Generally a bad idea for libraries, but we're shadowing.
|
||||
|
||||
testImplementation project(':testing')
|
||||
testImplementation project(':testing-common')
|
||||
testImplementation group: 'org.mockito', name: 'mockito-core', version: '2.19.0'
|
||||
testImplementation group: 'org.assertj', name: 'assertj-core', version: '1.7.1'
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ dependencies {
|
|||
implementation deps.autoservice
|
||||
implementation deps.slf4j
|
||||
|
||||
testImplementation project(':testing')
|
||||
testImplementation project(':testing-common')
|
||||
|
||||
instrumentationMuzzle sourceSets.main.output
|
||||
instrumentationMuzzle configurations.implementation
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
ext {
|
||||
noShadowPublish = true
|
||||
}
|
||||
|
||||
group = 'io.opentelemetry.instrumentation'
|
||||
|
||||
apply from: "$rootDir/gradle/java.gradle"
|
||||
apply from: "$rootDir/gradle/publish.gradle"
|
||||
|
||||
apply plugin: 'com.github.johnrengelman.shadow'
|
||||
|
||||
archivesBaseName = projectDir.parentFile.name
|
||||
|
||||
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
|
||||
|
||||
testImplementation project(':testing-common')
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
archiveClassifier = 'agent'
|
||||
|
||||
configurations = []
|
||||
|
||||
relocate "io.opentelemetry.instrumentation.${javaSubPackage}", "io.opentelemetry.auto.instrumentation.${javaSubPackage}.shaded"
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
archivesBaseName = 'opentelemetry-' + archivesBaseName
|
||||
}
|
|
@ -3,6 +3,10 @@
|
|||
apply plugin: 'net.bytebuddy.byte-buddy'
|
||||
apply plugin: 'muzzle'
|
||||
|
||||
ext {
|
||||
packageInAgentBundle = true
|
||||
}
|
||||
|
||||
byteBuddy {
|
||||
transformation {
|
||||
// Applying NoOp optimizes build by applying bytebuddy plugin to only compileJava task
|
||||
|
@ -21,6 +25,8 @@ tasks.withType(Test) {
|
|||
}
|
||||
|
||||
afterEvaluate {
|
||||
archivesBaseName = 'opentelemetry-auto-' + archivesBaseName
|
||||
|
||||
byteBuddy {
|
||||
transformation {
|
||||
tasks = ['compileJava', 'compileScala', 'compileKotlin']
|
||||
|
@ -34,7 +40,7 @@ afterEvaluate {
|
|||
// Apply common dependencies for instrumentation.
|
||||
implementation(project(':auto-tooling')) {
|
||||
// OpenTelemetry SDK is not needed for compilation, and :opentelemetry-sdk-shaded-for-testing
|
||||
// is brought in for tests by project(:testing) below
|
||||
// is brought in for tests by project(:testing-common) below
|
||||
exclude group: 'io.opentelemetry', module: 'opentelemetry-sdk'
|
||||
}
|
||||
implementation deps.bytebuddy
|
||||
|
@ -48,7 +54,7 @@ afterEvaluate {
|
|||
//testImplementation project(':instrumentation:http-url-connection')
|
||||
testImplementation project(':instrumentation:java-classloader')
|
||||
|
||||
testImplementation project(':testing')
|
||||
testImplementation project(':testing-common')
|
||||
testAnnotationProcessor deps.autoservice
|
||||
testImplementation deps.autoservice
|
||||
testImplementation project(':utils:test-utils')
|
||||
|
|
|
@ -1,63 +0,0 @@
|
|||
plugins {
|
||||
id "com.github.johnrengelman.shadow"
|
||||
}
|
||||
|
||||
ext {
|
||||
minJavaVersionForTests = JavaVersion.VERSION_1_8
|
||||
noShadowPublish = true
|
||||
}
|
||||
|
||||
group = 'io.opentelemetry.instrumentation'
|
||||
|
||||
apply from: "$rootDir/gradle/java.gradle"
|
||||
apply from: "$rootDir/gradle/publish.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'
|
||||
|
||||
testImplementation project(':testing')
|
||||
|
||||
// Include httpclient instrumentation for testing because it is a dependency for aws-sdk.
|
||||
testImplementation project(':instrumentation:apache-httpclient:apache-httpclient-4.0')
|
||||
// Also include netty instrumentation because it is used by aws async client
|
||||
testImplementation project(':instrumentation:netty:netty-4.1')
|
||||
testImplementation group: 'software.amazon.awssdk', name: 'apache-client', version: '2.2.0'
|
||||
testImplementation group: 'software.amazon.awssdk', name: 's3', version: '2.2.0'
|
||||
testImplementation group: 'software.amazon.awssdk', name: 'rds', version: '2.2.0'
|
||||
testImplementation group: 'software.amazon.awssdk', name: 'ec2', version: '2.2.0'
|
||||
testImplementation group: 'software.amazon.awssdk', name: 'sqs', version: '2.2.0'
|
||||
testImplementation group: 'software.amazon.awssdk', name: 'dynamodb', version: '2.2.0'
|
||||
testImplementation group: 'software.amazon.awssdk', name: 'kinesis', version: '2.2.0'
|
||||
testImplementation deps.guava
|
||||
|
||||
latestDepTestImplementation project(':instrumentation:apache-httpclient:apache-httpclient-4.0')
|
||||
latestDepTestImplementation project(':instrumentation:netty:netty-4.1')
|
||||
|
||||
latestDepTestImplementation group: 'software.amazon.awssdk', name: 'apache-client', version: '+'
|
||||
latestDepTestImplementation group: 'software.amazon.awssdk', name: 's3', version: '+'
|
||||
latestDepTestImplementation group: 'software.amazon.awssdk', name: 'rds', version: '+'
|
||||
latestDepTestImplementation group: 'software.amazon.awssdk', name: 'ec2', version: '+'
|
||||
latestDepTestImplementation group: 'software.amazon.awssdk', name: 'sqs', version: '+'
|
||||
latestDepTestImplementation group: 'software.amazon.awssdk', name: 'dynamodb', version: '+'
|
||||
latestDepTestImplementation group: 'software.amazon.awssdk', name: 'kinesis', version: '+'
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
archiveClassifier = 'agent'
|
||||
|
||||
configurations = []
|
||||
|
||||
relocate 'io.opentelemetry.instrumentation.awssdk.v2_2', 'io.opentelemetry.auto.instrumentation.awssdk.v2_2.shaded'
|
||||
}
|
|
@ -41,7 +41,7 @@ dependencies {
|
|||
implementation deps.opentelemetryApi
|
||||
implementation deps.slf4j
|
||||
|
||||
testImplementation project(':testing')
|
||||
testImplementation project(':testing-common')
|
||||
|
||||
latestDepTestImplementation group: 'io.projectreactor', name: 'reactor-core', version: '3.+'
|
||||
// Looks like later versions on reactor need this dependency for some reason even though it is marked as optional.
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
ext {
|
||||
minJavaVersionForTests = JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
apply from: "$rootDir/gradle/instrumentation.gradle"
|
||||
apply plugin: 'org.unbroken-dome.test-sets'
|
||||
|
||||
// TODO(anuraaga): Move into instrumentation.gradle
|
||||
archivesBaseName = projectDir.parentFile.name
|
||||
|
||||
muzzle {
|
||||
pass {
|
||||
group = "software.amazon.awssdk"
|
||||
module = "aws-core"
|
||||
versions = "[2.2.0,)"
|
||||
}
|
||||
}
|
||||
|
||||
testSets {
|
||||
latestDepTest {
|
||||
dirName = 'test'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(path: ':instrumentation:aws-sdk:aws-sdk-2.2:library', configuration: 'shadow')
|
||||
|
||||
compileOnly group: 'software.amazon.awssdk', name: 'aws-core', version: '2.2.0'
|
||||
|
||||
testImplementation project(':instrumentation:aws-sdk:aws-sdk-2.2:testing')
|
||||
|
||||
latestDepTestImplementation group: 'software.amazon.awssdk', name: 'apache-client', version: '+'
|
||||
latestDepTestImplementation group: 'software.amazon.awssdk', name: 's3', version: '+'
|
||||
latestDepTestImplementation group: 'software.amazon.awssdk', name: 'rds', version: '+'
|
||||
latestDepTestImplementation group: 'software.amazon.awssdk', name: 'ec2', version: '+'
|
||||
latestDepTestImplementation group: 'software.amazon.awssdk', name: 'sqs', version: '+'
|
||||
latestDepTestImplementation group: 'software.amazon.awssdk', name: 'dynamodb', version: '+'
|
||||
latestDepTestImplementation group: 'software.amazon.awssdk', name: 'kinesis', version: '+'
|
||||
}
|
|
@ -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.
|
||||
*/
|
||||
|
||||
import io.opentelemetry.auto.test.AgentTestTrait
|
||||
import io.opentelemetry.auto.test.SpockRunner
|
||||
import io.opentelemetry.instrumentation.awssdk.v2_2.AbstractAws2ClientTest
|
||||
import org.junit.runner.RunWith
|
||||
import software.amazon.awssdk.core.client.builder.SdkClientBuilder
|
||||
|
||||
@RunWith(SpockRunner)
|
||||
class Aws2ClientTest extends AbstractAws2ClientTest implements AgentTestTrait {
|
||||
@Override
|
||||
void configureSdkClient(SdkClientBuilder builder) {
|
||||
}
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
ext {
|
||||
minJavaVersionForTests = JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
apply from: "$rootDir/gradle/instrumentation.gradle"
|
||||
apply plugin: 'org.unbroken-dome.test-sets'
|
||||
|
||||
muzzle {
|
||||
pass {
|
||||
group = "software.amazon.awssdk"
|
||||
module = "aws-core"
|
||||
versions = "[2.2.0,)"
|
||||
}
|
||||
}
|
||||
|
||||
testSets {
|
||||
latestDepTest
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(path: ':instrumentation-core:aws-sdk:aws-sdk-2.2', configuration: 'shadow')
|
||||
|
||||
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.
|
||||
testImplementation project(':instrumentation:apache-httpclient:apache-httpclient-4.0')
|
||||
// Also include netty instrumentation because it is used by aws async client
|
||||
testImplementation project(':instrumentation:netty:netty-4.1')
|
||||
testImplementation group: 'software.amazon.awssdk', name: 'apache-client', version: '2.2.0'
|
||||
testImplementation group: 'software.amazon.awssdk', name: 's3', version: '2.2.0'
|
||||
testImplementation group: 'software.amazon.awssdk', name: 'rds', version: '2.2.0'
|
||||
testImplementation group: 'software.amazon.awssdk', name: 'ec2', version: '2.2.0'
|
||||
testImplementation group: 'software.amazon.awssdk', name: 'sqs', version: '2.2.0'
|
||||
testImplementation group: 'software.amazon.awssdk', name: 'dynamodb', version: '2.2.0'
|
||||
testImplementation group: 'software.amazon.awssdk', name: 'kinesis', version: '2.2.0'
|
||||
testImplementation deps.guava
|
||||
|
||||
latestDepTestImplementation project(':instrumentation:apache-httpclient:apache-httpclient-4.0')
|
||||
latestDepTestImplementation project(':instrumentation:netty:netty-4.1')
|
||||
|
||||
latestDepTestImplementation group: 'software.amazon.awssdk', name: 'apache-client', version: '+'
|
||||
latestDepTestImplementation group: 'software.amazon.awssdk', name: 's3', version: '+'
|
||||
latestDepTestImplementation group: 'software.amazon.awssdk', name: 'rds', version: '+'
|
||||
latestDepTestImplementation group: 'software.amazon.awssdk', name: 'ec2', version: '+'
|
||||
latestDepTestImplementation group: 'software.amazon.awssdk', name: 'sqs', version: '+'
|
||||
latestDepTestImplementation group: 'software.amazon.awssdk', name: 'dynamodb', version: '+'
|
||||
latestDepTestImplementation group: 'software.amazon.awssdk', name: 'kinesis', version: '+'
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
ext {
|
||||
minJavaVersionForTests = JavaVersion.VERSION_1_8
|
||||
javaSubPackage = 'awssdk.v2_2'
|
||||
}
|
||||
|
||||
apply from: "$rootDir/gradle/instrumentation-library.gradle"
|
||||
apply plugin: 'org.unbroken-dome.test-sets'
|
||||
|
||||
testSets {
|
||||
latestDepTest {
|
||||
dirName = 'test'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly group: 'software.amazon.awssdk', name: 'aws-core', version: '2.2.0'
|
||||
|
||||
testImplementation project(':instrumentation:aws-sdk:aws-sdk-2.2:testing')
|
||||
|
||||
latestDepTestImplementation group: 'software.amazon.awssdk', name: 'apache-client', version: '+'
|
||||
latestDepTestImplementation group: 'software.amazon.awssdk', name: 's3', version: '+'
|
||||
latestDepTestImplementation group: 'software.amazon.awssdk', name: 'rds', version: '+'
|
||||
latestDepTestImplementation group: 'software.amazon.awssdk', name: 'ec2', version: '+'
|
||||
latestDepTestImplementation group: 'software.amazon.awssdk', name: 'sqs', version: '+'
|
||||
latestDepTestImplementation group: 'software.amazon.awssdk', name: 'dynamodb', version: '+'
|
||||
latestDepTestImplementation group: 'software.amazon.awssdk', name: 'kinesis', version: '+'
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* 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 io.opentelemetry.auto.test.InstrumentationTestTrait
|
||||
import software.amazon.awssdk.core.client.builder.SdkClientBuilder
|
||||
import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration
|
||||
|
||||
class Aws2ClientTest extends AbstractAws2ClientTest implements InstrumentationTestTrait {
|
||||
@Override
|
||||
void configureSdkClient(SdkClientBuilder builder) {
|
||||
builder.overrideConfiguration(ClientOverrideConfiguration.builder()
|
||||
.addExecutionInterceptor(AwsSdk.newInterceptor())
|
||||
.build())
|
||||
}
|
||||
}
|
|
@ -1,349 +0,0 @@
|
|||
/*
|
||||
* 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.test.AgentTestRunner
|
||||
import io.opentelemetry.trace.attributes.SemanticAttributes
|
||||
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.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 Aws2ClientTest extends AgentTestRunner {
|
||||
|
||||
private static final StaticCredentialsProvider CREDENTIALS_PROVIDER = StaticCredentialsProvider
|
||||
.create(AwsBasicCredentials.create("my-access-key", "my-secret-key"))
|
||||
|
||||
@Shared
|
||||
def responseBody = new AtomicReference<String>()
|
||||
|
||||
@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)
|
||||
.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()
|
||||
attributes {
|
||||
"${SemanticAttributes.NET_PEER_NAME.key()}" "localhost"
|
||||
"${SemanticAttributes.NET_PEER_PORT.key()}" server.address.port
|
||||
"${SemanticAttributes.HTTP_URL.key()}" { it.startsWith("${server.address}${path}") }
|
||||
"${SemanticAttributes.HTTP_METHOD.key()}" "$method"
|
||||
"${SemanticAttributes.HTTP_STATUS_CODE.key()}" 200
|
||||
"${SemanticAttributes.HTTP_USER_AGENT.key()}" { it.startsWith("aws-sdk-java/") }
|
||||
"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()) } | """
|
||||
<CreateQueueResponse>
|
||||
<CreateQueueResult><QueueUrl>https://queue.amazonaws.com/123456789012/MyQueue</QueueUrl></CreateQueueResult>
|
||||
<ResponseMetadata><RequestId>7a62c49f-347e-4fc4-9331-6e8e7a96aa73</RequestId></ResponseMetadata>
|
||||
</CreateQueueResponse>
|
||||
"""
|
||||
"Sqs" | "SendMessage" | "POST" | "/" | "27daac76-34dd-47df-bd01-1f6e873584a0" | SqsClient.builder() | { c -> c.sendMessage(SendMessageRequest.builder().queueUrl("someurl").messageBody("").build()) } | """
|
||||
<SendMessageResponse>
|
||||
<SendMessageResult>
|
||||
<MD5OfMessageBody>d41d8cd98f00b204e9800998ecf8427e</MD5OfMessageBody>
|
||||
<MD5OfMessageAttributes>3ae8f24a165a8cedc005670c81a27295</MD5OfMessageAttributes>
|
||||
<MessageId>5fea7756-0ea4-451a-a703-a558b933e274</MessageId>
|
||||
</SendMessageResult>
|
||||
<ResponseMetadata><RequestId>27daac76-34dd-47df-bd01-1f6e873584a0</RequestId></ResponseMetadata>
|
||||
</SendMessageResponse>
|
||||
"""
|
||||
"Ec2" | "AllocateAddress" | "POST" | "/" | "59dbff89-35bd-4eac-99ed-be587EXAMPLE" | Ec2Client.builder() | { c -> c.allocateAddress() } | """
|
||||
<AllocateAddressResponse xmlns="http://ec2.amazonaws.com/doc/2016-11-15/">
|
||||
<requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
|
||||
<publicIp>192.0.2.1</publicIp>
|
||||
<domain>standard</domain>
|
||||
</AllocateAddressResponse>
|
||||
"""
|
||||
"Rds" | "DeleteOptionGroup" | "POST" | "/" | "0ac9cda2-bbf4-11d3-f92b-31fa5e8dbc99" | RdsClient.builder() | { c -> c.deleteOptionGroup(DeleteOptionGroupRequest.builder().build()) } | """
|
||||
<DeleteOptionGroupResponse xmlns="http://rds.amazonaws.com/doc/2014-09-01/">
|
||||
<ResponseMetadata><RequestId>0ac9cda2-bbf4-11d3-f92b-31fa5e8dbc99</RequestId></ResponseMetadata>
|
||||
</DeleteOptionGroupResponse>
|
||||
"""
|
||||
}
|
||||
|
||||
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)
|
||||
.build()
|
||||
responseBody.set(body)
|
||||
def response = call.call(client)
|
||||
|
||||
if (response instanceof Future) {
|
||||
response = response.get()
|
||||
}
|
||||
|
||||
expect:
|
||||
response != null
|
||||
|
||||
assertTraces(2) {
|
||||
trace(0, 1) {
|
||||
span(0) {
|
||||
operationName "$service.$operation"
|
||||
spanKind CLIENT
|
||||
errored false
|
||||
parent()
|
||||
attributes {
|
||||
"${SemanticAttributes.NET_PEER_NAME.key()}" "localhost"
|
||||
"${SemanticAttributes.NET_PEER_PORT.key()}" server.address.port
|
||||
"${SemanticAttributes.HTTP_URL.key()}" { it.startsWith("${server.address}${path}") }
|
||||
"${SemanticAttributes.HTTP_METHOD.key()}" "$method"
|
||||
"${SemanticAttributes.HTTP_STATUS_CODE.key()}" 200
|
||||
"${SemanticAttributes.HTTP_USER_AGENT.key()}" { it.startsWith("aws-sdk-java/") }
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO: this should be part of the same trace but netty instrumentation doesn't cooperate
|
||||
trace(1, 1) {
|
||||
span(0) {
|
||||
operationName expectedOperationName(method)
|
||||
spanKind CLIENT
|
||||
errored false
|
||||
parent()
|
||||
attributes {
|
||||
"${SemanticAttributes.NET_PEER_NAME.key()}" "localhost"
|
||||
"${SemanticAttributes.NET_PEER_IP.key()}" "127.0.0.1"
|
||||
"${SemanticAttributes.NET_PEER_PORT.key()}" server.address.port
|
||||
"${SemanticAttributes.HTTP_URL.key()}" { it.startsWith("${server.address}${path}") }
|
||||
"${SemanticAttributes.HTTP_METHOD.key()}" "$method"
|
||||
"${SemanticAttributes.HTTP_STATUS_CODE.key()}" 200
|
||||
"${SemanticAttributes.HTTP_USER_AGENT.key()}" { it.startsWith("aws-sdk-java/") }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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()) } | """
|
||||
<CreateQueueResponse>
|
||||
<CreateQueueResult><QueueUrl>https://queue.amazonaws.com/123456789012/MyQueue</QueueUrl></CreateQueueResult>
|
||||
<ResponseMetadata><RequestId>7a62c49f-347e-4fc4-9331-6e8e7a96aa73</RequestId></ResponseMetadata>
|
||||
</CreateQueueResponse>
|
||||
"""
|
||||
"Sqs" | "SendMessage" | "POST" | "/" | "27daac76-34dd-47df-bd01-1f6e873584a0" | SqsAsyncClient.builder() | { c -> c.sendMessage(SendMessageRequest.builder().queueUrl("someurl").messageBody("").build()) } | """
|
||||
<SendMessageResponse>
|
||||
<SendMessageResult>
|
||||
<MD5OfMessageBody>d41d8cd98f00b204e9800998ecf8427e</MD5OfMessageBody>
|
||||
<MD5OfMessageAttributes>3ae8f24a165a8cedc005670c81a27295</MD5OfMessageAttributes>
|
||||
<MessageId>5fea7756-0ea4-451a-a703-a558b933e274</MessageId>
|
||||
</SendMessageResult>
|
||||
<ResponseMetadata><RequestId>27daac76-34dd-47df-bd01-1f6e873584a0</RequestId></ResponseMetadata>
|
||||
</SendMessageResponse>
|
||||
"""
|
||||
"Ec2" | "AllocateAddress" | "POST" | "/" | "59dbff89-35bd-4eac-99ed-be587EXAMPLE" | Ec2AsyncClient.builder() | { c -> c.allocateAddress() } | """
|
||||
<AllocateAddressResponse xmlns="http://ec2.amazonaws.com/doc/2016-11-15/">
|
||||
<requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
|
||||
<publicIp>192.0.2.1</publicIp>
|
||||
<domain>standard</domain>
|
||||
</AllocateAddressResponse>
|
||||
"""
|
||||
"Rds" | "DeleteOptionGroup" | "POST" | "/" | "0ac9cda2-bbf4-11d3-f92b-31fa5e8dbc99" | RdsAsyncClient.builder() | { c -> c.deleteOptionGroup(DeleteOptionGroupRequest.builder().build()) } | """
|
||||
<DeleteOptionGroupResponse xmlns="http://rds.amazonaws.com/doc/2014-09-01/">
|
||||
<ResponseMetadata><RequestId>0ac9cda2-bbf4-11d3-f92b-31fa5e8dbc99</RequestId></ResponseMetadata>
|
||||
</DeleteOptionGroupResponse>
|
||||
"""
|
||||
}
|
||||
|
||||
def "client can be customized by user"() {
|
||||
setup:
|
||||
def client = DynamoDbClient.builder()
|
||||
.endpointOverride(server.address)
|
||||
.region(Region.AP_NORTHEAST_1)
|
||||
.credentialsProvider(CREDENTIALS_PROVIDER)
|
||||
.overrideConfiguration {
|
||||
it.putHeader("x-name", "value")
|
||||
}
|
||||
.build()
|
||||
|
||||
when:
|
||||
responseBody.set("")
|
||||
client.createTable(CreateTableRequest.builder().tableName("sometable").build())
|
||||
|
||||
then:
|
||||
assertTraces(1) {
|
||||
trace(0, 1) {
|
||||
span(0) {}
|
||||
}
|
||||
}
|
||||
server.lastRequest.headers.get("x-name") == "value"
|
||||
|
||||
cleanup:
|
||||
server.close()
|
||||
}
|
||||
|
||||
// TODO(anuraaga): Add events for retries.
|
||||
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)
|
||||
.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()
|
||||
attributes {
|
||||
"${SemanticAttributes.NET_PEER_NAME.key()}" "localhost"
|
||||
"${SemanticAttributes.NET_PEER_PORT.key()}" server.address.port
|
||||
"${SemanticAttributes.HTTP_URL.key()}" "$server.address/somebucket/somekey"
|
||||
"${SemanticAttributes.HTTP_METHOD.key()}" "GET"
|
||||
"aws.service" "S3"
|
||||
"aws.operation" "GetObject"
|
||||
"aws.agent" "java-aws-sdk"
|
||||
"aws.bucket.name" "somebucket"
|
||||
errorAttributes 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
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
apply from: "$rootDir/gradle/java.gradle"
|
||||
|
||||
dependencies {
|
||||
api project(':testing-common')
|
||||
|
||||
api group: 'software.amazon.awssdk', name: 'apache-client', version: '2.2.0'
|
||||
api group: 'software.amazon.awssdk', name: 's3', version: '2.2.0'
|
||||
api group: 'software.amazon.awssdk', name: 'rds', version: '2.2.0'
|
||||
api group: 'software.amazon.awssdk', name: 'ec2', version: '2.2.0'
|
||||
api group: 'software.amazon.awssdk', name: 'sqs', version: '2.2.0'
|
||||
api group: 'software.amazon.awssdk', name: 'dynamodb', version: '2.2.0'
|
||||
api group: 'software.amazon.awssdk', name: 'kinesis', version: '2.2.0'
|
||||
|
||||
implementation deps.guava
|
||||
|
||||
implementation deps.groovy
|
||||
implementation deps.opentelemetryApi
|
||||
implementation deps.spock
|
||||
}
|
|
@ -14,15 +14,15 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import io.opentelemetry.auto.bootstrap.instrumentation.decorator.HttpClientDecorator
|
||||
import io.opentelemetry.auto.test.InstrumentationTestRunner
|
||||
import io.opentelemetry.instrumentation.awssdk.v2_2.AwsSdk
|
||||
package io.opentelemetry.instrumentation.awssdk.v2_2
|
||||
|
||||
import io.opentelemetry.auto.test.InstrumentationSpecification
|
||||
import io.opentelemetry.trace.attributes.SemanticAttributes
|
||||
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.client.builder.SdkClientBuilder
|
||||
import software.amazon.awssdk.core.exception.SdkClientException
|
||||
import software.amazon.awssdk.http.apache.ApacheHttpClient
|
||||
import software.amazon.awssdk.regions.Region
|
||||
|
@ -54,7 +54,7 @@ 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 {
|
||||
abstract class AbstractAws2ClientTest extends InstrumentationSpecification {
|
||||
|
||||
private static final StaticCredentialsProvider CREDENTIALS_PROVIDER = StaticCredentialsProvider
|
||||
.create(AwsBasicCredentials.create("my-access-key", "my-secret-key"))
|
||||
|
@ -72,15 +72,15 @@ class Aws2ClientCoreTest extends InstrumentationTestRunner {
|
|||
}
|
||||
}
|
||||
|
||||
def "send #operation request with builder {#builder.class.getName()} mocked response"() {
|
||||
abstract void configureSdkClient(SdkClientBuilder builder)
|
||||
|
||||
def "send #operation request with builder #builder.class.getName() mocked response"() {
|
||||
setup:
|
||||
configureSdkClient(builder)
|
||||
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)
|
||||
|
@ -164,15 +164,13 @@ class Aws2ClientCoreTest extends InstrumentationTestRunner {
|
|||
"""
|
||||
}
|
||||
|
||||
def "send #operation async request with builder {#builder.class.getName()} mocked response"() {
|
||||
def "send #operation async request with builder #builder.class.getName() mocked response"() {
|
||||
setup:
|
||||
configureSdkClient(builder)
|
||||
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)
|
||||
|
@ -269,13 +267,12 @@ class Aws2ClientCoreTest extends InstrumentationTestRunner {
|
|||
}
|
||||
}
|
||||
}
|
||||
def client = S3Client.builder()
|
||||
def builder = S3Client.builder()
|
||||
configureSdkClient(builder)
|
||||
def client = 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()
|
||||
|
||||
|
@ -314,4 +311,5 @@ class Aws2ClientCoreTest extends InstrumentationTestRunner {
|
|||
String expectedOperationName(String method) {
|
||||
return method != null ? "HTTP $method" : HttpClientDecorator.DEFAULT_SPAN_NAME
|
||||
}
|
||||
|
||||
}
|
|
@ -21,13 +21,13 @@ Project instr_project = project
|
|||
subprojects {
|
||||
afterEvaluate { Project subProj ->
|
||||
if (subProj.getPlugins().hasPlugin('java')) {
|
||||
subProj.archivesBaseName = 'opentelemetry-auto-' + subProj.archivesBaseName
|
||||
|
||||
// Make it so all instrumentation subproject tests can be run with a single command.
|
||||
instr_project.tasks.test.dependsOn(subProj.tasks.test)
|
||||
|
||||
instr_project.dependencies {
|
||||
implementation(project(subProj.getPath()))
|
||||
if (subProj.findProperty('packageInAgentBundle')) {
|
||||
instr_project.dependencies {
|
||||
implementation(project(subProj.getPath()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ dependencies {
|
|||
implementation project(':instrumentation:servlet:servlet-3.0')
|
||||
|
||||
// Don't want to conflict with jetty from the test server.
|
||||
testImplementation(project(':testing')) {
|
||||
testImplementation(project(':testing-common')) {
|
||||
exclude group: 'org.eclipse.jetty', module: 'jetty-server'
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ testSets {
|
|||
|
||||
configurations {
|
||||
// In order to test the real log4j library we need to remove the log4j transitive
|
||||
// dependency 'log4j-over-slf4j' brought in by :testing which would shadow
|
||||
// dependency 'log4j-over-slf4j' brought in by :testing-common which would shadow
|
||||
// the log4j module under test using a proxy to slf4j instead.
|
||||
testImplementation.exclude group: 'org.slf4j', module: 'log4j-over-slf4j'
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ testSets {
|
|||
|
||||
configurations {
|
||||
// In order to test the real log4j library we need to remove the log4j transitive
|
||||
// dependency 'log4j-over-slf4j' brought in by :testing which would shadow
|
||||
// dependency 'log4j-over-slf4j' brought in by :testing-common which would shadow
|
||||
// the log4j module under test using a proxy to slf4j instead.
|
||||
testImplementation.exclude group: 'org.slf4j', module: 'log4j-over-slf4j'
|
||||
}
|
||||
|
|
|
@ -25,7 +25,6 @@ dependencies {
|
|||
|
||||
implementation project(':auto-tooling')
|
||||
|
||||
testImplementation project(':testing')
|
||||
testImplementation project(':instrumentation:java-concurrent')
|
||||
testImplementation group: 'com.squareup.okhttp', name: 'okhttp', version: '2.2.0'
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ dependencies {
|
|||
|
||||
implementation project(':auto-tooling')
|
||||
|
||||
testImplementation(project(':testing')) {
|
||||
testImplementation(project(':testing-common')) {
|
||||
exclude module: 'okhttp'
|
||||
}
|
||||
testImplementation group: 'com.squareup.okhttp3', name: 'okhttp', version: '3.0.0'
|
||||
|
|
|
@ -26,7 +26,7 @@ dependencies {
|
|||
compileOnly group: 'javax.servlet', name: 'servlet-api', version: '2.2'
|
||||
api(project(':instrumentation-core:servlet'))
|
||||
|
||||
testImplementation(project(':testing')) {
|
||||
testImplementation(project(':testing-common')) {
|
||||
exclude group: 'org.eclipse.jetty', module: 'jetty-server'
|
||||
}
|
||||
testImplementation group: 'org.eclipse.jetty', name: 'jetty-server', version: '7.0.0.v20091005'
|
||||
|
|
|
@ -25,7 +25,7 @@ dependencies {
|
|||
compileOnly group: 'javax.servlet', name: 'javax.servlet-api', version: '3.0.1'
|
||||
api(project(':instrumentation-core:servlet'))
|
||||
|
||||
testImplementation(project(':testing')) {
|
||||
testImplementation(project(':testing-common')) {
|
||||
exclude group: 'org.eclipse.jetty', module: 'jetty-server'
|
||||
}
|
||||
testImplementation group: 'org.eclipse.jetty', name: 'jetty-server', version: '8.0.0.v20110901'
|
||||
|
|
|
@ -17,7 +17,7 @@ testSets {
|
|||
dependencies {
|
||||
compileOnly group: 'javax.servlet', name: 'servlet-api', version: '2.3'
|
||||
|
||||
testImplementation(project(':testing')) {
|
||||
testImplementation(project(':testing-common')) {
|
||||
exclude group: 'org.eclipse.jetty', module: 'jetty-server'
|
||||
}
|
||||
testImplementation group: 'org.eclipse.jetty', name: 'jetty-server', version: '7.0.0.v20091005'
|
||||
|
|
|
@ -27,7 +27,7 @@ dependencies {
|
|||
// compileOnly group: 'org.springframework', name: 'spring-webmvc', version: '2.5.6'
|
||||
// compileOnly group: 'javax.servlet', name: 'servlet-api', version: '2.4'
|
||||
|
||||
testImplementation(project(':testing')) {
|
||||
testImplementation(project(':testing-common')) {
|
||||
exclude(module: 'jetty-server') // incompatable servlet api
|
||||
}
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ include ':agent-tooling'
|
|||
include ':load-generator'
|
||||
|
||||
// misc
|
||||
include ':testing'
|
||||
include ':testing-common'
|
||||
include ':utils:test-utils'
|
||||
include ':utils:thread-utils'
|
||||
|
||||
|
@ -47,7 +47,9 @@ include ':instrumentation:apache-httpasyncclient-4.0'
|
|||
include ':instrumentation:apache-httpclient:apache-httpclient-2.0'
|
||||
include ':instrumentation:apache-httpclient:apache-httpclient-4.0'
|
||||
include ':instrumentation:aws-sdk:aws-sdk-1.11'
|
||||
include ':instrumentation:aws-sdk:aws-sdk-2.2'
|
||||
include ':instrumentation:aws-sdk:aws-sdk-2.2:auto'
|
||||
include ':instrumentation:aws-sdk:aws-sdk-2.2:library'
|
||||
include ':instrumentation:aws-sdk:aws-sdk-2.2:testing'
|
||||
include ':instrumentation:cassandra:cassandra-3.0'
|
||||
include ':instrumentation:cassandra:cassandra-4.0'
|
||||
include ':instrumentation:cdi-testing'
|
||||
|
@ -151,7 +153,6 @@ include ':instrumentation:vertx-3.0'
|
|||
include ':instrumentation:vertx-reactive-3.5'
|
||||
|
||||
include ':instrumentation-core:apache-httpclient-4.0'
|
||||
include ':instrumentation-core:aws-sdk:aws-sdk-2.2'
|
||||
include ':instrumentation-core:reactor-3.1'
|
||||
include ':instrumentation-core:servlet'
|
||||
include ':instrumentation-core:spring:spring-boot-autoconfigure'
|
||||
|
@ -173,7 +174,11 @@ include ':benchmark-integration:jetty-perftest'
|
|||
include ':benchmark-integration:play-perftest'
|
||||
|
||||
def setBuildFile(project) {
|
||||
project.buildFileName = "${project.name}.gradle"
|
||||
if (['auto', 'library', 'testing'].contains(project.projectDir.name)) {
|
||||
project.buildFileName = "${project.projectDir.parentFile.name}-${project.projectDir.name}.gradle"
|
||||
} else {
|
||||
project.buildFileName = "${project.name}.gradle"
|
||||
}
|
||||
project.children.each {
|
||||
setBuildFile(it)
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ description = 'smoke-tests'
|
|||
|
||||
dependencies {
|
||||
api deps.spock
|
||||
api project(':testing')
|
||||
api project(':testing-common')
|
||||
|
||||
implementation project(':auto-exporters:opentelemetry-auto-exporter-logging')
|
||||
implementation deps.slf4j
|
||||
|
|
|
@ -171,7 +171,6 @@ public abstract class AgentTestRunner extends AgentSpecification {
|
|||
public void beforeTest() {
|
||||
assert !getTestTracer().getCurrentSpan().getContext().isValid()
|
||||
: "Span is active before test has started: " + getTestTracer().getCurrentSpan();
|
||||
log.debug("Starting test: '{}'", getSpecificationContext().getCurrentIteration().getName());
|
||||
TEST_WRITER.clear();
|
||||
}
|
||||
|
|
@ -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.auto.test
|
||||
|
||||
import com.google.common.base.Predicate
|
||||
import groovy.transform.stc.ClosureParams
|
||||
import groovy.transform.stc.SimpleType
|
||||
import io.opentelemetry.auto.test.asserts.InMemoryExporterAssert
|
||||
import io.opentelemetry.sdk.trace.data.SpanData
|
||||
|
||||
/**
|
||||
* A trait which initializes agent tests, including bytecode manipulation and a test span exporter.
|
||||
* All agent tests should implement this trait.
|
||||
*/
|
||||
trait AgentTestTrait {
|
||||
|
||||
static AgentTestRunner agentTestRunner
|
||||
static InMemoryExporter testWriter
|
||||
|
||||
def setupSpec() {
|
||||
agentTestRunner = new AgentTestRunnerImpl()
|
||||
testWriter = AgentTestRunner.TEST_WRITER
|
||||
|
||||
AgentTestRunner.agentSetup()
|
||||
agentTestRunner.setupBeforeTests()
|
||||
|
||||
childSetupSpec()
|
||||
}
|
||||
|
||||
def setup() {
|
||||
agentTestRunner.beforeTest()
|
||||
|
||||
childSetup()
|
||||
}
|
||||
|
||||
def cleanupSpec() {
|
||||
agentTestRunner.cleanUpAfterTests()
|
||||
AgentTestRunner.agentCleanup()
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialization method called once per individual test. Equivalent to Spock's {@code setup} which
|
||||
* we can't use because of https://stackoverflow.com/questions/56464191/public-groovy-method-must-be-public-says-the-compiler
|
||||
*/
|
||||
def childSetup() {}
|
||||
|
||||
/**
|
||||
* Initialization method called once per test class. Equivalent to Spock's {@code setupSpec} which
|
||||
* we can't use because of https://stackoverflow.com/questions/56464191/public-groovy-method-must-be-public-says-the-compiler
|
||||
*/
|
||||
def childSetupSpec() {}
|
||||
|
||||
/**
|
||||
* Cleanup method called once per test class. Equivalent to Spock's {@code cleanupSpec} which
|
||||
* we can't use because of https://stackoverflow.com/questions/56464191/public-groovy-method-must-be-public-says-the-compiler
|
||||
*/
|
||||
def childCleanupSpec() {}
|
||||
|
||||
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) {
|
||||
AgentTestRunner.assertTraces(size, spec)
|
||||
}
|
||||
|
||||
void assertTracesWithFilter(
|
||||
final int size,
|
||||
final Predicate<List<SpanData>> excludes,
|
||||
@ClosureParams(
|
||||
value = SimpleType,
|
||||
options = "io.opentelemetry.auto.test.asserts.ListWriterAssert")
|
||||
@DelegatesTo(value = InMemoryExporterAssert, strategy = Closure.DELEGATE_FIRST)
|
||||
final Closure spec) {
|
||||
AgentTestRunner.assertTracesWithFilter(size, excludes, spec)
|
||||
}
|
||||
|
||||
static class AgentTestRunnerImpl extends AgentTestRunner {}
|
||||
}
|
|
@ -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.test
|
||||
|
||||
import com.google.common.base.Predicate
|
||||
import groovy.transform.stc.ClosureParams
|
||||
import groovy.transform.stc.SimpleType
|
||||
import io.opentelemetry.auto.test.asserts.InMemoryExporterAssert
|
||||
import io.opentelemetry.sdk.trace.data.SpanData
|
||||
import spock.lang.Specification
|
||||
|
||||
/**
|
||||
* Base class for test specifications that are shared between instrumentation libraries and agent.
|
||||
* The methods in this class are implemented by {@link AgentTestTrait} and
|
||||
* {@link InstrumentationTestTrait}.
|
||||
*/
|
||||
abstract class InstrumentationSpecification extends Specification {
|
||||
abstract 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)
|
||||
|
||||
abstract void assertTracesWithFilter(
|
||||
final int size,
|
||||
final Predicate<List<SpanData>> excludes,
|
||||
@ClosureParams(
|
||||
value = SimpleType,
|
||||
options = "io.opentelemetry.auto.test.asserts.ListWriterAssert")
|
||||
@DelegatesTo(value = InMemoryExporterAssert, strategy = Closure.DELEGATE_FIRST)
|
||||
final Closure spec)
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* 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 groovy.transform.stc.ClosureParams
|
||||
import groovy.transform.stc.SimpleType
|
||||
import io.opentelemetry.auto.test.asserts.InMemoryExporterAssert
|
||||
import io.opentelemetry.sdk.trace.data.SpanData
|
||||
|
||||
/**
|
||||
* A trait which initializes instrumentation library tests, including a test span exporter. All
|
||||
* library tests should implement this trait.
|
||||
*/
|
||||
trait InstrumentationTestTrait {
|
||||
|
||||
static InstrumentationTestRunner instrumentationTestRunner
|
||||
static InMemoryExporter testWriter
|
||||
|
||||
def setupSpec() {
|
||||
instrumentationTestRunner = new InstrumentationTestRunnerImpl()
|
||||
testWriter = InstrumentationTestRunner.TEST_WRITER
|
||||
}
|
||||
|
||||
def setup() {
|
||||
instrumentationTestRunner.beforeTest()
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialization method called once per test class. Equivalent to Spock's {@code setupSpec} which
|
||||
* we can't use because of https://stackoverflow.com/questions/56464191/public-groovy-method-must-be-public-says-the-compiler
|
||||
*/
|
||||
def childSetupSpec() {}
|
||||
|
||||
/**
|
||||
* Initialization method called once per individual test. Equivalent to Spock's {@code setup} which
|
||||
* we can't use because of https://stackoverflow.com/questions/56464191/public-groovy-method-must-be-public-says-the-compiler
|
||||
*/
|
||||
def childSetup() {}
|
||||
|
||||
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) {
|
||||
instrumentationTestRunner.assertTraces(size, spec)
|
||||
}
|
||||
|
||||
void assertTracesWithFilter(
|
||||
final int size,
|
||||
final Predicate<List<SpanData>> excludes,
|
||||
@ClosureParams(
|
||||
value = SimpleType,
|
||||
options = "io.opentelemetry.auto.test.asserts.ListWriterAssert")
|
||||
@DelegatesTo(value = InMemoryExporterAssert, strategy = Closure.DELEGATE_FIRST)
|
||||
final Closure spec) {
|
||||
instrumentationTestRunner.assertTracesWithFilter(size, spec)
|
||||
}
|
||||
|
||||
static class InstrumentationTestRunnerImpl extends InstrumentationTestRunner {}
|
||||
}
|
Loading…
Reference in New Issue