diff --git a/.circleci/config.yml b/.circleci/config.yml index 38a1d780d7..0d62e90151 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -142,11 +142,21 @@ jobs: environment: - TEST_TASK: testJava13 + test_zulu13: + <<: *default_test_job + environment: + - TEST_TASK: testJavaZULU13 + test_14: <<: *default_test_job environment: - TEST_TASK: testJava14 + test_zulu14: + <<: *default_test_job + environment: + - TEST_TASK: testJavaZULU14 + check: <<: *defaults @@ -243,25 +253,25 @@ workflows: only: /.*/ # - test_ibm8: # requires: - # - build + # - build # filters: # tags: # only: /.*/ # - test_zulu8: # requires: - # - build + # - build # filters: # tags: # only: /.*/ # - test_9: # requires: - # - build + # - build # filters: # tags: # only: /.*/ # - test_10: # requires: - # - build + # - build # filters: # tags: # only: /.*/ @@ -273,13 +283,13 @@ workflows: only: /.*/ # - test_zulu11: # requires: - # - build + # - build # filters: # tags: # only: /.*/ # - test_12: # requires: - # - build + # - build # filters: # tags: # only: /.*/ @@ -289,12 +299,24 @@ workflows: # filters: # tags: # only: /.*/ + # - test_zulu13: + # requires: + # - build + # filters: + # tags: + # only: /.*/ - test_14: requires: - build filters: tags: only: /.*/ + # - test_zulu14: + # requires: + # - build + # filters: + # tags: + # only: /.*/ - check: requires: @@ -323,7 +345,9 @@ workflows: # - test_zulu11 # - test_12 # - test_13 + # - test_zulu13 - test_14 + # - test_zulu14 - check filters: branches: @@ -344,7 +368,9 @@ workflows: # - test_zulu11 # - test_12 # - test_13 + # - test_zulu13 - test_14 + # - test_zulu14 - check filters: branches: diff --git a/.github/workflows/draft-release-notes-on-tag.yaml b/.github/workflows/draft-release-notes-on-tag.yaml new file mode 100644 index 0000000000..d889a3a5d1 --- /dev/null +++ b/.github/workflows/draft-release-notes-on-tag.yaml @@ -0,0 +1,85 @@ +name: Draft release notes on tag +on: + create + +jobs: + draft_release_notes: + if: github.event.ref_type == 'tag' && github.event.master_branch == 'master' + runs-on: ubuntu-latest + steps: + - name: Get milestone title + id: milestoneTitle + uses: actions/github-script@0.9.0 + with: + result-encoding: string + script: | + // Our tags are of the form vX.X.X and milestones don't have the "v" + return '${{github.event.ref}}'.startsWith('v') ? '${{github.event.ref}}'.substring(1) : '${{github.event.ref}}'; + - name: Get milestone for tag + id: milestone + uses: actions/github-script@0.9.0 + with: + github-token: ${{secrets.GITHUB_TOKEN}} + script: | + const options = github.issues.listMilestonesForRepo.endpoint.merge({ + owner: context.repo.owner, + repo: context.repo.repo, + state: 'all' + }) + + const milestones = await github.paginate(options) + + const milestone = milestones.find( milestone => milestone.title == '${{steps.milestoneTitle.outputs.result}}' ) + + if (milestone) { + return milestone.number + } else { + return null + } + - name: Get pull requests for milestone + if: fromJSON(steps.milestone.outputs.result) + id: pulls + uses: actions/github-script@0.9.0 + with: + github-token: ${{secrets.GITHUB_TOKEN}} + script: | + const options = github.pulls.list.endpoint.merge({ + owner: context.repo.owner, + repo: context.repo.repo, + state: 'closed' + }) + + const pullRequests = await github.paginate(options) + + return pullRequests.filter(pullRequest => pullRequest.merged_at + && pullRequest.milestone + && pullRequest.milestone.number == ${{steps.milestone.outputs.result}}) + - name: Generate release notes text + if: fromJSON(steps.milestone.outputs.result) + id: generate + uses: actions/github-script@0.9.0 + with: + github-token: ${{secrets.GITHUB_TOKEN}} + result-encoding: string + script: | + var draftText = "# Improvements \n\n# Changes \n\n" + for (let pull of ${{ steps.pulls.outputs.result }}) { + draftText += "* " + pull.title + " #" + pull.number + " \n" + } + draftText += "\n# Fixes \n" + return draftText + - name: Create release notes + if: fromJSON(steps.milestone.outputs.result) + # can't use actions/create-release because it doesn't like the text coming from JS + uses: actions/github-script@0.9.0 + with: + github-token: ${{secrets.GITHUB_TOKEN}} + script: | + await github.repos.createRelease({ + owner: context.repo.owner, + repo: context.repo.repo, + tag_name: '${{ github.event.ref }}', + name: '${{ steps.milestoneTitle.outputs.result }}', + draft: true, + body: `${{ steps.generate.outputs.result }}` + }) \ No newline at end of file diff --git a/.github/workflows/increment-milestones-on-tag.yaml b/.github/workflows/increment-milestones-on-tag.yaml new file mode 100644 index 0000000000..206f0d452d --- /dev/null +++ b/.github/workflows/increment-milestones-on-tag.yaml @@ -0,0 +1,63 @@ +name: Increment milestones on tag +on: + create + +jobs: + increment_milestone: + if: github.event.ref_type == 'tag' && github.event.master_branch == 'master' + runs-on: ubuntu-latest + steps: + - name: Get milestone title + id: milestoneTitle + uses: actions/github-script@0.9.0 + with: + result-encoding: string + script: | + // Our tags are of the form vX.X.X and milestones don't have the "v" + return '${{github.event.ref}}'.startsWith('v') ? '${{github.event.ref}}'.substring(1) : '${{github.event.ref}}'; + - name: Get milestone for tag + id: milestone + uses: actions/github-script@0.9.0 + with: + github-token: ${{secrets.GITHUB_TOKEN}} + script: | + const options = github.issues.listMilestonesForRepo.endpoint.merge({ + owner: context.repo.owner, + repo: context.repo.repo, + state: 'all' + }) + + const milestones = await github.paginate(options) + + const milestone = milestones.find( milestone => milestone.title == '${{steps.milestoneTitle.outputs.result}}' ) + + if (milestone) { + return milestone.number + } else { + return null + } + - name: Close milestone + if: fromJSON(steps.milestone.outputs.result) + uses: actions/github-script@0.9.0 + with: + github-token: ${{secrets.GITHUB_TOKEN}} + script: | + await github.issues.updateMilestone({ + owner: context.repo.owner, + repo: context.repo.repo, + state: 'closed', + milestone_number: ${{steps.milestone.outputs.result}} + }) + - name: Get next minor version + if: fromJSON(steps.milestone.outputs.result) + id: semvers + uses: WyriHaximus/github-action-next-semvers@0.1.0 + with: + version: ${{steps.milestoneTitle.outputs.result}} + - name: Create next milestone + if: fromJSON(steps.milestone.outputs.result) + uses: WyriHaximus/github-action-create-milestone@0.1.0 + with: + title: ${{ steps.semvers.outputs.minor }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/README.md b/README.md index 1d435f0fc1..73826821e8 100644 --- a/README.md +++ b/README.md @@ -34,17 +34,18 @@ to capture telemetry from a number of popular libraries and frameworks. | [JMS](https://javaee.github.io/javaee-spec/javadocs/javax/jms/package-summary.html) | 1.1+ | | [JSP](https://javaee.github.io/javaee-spec/javadocs/javax/servlet/jsp/package-summary.html) | 2.3+ | | [Kafka](https://kafka.apache.org/20/javadoc/overview-summary.html) | 0.11+ | -| [Lettuce](https://github.com/lettuce-io/lettuce-core) | 5.0+ | +| [Lettuce](https://github.com/lettuce-io/lettuce-core) | 4.0+ | | [Log4j](https://logging.apache.org/log4j/2.x/) | 1.1+ | | [Logback](https://github.com/qos-ch/logback) | 1.0+ | | [MongoDB Drivers](https://mongodb.github.io/mongo-java-driver/) | 3.3+ | -| [Netty](https://github.com/netty/netty) | 4.0+ | +| [Netty](https://github.com/netty/netty) | 3.8+ | | [OkHttp](https://github.com/square/okhttp/) | 3.0+ | -| [Play](https://github.com/playframework/playframework) | 2.4+ (not including 2.8.x yet) | +| [Play](https://github.com/playframework/playframework) | 2.3+ (not including 2.8.x yet) | | [Play WS](https://github.com/playframework/play-ws) | 1.0+ | -| [Project Reactor](https://github.com/reactor/reactor-core) | 3.1+ | | [RabbitMQ Client](https://github.com/rabbitmq/rabbitmq-java-client) | 2.7+ | | [Ratpack](https://github.com/ratpack/ratpack) | 1.5+ | +| [Reactor](https://github.com/reactor/reactor-core) | 3.1+ | +| [Rediscala](https://github.com/etaty/rediscala) | 1.8+ | | [RMI](https://docs.oracle.com/en/java/javase/11/docs/api/java.rmi/java/rmi/package-summary.html) | Java 7+ | | [RxJava](https://github.com/ReactiveX/RxJava) | 1.0+ | | [Servlet](https://javaee.github.io/javaee-spec/javadocs/javax/servlet/package-summary.html) | 2.3+ | 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 ff12a51ee6..38a1dc2ba5 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 @@ -37,7 +37,7 @@ public class State { public void setParentSpan(final Span parentSpan) { final boolean result = parentSpanRef.compareAndSet(null, parentSpan); - if (!result && parentSpanRef.get() != parentSpan) { + if (!result) { log.debug( "Failed to set parent span because another parent span is already set {}: new: {}, old: {}", this, 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 f9ec93ef2b..f6c0889fb7 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 @@ -47,6 +47,7 @@ import lombok.extern.slf4j.Slf4j; @Slf4j @ToString(includeFieldNames = true) public class Config { + private static final MethodHandles.Lookup PUBLIC_LOOKUP = MethodHandles.publicLookup(); /** Config keys below */ private static final String PREFIX = "ota."; @@ -404,7 +405,7 @@ public class Config { } try { return (T) - MethodHandles.publicLookup() + PUBLIC_LOOKUP .findStatic(tClass, "valueOf", MethodType.methodType(tClass, String.class)) .invoke(value); } catch (final NumberFormatException e) { diff --git a/instrumentation/dropwizard-testing/src/test/groovy/DropwizardAsyncTest.groovy b/instrumentation/dropwizard-testing/src/test/groovy/DropwizardAsyncTest.groovy index e9f98e4a10..6b854bfd62 100644 --- a/instrumentation/dropwizard-testing/src/test/groovy/DropwizardAsyncTest.groovy +++ b/instrumentation/dropwizard-testing/src/test/groovy/DropwizardAsyncTest.groovy @@ -71,9 +71,11 @@ class DropwizardAsyncTest extends DropwizardTest { @GET @Path("query") - Response query_param(@QueryParam("some") String param) { - controller(QUERY_PARAM) { - Response.status(QUERY_PARAM.status).entity("some=$param".toString()).build() + Response query_param(@QueryParam("some") String param, @Suspended final AsyncResponse asyncResponse) { + executor.execute { + controller(QUERY_PARAM) { + asyncResponse.resume(Response.status(QUERY_PARAM.status).entity("some=$param".toString()).build()) + } } } diff --git a/instrumentation/java-concurrent/src/main/java/io/opentelemetry/auto/instrumentation/javaconcurrent/JavaExecutorInstrumentation.java b/instrumentation/java-concurrent/src/main/java/io/opentelemetry/auto/instrumentation/javaconcurrent/JavaExecutorInstrumentation.java index 82026b54d2..f577d91028 100644 --- a/instrumentation/java-concurrent/src/main/java/io/opentelemetry/auto/instrumentation/javaconcurrent/JavaExecutorInstrumentation.java +++ b/instrumentation/java-concurrent/src/main/java/io/opentelemetry/auto/instrumentation/javaconcurrent/JavaExecutorInstrumentation.java @@ -19,6 +19,7 @@ import static io.opentelemetry.auto.bootstrap.instrumentation.java.concurrent.Ad import static net.bytebuddy.matcher.ElementMatchers.nameMatches; import static net.bytebuddy.matcher.ElementMatchers.named; import static net.bytebuddy.matcher.ElementMatchers.takesArgument; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; import com.google.auto.service.AutoService; import io.opentelemetry.auto.bootstrap.ContextStore; @@ -59,7 +60,7 @@ public final class JavaExecutorInstrumentation extends AbstractExecutorInstrumen public Map, String> transformers() { final Map, String> transformers = new HashMap<>(); transformers.put( - named("execute").and(takesArgument(0, Runnable.class)), + named("execute").and(takesArgument(0, Runnable.class)).and(takesArguments(1)), JavaExecutorInstrumentation.class.getName() + "$SetExecuteRunnableStateAdvice"); transformers.put( named("execute").and(takesArgument(0, ForkJoinTask.class)), 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 f93bd9c1cd..7ea0707dc6 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 @@ -29,6 +29,7 @@ import static net.bytebuddy.matcher.ElementMatchers.named; import com.google.auto.service.AutoService; import io.opentelemetry.auto.bootstrap.CallDepthThreadLocalMap; +import io.opentelemetry.auto.bootstrap.ContextStore; import io.opentelemetry.auto.bootstrap.InstrumentationContext; import io.opentelemetry.auto.instrumentation.api.SpanWithScope; import io.opentelemetry.auto.tooling.Instrumenter; @@ -99,7 +100,28 @@ public final class JaxRsAnnotationsInstrumentation extends Instrumenter.Default @Advice.OnMethodEnter(suppress = Throwable.class) public static SpanWithScope nameSpan( - @Advice.This final Object target, @Advice.Origin final Method method) { + @Advice.This final Object target, + @Advice.Origin final Method method, + @Advice.AllArguments final Object[] args, + @Advice.Local("asyncResponse") AsyncResponse asyncResponse) { + ContextStore contextStore = null; + for (final Object arg : args) { + if (arg instanceof AsyncResponse) { + asyncResponse = (AsyncResponse) arg; + contextStore = InstrumentationContext.get(AsyncResponse.class, Span.class); + if (contextStore.get(asyncResponse) != null) { + /** + * We are probably in a recursive call and don't want to start a new span because it + * would replace the existing span in the asyncResponse and cause it to never finish. We + * could work around this by using a list instead, but we likely don't want the extra + * span anyway. + */ + return null; + } + break; + } + } + if (CallDepthThreadLocalMap.incrementCallDepth(Path.class) > 0) { return null; } @@ -111,6 +133,10 @@ public final class JaxRsAnnotationsInstrumentation extends Instrumenter.Default DECORATE.onJaxRsSpan(span, parent, target.getClass(), method); DECORATE.afterStart(span); + if (contextStore != null && asyncResponse != null) { + contextStore.put(asyncResponse, span); + } + return new SpanWithScope(span, currentContextWith(span)); } @@ -118,7 +144,7 @@ public final class JaxRsAnnotationsInstrumentation extends Instrumenter.Default public static void stopSpan( @Advice.Enter final SpanWithScope spanWithScope, @Advice.Thrown final Throwable throwable, - @Advice.AllArguments final Object[] args) { + @Advice.Local("asyncResponse") final AsyncResponse asyncResponse) { if (spanWithScope == null) { return; } @@ -133,16 +159,11 @@ public final class JaxRsAnnotationsInstrumentation extends Instrumenter.Default return; } - AsyncResponse asyncResponse = null; - for (final Object arg : args) { - if (arg instanceof AsyncResponse) { - asyncResponse = (AsyncResponse) arg; - break; - } + if (asyncResponse != null && !asyncResponse.isSuspended()) { + // Clear span from the asyncResponse. Logically this should never happen. Added to be safe. + InstrumentationContext.get(AsyncResponse.class, Span.class).put(asyncResponse, null); } - if (asyncResponse != null && asyncResponse.isSuspended()) { - InstrumentationContext.get(AsyncResponse.class, Span.class).put(asyncResponse, span); - } else { + if (asyncResponse == null || !asyncResponse.isSuspended()) { DECORATE.beforeFinish(span); span.end(); } diff --git a/instrumentation/jdbc/src/main/java/io/opentelemetry/auto/instrumentation/jdbc/JDBCDecorator.java b/instrumentation/jdbc/src/main/java/io/opentelemetry/auto/instrumentation/jdbc/JDBCDecorator.java index 56c2574537..16610c1e18 100644 --- a/instrumentation/jdbc/src/main/java/io/opentelemetry/auto/instrumentation/jdbc/JDBCDecorator.java +++ b/instrumentation/jdbc/src/main/java/io/opentelemetry/auto/instrumentation/jdbc/JDBCDecorator.java @@ -76,7 +76,7 @@ public class JDBCDecorator extends DatabaseClientDecorator { if (url != null) { try { dbInfo = JDBCConnectionUrlParser.parse(url, connection.getClientInfo()); - } catch (final Exception ex) { + } catch (final Throwable ex) { // getClientInfo is likely not allowed. dbInfo = JDBCConnectionUrlParser.parse(url, null); } diff --git a/instrumentation/jdbc/src/test/groovy/test/TestConnection.groovy b/instrumentation/jdbc/src/test/groovy/test/TestConnection.groovy index 4ca9b9d3bb..a9d93f29c4 100644 --- a/instrumentation/jdbc/src/test/groovy/test/TestConnection.groovy +++ b/instrumentation/jdbc/src/test/groovy/test/TestConnection.groovy @@ -266,7 +266,7 @@ class TestConnection implements Connection { @Override Properties getClientInfo() throws SQLException { - throw new UnsupportedOperationException("Test 123") + throw new Throwable("Test 123") } @Override diff --git a/instrumentation/lettuce/lettuce-4.0/lettuce-4.0.gradle b/instrumentation/lettuce/lettuce-4.0/lettuce-4.0.gradle new file mode 100644 index 0000000000..b4cf447eac --- /dev/null +++ b/instrumentation/lettuce/lettuce-4.0/lettuce-4.0.gradle @@ -0,0 +1,32 @@ +// Set properties before any plugins get loaded +ext { + minJavaVersionForTests = JavaVersion.VERSION_1_8 +} + +apply from: "${rootDir}/gradle/instrumentation.gradle" +apply plugin: 'org.unbroken-dome.test-sets' + +muzzle { + pass { + group = "biz.paluch.redis" + module = "lettuce" + versions = "[4.0.Final,)" + assertInverse = true + } +} + +testSets { + latestDepTest { + dirName = 'test' + } +} + +dependencies { + compileOnly group: 'biz.paluch.redis', name: 'lettuce', version: '4.0.Final' + main_java8CompileOnly group: 'biz.paluch.redis', name: 'lettuce', version: '4.0.Final' + + testCompile group: 'com.github.kstyrc', name: 'embedded-redis', version: '0.6' + testCompile group: 'biz.paluch.redis', name: 'lettuce', version: '4.0.Final' + + latestDepTestCompile group: 'biz.paluch.redis', name: 'lettuce', version: '4.+' +} 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 new file mode 100644 index 0000000000..83e066f53a --- /dev/null +++ b/instrumentation/lettuce/lettuce-4.0/src/main/java/io/opentelemetry/auto/instrumentation/lettuce/v4_0/LettuceAsyncCommandsInstrumentation.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.lettuce.v4_0; + +import static java.util.Collections.singletonMap; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +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.Map; +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +@AutoService(Instrumenter.class) +public class LettuceAsyncCommandsInstrumentation extends Instrumenter.Default { + + public LettuceAsyncCommandsInstrumentation() { + super("lettuce", "lettuce-4-async"); + } + + @Override + public ElementMatcher typeMatcher() { + return named("com.lambdaworks.redis.AbstractRedisAsyncCommands"); + } + + @Override + public String[] helperClassNames() { + return new String[] { + packageName + ".LettuceClientDecorator", packageName + ".InstrumentationPoints" + }; + } + + @Override + public Map, String> transformers() { + return singletonMap( + isMethod() + .and(named("dispatch")) + .and(takesArgument(0, named("com.lambdaworks.redis.protocol.RedisCommand"))), + // Cannot reference class directly here because it would lead to class load failure on Java7 + packageName + ".LettuceAsyncCommandsAdvice"); + } +} 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 new file mode 100644 index 0000000000..1d6cd36a06 --- /dev/null +++ b/instrumentation/lettuce/lettuce-4.0/src/main/java/io/opentelemetry/auto/instrumentation/lettuce/v4_0/LettuceClientInstrumentation.java @@ -0,0 +1,55 @@ +/* + * 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.lettuce.v4_0; + +import static java.util.Collections.singletonMap; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.named; + +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; + +@AutoService(Instrumenter.class) +public final class LettuceClientInstrumentation extends Instrumenter.Default { + + public LettuceClientInstrumentation() { + super("lettuce"); + } + + @Override + public ElementMatcher typeMatcher() { + return named("com.lambdaworks.redis.RedisClient"); + } + + @Override + public String[] helperClassNames() { + return new String[] { + packageName + ".LettuceClientDecorator", packageName + ".InstrumentationPoints" + }; + } + + @Override + public Map, String> transformers() { + return singletonMap( + isMethod().and(named("connectStandalone")), + // Cannot reference class directly here because it would lead to class load failure on Java7 + packageName + ".RedisConnectionAdvice"); + } +} 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 new file mode 100644 index 0000000000..24edf3a1bf --- /dev/null +++ b/instrumentation/lettuce/lettuce-4.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/v4_0/InstrumentationPoints.java @@ -0,0 +1,113 @@ +/* + * 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.lettuce.v4_0; + +import static com.lambdaworks.redis.protocol.CommandKeyword.SEGFAULT; +import static com.lambdaworks.redis.protocol.CommandType.DEBUG; +import static com.lambdaworks.redis.protocol.CommandType.SHUTDOWN; +import static io.opentelemetry.trace.Span.Kind.CLIENT; +import static io.opentelemetry.trace.TracingContextUtils.currentContextWith; + +import com.lambdaworks.redis.RedisURI; +import com.lambdaworks.redis.protocol.AsyncCommand; +import com.lambdaworks.redis.protocol.CommandType; +import com.lambdaworks.redis.protocol.ProtocolKeyword; +import com.lambdaworks.redis.protocol.RedisCommand; +import io.opentelemetry.auto.instrumentation.api.SpanWithScope; +import io.opentelemetry.trace.Span; +import java.util.EnumSet; +import java.util.Set; +import java.util.concurrent.CancellationException; + +public final class InstrumentationPoints { + + private static final Set NON_INSTRUMENTING_COMMANDS = EnumSet.of(SHUTDOWN, DEBUG); + + public static SpanWithScope beforeCommand(final RedisCommand command) { + final String spanName = command == null ? "Redis Command" : command.getType().name(); + final Span span = + LettuceClientDecorator.TRACER.spanBuilder(spanName).setSpanKind(CLIENT).startSpan(); + LettuceClientDecorator.DECORATE.afterStart(span); + return new SpanWithScope(span, currentContextWith(span)); + } + + public static void afterCommand( + final RedisCommand command, + final SpanWithScope spanWithScope, + final Throwable throwable, + final AsyncCommand asyncCommand) { + final Span span = spanWithScope.getSpan(); + if (throwable != null) { + LettuceClientDecorator.DECORATE.onError(span, throwable); + LettuceClientDecorator.DECORATE.beforeFinish(span); + span.end(); + } else if (finishSpanEarly(command)) { + span.end(); + } else { + asyncCommand.handleAsync( + (value, ex) -> { + if (ex instanceof CancellationException) { + span.setAttribute("db.command.cancelled", true); + } else { + LettuceClientDecorator.DECORATE.onError(span, ex); + } + LettuceClientDecorator.DECORATE.beforeFinish(span); + span.end(); + return null; + }); + } + spanWithScope.closeScope(); + } + + public static SpanWithScope beforeConnect(final RedisURI redisURI) { + final Span span = + LettuceClientDecorator.TRACER.spanBuilder("CONNECT").setSpanKind(CLIENT).startSpan(); + LettuceClientDecorator.DECORATE.afterStart(span); + LettuceClientDecorator.DECORATE.onConnection(span, redisURI); + return new SpanWithScope(span, currentContextWith(span)); + } + + public static void afterConnect(final SpanWithScope spanWithScope, final Throwable throwable) { + final Span span = spanWithScope.getSpan(); + if (throwable != null) { + LettuceClientDecorator.DECORATE.onError(span, throwable); + LettuceClientDecorator.DECORATE.beforeFinish(span); + } + span.end(); + spanWithScope.closeScope(); + } + + /** + * Determines whether a redis command should finish its relevant span early (as soon as tags are + * added and the command is executed) because these commands have no return values/call backs, so + * 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) + */ + public static boolean finishSpanEarly(final RedisCommand command) { + final ProtocolKeyword keyword = command.getType(); + return isNonInstrumentingCommand(keyword) || isNonInstrumentingKeyword(keyword); + } + + private static boolean isNonInstrumentingCommand(final ProtocolKeyword keyword) { + return keyword instanceof CommandType && NON_INSTRUMENTING_COMMANDS.contains(keyword); + } + + private static boolean isNonInstrumentingKeyword(final ProtocolKeyword keyword) { + return keyword == SEGFAULT; + } +} diff --git a/instrumentation/lettuce/lettuce-4.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/v4_0/LettuceAsyncCommandsAdvice.java b/instrumentation/lettuce/lettuce-4.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/v4_0/LettuceAsyncCommandsAdvice.java new file mode 100644 index 0000000000..10722bbe6b --- /dev/null +++ b/instrumentation/lettuce/lettuce-4.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/v4_0/LettuceAsyncCommandsAdvice.java @@ -0,0 +1,38 @@ +/* + * 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.lettuce.v4_0; + +import com.lambdaworks.redis.protocol.AsyncCommand; +import com.lambdaworks.redis.protocol.RedisCommand; +import io.opentelemetry.auto.instrumentation.api.SpanWithScope; +import net.bytebuddy.asm.Advice; + +public class LettuceAsyncCommandsAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static SpanWithScope onEnter(@Advice.Argument(0) final RedisCommand command) { + return InstrumentationPoints.beforeCommand(command); + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void onExit( + @Advice.Argument(0) final RedisCommand command, + @Advice.Enter final SpanWithScope spanWithScope, + @Advice.Thrown final Throwable throwable, + @Advice.Return final AsyncCommand asyncCommand) { + InstrumentationPoints.afterCommand(command, spanWithScope, throwable, asyncCommand); + } +} diff --git a/instrumentation/lettuce/lettuce-4.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/v4_0/LettuceClientDecorator.java b/instrumentation/lettuce/lettuce-4.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/v4_0/LettuceClientDecorator.java new file mode 100644 index 0000000000..d0361a3613 --- /dev/null +++ b/instrumentation/lettuce/lettuce-4.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/v4_0/LettuceClientDecorator.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.lettuce.v4_0; + +import com.lambdaworks.redis.RedisURI; +import io.opentelemetry.OpenTelemetry; +import io.opentelemetry.auto.bootstrap.instrumentation.decorator.DatabaseClientDecorator; +import io.opentelemetry.auto.instrumentation.api.MoreTags; +import io.opentelemetry.trace.Span; +import io.opentelemetry.trace.Tracer; + +public class LettuceClientDecorator extends DatabaseClientDecorator { + + public static final LettuceClientDecorator DECORATE = new LettuceClientDecorator(); + + public static final Tracer TRACER = + OpenTelemetry.getTracerProvider().get("io.opentelemetry.auto.lettuce-4.0"); + + @Override + protected String dbType() { + return "redis"; + } + + @Override + protected String dbUser(final RedisURI connection) { + return null; + } + + @Override + protected String dbInstance(final RedisURI connection) { + return null; + } + + @Override + public Span onConnection(final Span span, final RedisURI connection) { + if (connection != null) { + span.setAttribute(MoreTags.NET_PEER_NAME, connection.getHost()); + span.setAttribute(MoreTags.NET_PEER_PORT, connection.getPort()); + + span.setAttribute("db.redis.dbIndex", connection.getDatabase()); + } + return super.onConnection(span, connection); + } +} diff --git a/instrumentation/lettuce/lettuce-4.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/v4_0/RedisConnectionAdvice.java b/instrumentation/lettuce/lettuce-4.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/v4_0/RedisConnectionAdvice.java new file mode 100644 index 0000000000..41ab5737f9 --- /dev/null +++ b/instrumentation/lettuce/lettuce-4.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/v4_0/RedisConnectionAdvice.java @@ -0,0 +1,34 @@ +/* + * 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.lettuce.v4_0; + +import com.lambdaworks.redis.RedisURI; +import io.opentelemetry.auto.instrumentation.api.SpanWithScope; +import net.bytebuddy.asm.Advice; + +public class RedisConnectionAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static SpanWithScope onEnter(@Advice.Argument(1) final RedisURI redisURI) { + return InstrumentationPoints.beforeConnect(redisURI); + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void onExit( + @Advice.Enter final SpanWithScope scope, @Advice.Thrown final Throwable throwable) { + InstrumentationPoints.afterConnect(scope, throwable); + } +} diff --git a/instrumentation/lettuce/lettuce-4.0/src/test/groovy/LettuceAsyncClientTest.groovy b/instrumentation/lettuce/lettuce-4.0/src/test/groovy/LettuceAsyncClientTest.groovy new file mode 100644 index 0000000000..95cd67e266 --- /dev/null +++ b/instrumentation/lettuce/lettuce-4.0/src/test/groovy/LettuceAsyncClientTest.groovy @@ -0,0 +1,483 @@ +/* + * 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.lambdaworks.redis.ClientOptions +import com.lambdaworks.redis.RedisClient +import com.lambdaworks.redis.RedisConnectionException +import com.lambdaworks.redis.RedisFuture +import com.lambdaworks.redis.RedisURI +import com.lambdaworks.redis.api.StatefulConnection +import com.lambdaworks.redis.api.async.RedisAsyncCommands +import com.lambdaworks.redis.api.sync.RedisCommands +import com.lambdaworks.redis.codec.Utf8StringCodec +import com.lambdaworks.redis.protocol.AsyncCommand +import io.opentelemetry.auto.instrumentation.api.MoreTags +import io.opentelemetry.auto.instrumentation.api.Tags +import io.opentelemetry.auto.test.AgentTestRunner +import io.opentelemetry.auto.test.utils.PortUtils +import redis.embedded.RedisServer +import spock.lang.Shared +import spock.util.concurrent.AsyncConditions + +import java.util.concurrent.CancellationException +import java.util.concurrent.TimeUnit +import java.util.function.BiConsumer +import java.util.function.BiFunction +import java.util.function.Consumer +import java.util.function.Function + +import static io.opentelemetry.trace.Span.Kind.CLIENT + +class LettuceAsyncClientTest extends AgentTestRunner { + public static final String HOST = "127.0.0.1" + public static final int DB_INDEX = 0 + // Disable autoreconnect so we do not get stray traces popping up on server shutdown + public static final ClientOptions CLIENT_OPTIONS = new ClientOptions.Builder().autoReconnect(false).build() + + @Shared + int port + @Shared + int incorrectPort + @Shared + String dbAddr + @Shared + String dbAddrNonExistent + @Shared + String dbUriNonExistent + @Shared + String embeddedDbUri + + @Shared + RedisServer redisServer + + @Shared + Map testHashMap = [ + firstname: "John", + lastname : "Doe", + age : "53" + ] + + RedisClient redisClient + StatefulConnection connection + RedisAsyncCommands asyncCommands + RedisCommands syncCommands + + def setupSpec() { + port = PortUtils.randomOpenPort() + incorrectPort = PortUtils.randomOpenPort() + dbAddr = HOST + ":" + port + "/" + DB_INDEX + dbAddrNonExistent = HOST + ":" + incorrectPort + "/" + DB_INDEX + dbUriNonExistent = "redis://" + dbAddrNonExistent + embeddedDbUri = "redis://" + dbAddr + + redisServer = RedisServer.builder() + // bind to localhost to avoid firewall popup + .setting("bind " + HOST) + // set max memory to avoid problems in CI + .setting("maxmemory 128M") + .port(port).build() + } + + def setup() { + redisClient = RedisClient.create(embeddedDbUri) + + println "Using redis: $redisServer.args" + redisServer.start() + redisClient.setOptions(CLIENT_OPTIONS) + + connection = redisClient.connect() + asyncCommands = connection.async() + syncCommands = connection.sync() + + syncCommands.set("TESTKEY", "TESTVAL") + + // 1 set + 1 connect trace + TEST_WRITER.waitForTraces(2) + TEST_WRITER.clear() + } + + def cleanup() { + connection.close() + redisServer.stop() + } + + def "connect using get on ConnectionFuture"() { + setup: + RedisClient testConnectionClient = RedisClient.create(embeddedDbUri) + testConnectionClient.setOptions(CLIENT_OPTIONS) + + when: + StatefulConnection connection = testConnectionClient.connect(new Utf8StringCodec(), + new RedisURI(HOST, port, 3, TimeUnit.SECONDS)) + + then: + connection != null + assertTraces(1) { + trace(0, 1) { + span(0) { + operationName "CONNECT" + spanKind CLIENT + errored false + tags { + "$MoreTags.NET_PEER_NAME" HOST + "$MoreTags.NET_PEER_PORT" port + "$Tags.DB_TYPE" "redis" + "db.redis.dbIndex" 0 + } + } + } + } + + cleanup: + connection.close() + } + + def "connect exception inside the connection future"() { + setup: + RedisClient testConnectionClient = RedisClient.create(dbUriNonExistent) + testConnectionClient.setOptions(CLIENT_OPTIONS) + + when: + StatefulConnection connection = testConnectionClient.connect(new Utf8StringCodec(), + new RedisURI(HOST, incorrectPort, 3, TimeUnit.SECONDS)) + + then: + connection == null + thrown RedisConnectionException + assertTraces(1) { + trace(0, 1) { + span(0) { + operationName "CONNECT" + spanKind CLIENT + errored true + tags { + "$MoreTags.NET_PEER_NAME" HOST + "$MoreTags.NET_PEER_PORT" incorrectPort + "$Tags.DB_TYPE" "redis" + "db.redis.dbIndex" 0 + errorTags RedisConnectionException, String + } + } + } + } + } + + def "set command using Future get with timeout"() { + setup: + RedisFuture redisFuture = asyncCommands.set("TESTSETKEY", "TESTSETVAL") + String res = redisFuture.get(3, TimeUnit.SECONDS) + + expect: + res == "OK" + assertTraces(1) { + trace(0, 1) { + span(0) { + operationName "SET" + spanKind CLIENT + errored false + tags { + "$Tags.DB_TYPE" "redis" + } + } + } + } + } + + def "get command chained with thenAccept"() { + setup: + def conds = new AsyncConditions() + Consumer consumer = new Consumer() { + @Override + void accept(String res) { + conds.evaluate { + assert res == "TESTVAL" + } + } + } + + when: + RedisFuture redisFuture = asyncCommands.get("TESTKEY") + redisFuture.thenAccept(consumer) + + then: + conds.await() + assertTraces(1) { + trace(0, 1) { + span(0) { + operationName "GET" + spanKind CLIENT + errored false + tags { + "$Tags.DB_TYPE" "redis" + } + } + } + } + } + + // to make sure instrumentation's chained completion stages won't interfere with user's, while still + // recording metrics + def "get non existent key command with handleAsync and chained with thenApply"() { + setup: + def conds = new AsyncConditions() + final String successStr = "KEY MISSING" + BiFunction firstStage = new BiFunction() { + @Override + String apply(String res, Throwable throwable) { + conds.evaluate { + assert res == null + assert throwable == null + } + return (res == null ? successStr : res) + } + } + Function secondStage = new Function() { + @Override + Object apply(String input) { + conds.evaluate { + assert input == successStr + } + return null + } + } + + when: + RedisFuture redisFuture = asyncCommands.get("NON_EXISTENT_KEY") + redisFuture.handleAsync(firstStage).thenApply(secondStage) + + then: + conds.await() + assertTraces(1) { + trace(0, 1) { + span(0) { + operationName "GET" + spanKind CLIENT + errored false + tags { + "$Tags.DB_TYPE" "redis" + } + } + } + } + } + + def "command with no arguments using a biconsumer"() { + setup: + def conds = new AsyncConditions() + BiConsumer biConsumer = new BiConsumer() { + @Override + void accept(String keyRetrieved, Throwable throwable) { + conds.evaluate { + assert keyRetrieved != null + } + } + } + + when: + RedisFuture redisFuture = asyncCommands.randomkey() + redisFuture.whenCompleteAsync(biConsumer) + + then: + conds.await() + assertTraces(1) { + trace(0, 1) { + span(0) { + operationName "RANDOMKEY" + spanKind CLIENT + errored false + tags { + "$Tags.DB_TYPE" "redis" + } + } + } + } + } + + def "hash set and then nest apply to hash getall"() { + setup: + def conds = new AsyncConditions() + + when: + RedisFuture hmsetFuture = asyncCommands.hmset("TESTHM", testHashMap) + hmsetFuture.thenApplyAsync(new Function() { + @Override + Object apply(String setResult) { + TEST_WRITER.waitForTraces(1) // Wait for 'hmset' trace to get written + conds.evaluate { + assert setResult == "OK" + } + RedisFuture> hmGetAllFuture = asyncCommands.hgetall("TESTHM") + hmGetAllFuture.exceptionally(new Function>() { + @Override + Map apply(Throwable throwable) { + println("unexpected:" + throwable.toString()) + throwable.printStackTrace() + assert false + return null + } + }) + hmGetAllFuture.thenAccept(new Consumer>() { + @Override + void accept(Map hmGetAllResult) { + conds.evaluate { + assert testHashMap == hmGetAllResult + } + } + }) + return null + } + }) + + then: + conds.await() + assertTraces(2) { + trace(0, 1) { + span(0) { + operationName "HMSET" + spanKind CLIENT + errored false + tags { + "$Tags.DB_TYPE" "redis" + } + } + } + trace(1, 1) { + span(0) { + operationName "HGETALL" + spanKind CLIENT + errored false + tags { + "$Tags.DB_TYPE" "redis" + } + } + } + } + } + + def "command completes exceptionally"() { + setup: + // turn off auto flush to complete the command exceptionally manually + asyncCommands.setAutoFlushCommands(false) + def conds = new AsyncConditions() + RedisFuture redisFuture = asyncCommands.del("key1", "key2") + boolean completedExceptionally = ((AsyncCommand) redisFuture).completeExceptionally(new IllegalStateException("TestException")) + redisFuture.exceptionally({ + throwable -> + conds.evaluate { + assert throwable != null + assert throwable instanceof IllegalStateException + assert throwable.getMessage() == "TestException" + } + throw throwable + }) + + when: + // now flush and execute the command + asyncCommands.flushCommands() + redisFuture.get() + + then: + conds.await() + completedExceptionally == true + thrown Exception + assertTraces(1) { + trace(0, 1) { + span(0) { + operationName "DEL" + spanKind CLIENT + errored true + tags { + "$Tags.DB_TYPE" "redis" + errorTags(IllegalStateException, "TestException") + } + } + } + } + } + + def "cancel command before it finishes"() { + setup: + asyncCommands.setAutoFlushCommands(false) + def conds = new AsyncConditions() + RedisFuture redisFuture = asyncCommands.sadd("SKEY", "1", "2") + redisFuture.whenCompleteAsync({ + res, throwable -> + conds.evaluate { + assert throwable != null + assert throwable instanceof CancellationException + } + }) + + when: + boolean cancelSuccess = redisFuture.cancel(true) + asyncCommands.flushCommands() + + then: + conds.await() + cancelSuccess == true + assertTraces(1) { + trace(0, 1) { + span(0) { + operationName "SADD" + spanKind CLIENT + errored false + tags { + "$Tags.DB_TYPE" "redis" + "db.command.cancelled" true + } + } + } + } + } + + def "debug segfault command (returns void) with no argument should produce span"() { + setup: + asyncCommands.debugSegfault() + + expect: + assertTraces(1) { + trace(0, 1) { + span(0) { + operationName "DEBUG" + spanKind CLIENT + errored false + tags { + "$Tags.DB_TYPE" "redis" + } + } + } + } + } + + + def "shutdown command (returns void) should produce a span"() { + setup: + asyncCommands.shutdown(false) + + expect: + assertTraces(1) { + trace(0, 1) { + span(0) { + operationName "SHUTDOWN" + spanKind CLIENT + errored false + tags { + "$Tags.DB_TYPE" "redis" + } + } + } + } + } +} diff --git a/instrumentation/lettuce/lettuce-4.0/src/test/groovy/LettuceSyncClientTest.groovy b/instrumentation/lettuce/lettuce-4.0/src/test/groovy/LettuceSyncClientTest.groovy new file mode 100644 index 0000000000..6f7d5d4252 --- /dev/null +++ b/instrumentation/lettuce/lettuce-4.0/src/test/groovy/LettuceSyncClientTest.groovy @@ -0,0 +1,333 @@ +/* + * 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.lambdaworks.redis.ClientOptions +import com.lambdaworks.redis.RedisClient +import com.lambdaworks.redis.RedisConnectionException +import com.lambdaworks.redis.api.StatefulConnection +import com.lambdaworks.redis.api.sync.RedisCommands +import io.opentelemetry.auto.instrumentation.api.MoreTags +import io.opentelemetry.auto.instrumentation.api.Tags +import io.opentelemetry.auto.test.AgentTestRunner +import io.opentelemetry.auto.test.utils.PortUtils +import redis.embedded.RedisServer +import spock.lang.Shared + +import static io.opentelemetry.trace.Span.Kind.CLIENT + +class LettuceSyncClientTest extends AgentTestRunner { + public static final String HOST = "127.0.0.1" + public static final int DB_INDEX = 0 + // Disable autoreconnect so we do not get stray traces popping up on server shutdown + public static final ClientOptions CLIENT_OPTIONS = new ClientOptions.Builder().autoReconnect(false).build() + + @Shared + int port + @Shared + int incorrectPort + @Shared + String dbAddr + @Shared + String dbAddrNonExistent + @Shared + String dbUriNonExistent + @Shared + String embeddedDbUri + + @Shared + RedisServer redisServer + + @Shared + Map testHashMap = [ + firstname: "John", + lastname : "Doe", + age : "53" + ] + + RedisClient redisClient + StatefulConnection connection + RedisCommands syncCommands + + def setupSpec() { + port = PortUtils.randomOpenPort() + incorrectPort = PortUtils.randomOpenPort() + dbAddr = HOST + ":" + port + "/" + DB_INDEX + dbAddrNonExistent = HOST + ":" + incorrectPort + "/" + DB_INDEX + dbUriNonExistent = "redis://" + dbAddrNonExistent + embeddedDbUri = "redis://" + dbAddr + + redisServer = RedisServer.builder() + // bind to localhost to avoid firewall popup + .setting("bind " + HOST) + // set max memory to avoid problems in CI + .setting("maxmemory 128M") + .port(port).build() + } + + def setup() { + redisClient = RedisClient.create(embeddedDbUri) + + redisServer.start() + connection = redisClient.connect() + syncCommands = connection.sync() + + syncCommands.set("TESTKEY", "TESTVAL") + syncCommands.hmset("TESTHM", testHashMap) + + // 2 sets + 1 connect trace + TEST_WRITER.waitForTraces(3) + TEST_WRITER.clear() + } + + def cleanup() { + connection.close() + redisServer.stop() + } + + def "connect"() { + setup: + RedisClient testConnectionClient = RedisClient.create(embeddedDbUri) + testConnectionClient.setOptions(CLIENT_OPTIONS) + + when: + StatefulConnection connection = testConnectionClient.connect() + + then: + assertTraces(1) { + trace(0, 1) { + span(0) { + operationName "CONNECT" + spanKind CLIENT + errored false + tags { + "$MoreTags.NET_PEER_NAME" HOST + "$MoreTags.NET_PEER_PORT" port + "$Tags.DB_TYPE" "redis" + "db.redis.dbIndex" 0 + } + } + } + } + + cleanup: + connection.close() + } + + def "connect exception"() { + setup: + RedisClient testConnectionClient = RedisClient.create(dbUriNonExistent) + testConnectionClient.setOptions(CLIENT_OPTIONS) + + when: + testConnectionClient.connect() + + then: + thrown RedisConnectionException + assertTraces(1) { + trace(0, 1) { + span(0) { + operationName "CONNECT" + spanKind CLIENT + errored true + tags { + "$MoreTags.NET_PEER_NAME" HOST + "$MoreTags.NET_PEER_PORT" incorrectPort + "$Tags.DB_TYPE" "redis" + "db.redis.dbIndex" 0 + errorTags RedisConnectionException, String + } + } + } + } + } + + def "set command"() { + setup: + String res = syncCommands.set("TESTSETKEY", "TESTSETVAL") + + expect: + res == "OK" + assertTraces(1) { + trace(0, 1) { + span(0) { + operationName "SET" + spanKind CLIENT + errored false + tags { + "$Tags.DB_TYPE" "redis" + } + } + } + } + } + + def "get command"() { + setup: + String res = syncCommands.get("TESTKEY") + + expect: + res == "TESTVAL" + assertTraces(1) { + trace(0, 1) { + span(0) { + operationName "GET" + spanKind CLIENT + errored false + tags { + "$Tags.DB_TYPE" "redis" + } + } + } + } + } + + def "get non existent key command"() { + setup: + String res = syncCommands.get("NON_EXISTENT_KEY") + + expect: + res == null + assertTraces(1) { + trace(0, 1) { + span(0) { + operationName "GET" + spanKind CLIENT + errored false + tags { + "$Tags.DB_TYPE" "redis" + } + } + } + } + } + + def "command with no arguments"() { + setup: + def keyRetrieved = syncCommands.randomkey() + + expect: + keyRetrieved != null + assertTraces(1) { + trace(0, 1) { + span(0) { + operationName "RANDOMKEY" + spanKind CLIENT + errored false + tags { + "$Tags.DB_TYPE" "redis" + } + } + } + } + } + + def "list command"() { + setup: + long res = syncCommands.lpush("TESTLIST", "TESTLIST ELEMENT") + + expect: + res == 1 + assertTraces(1) { + trace(0, 1) { + span(0) { + operationName "LPUSH" + spanKind CLIENT + errored false + tags { + "$Tags.DB_TYPE" "redis" + } + } + } + } + } + + def "hash set command"() { + setup: + def res = syncCommands.hmset("user", testHashMap) + + expect: + res == "OK" + assertTraces(1) { + trace(0, 1) { + span(0) { + operationName "HMSET" + spanKind CLIENT + errored false + tags { + "$Tags.DB_TYPE" "redis" + } + } + } + } + } + + def "hash getall command"() { + setup: + Map res = syncCommands.hgetall("TESTHM") + + expect: + res == testHashMap + assertTraces(1) { + trace(0, 1) { + span(0) { + operationName "HGETALL" + spanKind CLIENT + errored false + tags { + "$Tags.DB_TYPE" "redis" + } + } + } + } + } + + def "debug segfault command (returns void) with no argument should produce span"() { + setup: + syncCommands.debugSegfault() + + expect: + assertTraces(1) { + trace(0, 1) { + span(0) { + operationName "DEBUG" + spanKind CLIENT + errored false + tags { + "$Tags.DB_TYPE" "redis" + } + } + } + } + } + + def "shutdown command (returns void) should produce a span"() { + setup: + syncCommands.shutdown(false) + + expect: + assertTraces(1) { + trace(0, 1) { + span(0) { + operationName "SHUTDOWN" + spanKind CLIENT + errored false + tags { + "$Tags.DB_TYPE" "redis" + } + } + } + } + } +} diff --git a/instrumentation/lettuce-5.0/lettuce-5.0.gradle b/instrumentation/lettuce/lettuce-5.0/lettuce-5.0.gradle similarity index 100% rename from instrumentation/lettuce-5.0/lettuce-5.0.gradle rename to instrumentation/lettuce/lettuce-5.0/lettuce-5.0.gradle diff --git a/instrumentation/lettuce-5.0/src/main/java/io/opentelemetry/auto/instrumentation/lettuce/LettuceAsyncCommandsInstrumentation.java b/instrumentation/lettuce/lettuce-5.0/src/main/java/io/opentelemetry/auto/instrumentation/lettuce/v5_0/LettuceAsyncCommandsInstrumentation.java similarity index 97% rename from instrumentation/lettuce-5.0/src/main/java/io/opentelemetry/auto/instrumentation/lettuce/LettuceAsyncCommandsInstrumentation.java rename to instrumentation/lettuce/lettuce-5.0/src/main/java/io/opentelemetry/auto/instrumentation/lettuce/v5_0/LettuceAsyncCommandsInstrumentation.java index 495dad8d6f..0979d53ce7 100644 --- a/instrumentation/lettuce-5.0/src/main/java/io/opentelemetry/auto/instrumentation/lettuce/LettuceAsyncCommandsInstrumentation.java +++ b/instrumentation/lettuce/lettuce-5.0/src/main/java/io/opentelemetry/auto/instrumentation/lettuce/v5_0/LettuceAsyncCommandsInstrumentation.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.opentelemetry.auto.instrumentation.lettuce; +package io.opentelemetry.auto.instrumentation.lettuce.v5_0; import static java.util.Collections.singletonMap; import static net.bytebuddy.matcher.ElementMatchers.isMethod; diff --git a/instrumentation/lettuce-5.0/src/main/java/io/opentelemetry/auto/instrumentation/lettuce/LettuceClientInstrumentation.java b/instrumentation/lettuce/lettuce-5.0/src/main/java/io/opentelemetry/auto/instrumentation/lettuce/v5_0/LettuceClientInstrumentation.java similarity index 97% rename from instrumentation/lettuce-5.0/src/main/java/io/opentelemetry/auto/instrumentation/lettuce/LettuceClientInstrumentation.java rename to instrumentation/lettuce/lettuce-5.0/src/main/java/io/opentelemetry/auto/instrumentation/lettuce/v5_0/LettuceClientInstrumentation.java index 7d2247d347..be04be7e8f 100644 --- a/instrumentation/lettuce-5.0/src/main/java/io/opentelemetry/auto/instrumentation/lettuce/LettuceClientInstrumentation.java +++ b/instrumentation/lettuce/lettuce-5.0/src/main/java/io/opentelemetry/auto/instrumentation/lettuce/v5_0/LettuceClientInstrumentation.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.opentelemetry.auto.instrumentation.lettuce; +package io.opentelemetry.auto.instrumentation.lettuce.v5_0; import static java.util.Collections.singletonMap; import static net.bytebuddy.matcher.ElementMatchers.isMethod; diff --git a/instrumentation/lettuce-5.0/src/main/java/io/opentelemetry/auto/instrumentation/lettuce/LettuceReactiveCommandsInstrumentation.java b/instrumentation/lettuce/lettuce-5.0/src/main/java/io/opentelemetry/auto/instrumentation/lettuce/v5_0/LettuceReactiveCommandsInstrumentation.java similarity index 98% rename from instrumentation/lettuce-5.0/src/main/java/io/opentelemetry/auto/instrumentation/lettuce/LettuceReactiveCommandsInstrumentation.java rename to instrumentation/lettuce/lettuce-5.0/src/main/java/io/opentelemetry/auto/instrumentation/lettuce/v5_0/LettuceReactiveCommandsInstrumentation.java index 1d42ae7a11..d89310818f 100644 --- a/instrumentation/lettuce-5.0/src/main/java/io/opentelemetry/auto/instrumentation/lettuce/LettuceReactiveCommandsInstrumentation.java +++ b/instrumentation/lettuce/lettuce-5.0/src/main/java/io/opentelemetry/auto/instrumentation/lettuce/v5_0/LettuceReactiveCommandsInstrumentation.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.opentelemetry.auto.instrumentation.lettuce; +package io.opentelemetry.auto.instrumentation.lettuce.v5_0; import static net.bytebuddy.matcher.ElementMatchers.isMethod; import static net.bytebuddy.matcher.ElementMatchers.nameEndsWith; diff --git a/instrumentation/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/ConnectionFutureAdvice.java b/instrumentation/lettuce/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/v5_0/ConnectionFutureAdvice.java similarity index 78% rename from instrumentation/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/ConnectionFutureAdvice.java rename to instrumentation/lettuce/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/v5_0/ConnectionFutureAdvice.java index e18b254218..2aa2ab37ee 100644 --- a/instrumentation/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/ConnectionFutureAdvice.java +++ b/instrumentation/lettuce/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/v5_0/ConnectionFutureAdvice.java @@ -13,10 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.opentelemetry.auto.instrumentation.lettuce; +package io.opentelemetry.auto.instrumentation.lettuce.v5_0; -import static io.opentelemetry.auto.instrumentation.lettuce.LettuceClientDecorator.DECORATE; -import static io.opentelemetry.auto.instrumentation.lettuce.LettuceClientDecorator.TRACER; import static io.opentelemetry.trace.Span.Kind.CLIENT; import static io.opentelemetry.trace.TracingContextUtils.currentContextWith; @@ -30,9 +28,10 @@ public class ConnectionFutureAdvice { @Advice.OnMethodEnter(suppress = Throwable.class) public static SpanWithScope onEnter(@Advice.Argument(1) final RedisURI redisURI) { - final Span span = TRACER.spanBuilder("CONNECT").setSpanKind(CLIENT).startSpan(); - DECORATE.afterStart(span); - DECORATE.onConnection(span, redisURI); + final Span span = + LettuceClientDecorator.TRACER.spanBuilder("CONNECT").setSpanKind(CLIENT).startSpan(); + LettuceClientDecorator.DECORATE.afterStart(span); + LettuceClientDecorator.DECORATE.onConnection(span, redisURI); return new SpanWithScope(span, currentContextWith(span)); } @@ -43,8 +42,8 @@ public class ConnectionFutureAdvice { @Advice.Return final ConnectionFuture connectionFuture) { final Span span = spanWithScope.getSpan(); if (throwable != null) { - DECORATE.onError(span, throwable); - DECORATE.beforeFinish(span); + LettuceClientDecorator.DECORATE.onError(span, throwable); + LettuceClientDecorator.DECORATE.beforeFinish(span); span.end(); spanWithScope.closeScope(); return; diff --git a/instrumentation/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/LettuceAsyncBiFunction.java b/instrumentation/lettuce/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/v5_0/LettuceAsyncBiFunction.java similarity index 87% rename from instrumentation/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/LettuceAsyncBiFunction.java rename to instrumentation/lettuce/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/v5_0/LettuceAsyncBiFunction.java index 92bc4e9804..ee499cabd3 100644 --- a/instrumentation/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/LettuceAsyncBiFunction.java +++ b/instrumentation/lettuce/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/v5_0/LettuceAsyncBiFunction.java @@ -13,9 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.opentelemetry.auto.instrumentation.lettuce; - -import static io.opentelemetry.auto.instrumentation.lettuce.LettuceClientDecorator.DECORATE; +package io.opentelemetry.auto.instrumentation.lettuce.v5_0; import io.opentelemetry.trace.Span; import java.util.concurrent.CancellationException; @@ -44,9 +42,9 @@ public class LettuceAsyncBiFunction(span)); diff --git a/instrumentation/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/LettuceClientDecorator.java b/instrumentation/lettuce/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/v5_0/LettuceClientDecorator.java similarity index 96% rename from instrumentation/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/LettuceClientDecorator.java rename to instrumentation/lettuce/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/v5_0/LettuceClientDecorator.java index 1321c6eaa5..2350232dc0 100644 --- a/instrumentation/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/LettuceClientDecorator.java +++ b/instrumentation/lettuce/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/v5_0/LettuceClientDecorator.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.opentelemetry.auto.instrumentation.lettuce; +package io.opentelemetry.auto.instrumentation.lettuce.v5_0; import io.lettuce.core.RedisURI; import io.opentelemetry.OpenTelemetry; diff --git a/instrumentation/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/LettuceInstrumentationUtil.java b/instrumentation/lettuce/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/v5_0/LettuceInstrumentationUtil.java similarity index 97% rename from instrumentation/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/LettuceInstrumentationUtil.java rename to instrumentation/lettuce/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/v5_0/LettuceInstrumentationUtil.java index fd72a1a96a..74bdcd271a 100644 --- a/instrumentation/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/LettuceInstrumentationUtil.java +++ b/instrumentation/lettuce/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/v5_0/LettuceInstrumentationUtil.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.opentelemetry.auto.instrumentation.lettuce; +package io.opentelemetry.auto.instrumentation.lettuce.v5_0; import io.lettuce.core.protocol.RedisCommand; import java.util.Arrays; diff --git a/instrumentation/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/rx/LettuceFluxCreationAdvice.java b/instrumentation/lettuce/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/v5_0/rx/LettuceFluxCreationAdvice.java similarity index 91% rename from instrumentation/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/rx/LettuceFluxCreationAdvice.java rename to instrumentation/lettuce/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/v5_0/rx/LettuceFluxCreationAdvice.java index f2be8d7c5f..6cd60bf31f 100644 --- a/instrumentation/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/rx/LettuceFluxCreationAdvice.java +++ b/instrumentation/lettuce/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/v5_0/rx/LettuceFluxCreationAdvice.java @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.opentelemetry.auto.instrumentation.lettuce.rx; +package io.opentelemetry.auto.instrumentation.lettuce.v5_0.rx; -import static io.opentelemetry.auto.instrumentation.lettuce.LettuceInstrumentationUtil.doFinishSpanEarly; +import static io.opentelemetry.auto.instrumentation.lettuce.v5_0.LettuceInstrumentationUtil.doFinishSpanEarly; import io.lettuce.core.protocol.RedisCommand; import java.util.function.Supplier; diff --git a/instrumentation/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/rx/LettuceFluxTerminationRunnable.java b/instrumentation/lettuce/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/v5_0/rx/LettuceFluxTerminationRunnable.java similarity index 91% rename from instrumentation/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/rx/LettuceFluxTerminationRunnable.java rename to instrumentation/lettuce/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/v5_0/rx/LettuceFluxTerminationRunnable.java index 5565ad11cb..cbe11feb7d 100644 --- a/instrumentation/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/rx/LettuceFluxTerminationRunnable.java +++ b/instrumentation/lettuce/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/v5_0/rx/LettuceFluxTerminationRunnable.java @@ -13,14 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.opentelemetry.auto.instrumentation.lettuce.rx; +package io.opentelemetry.auto.instrumentation.lettuce.v5_0.rx; -import static io.opentelemetry.auto.instrumentation.lettuce.LettuceClientDecorator.DECORATE; -import static io.opentelemetry.auto.instrumentation.lettuce.LettuceClientDecorator.TRACER; +import static io.opentelemetry.auto.instrumentation.lettuce.v5_0.LettuceClientDecorator.DECORATE; +import static io.opentelemetry.auto.instrumentation.lettuce.v5_0.LettuceClientDecorator.TRACER; import static io.opentelemetry.trace.Span.Kind.CLIENT; import io.lettuce.core.protocol.RedisCommand; -import io.opentelemetry.auto.instrumentation.lettuce.LettuceInstrumentationUtil; +import io.opentelemetry.auto.instrumentation.lettuce.v5_0.LettuceInstrumentationUtil; import io.opentelemetry.trace.Span; import java.util.function.Consumer; import org.reactivestreams.Subscription; diff --git a/instrumentation/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/rx/LettuceMonoCreationAdvice.java b/instrumentation/lettuce/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/v5_0/rx/LettuceMonoCreationAdvice.java similarity index 92% rename from instrumentation/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/rx/LettuceMonoCreationAdvice.java rename to instrumentation/lettuce/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/v5_0/rx/LettuceMonoCreationAdvice.java index 9fd3708f16..e76431266f 100644 --- a/instrumentation/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/rx/LettuceMonoCreationAdvice.java +++ b/instrumentation/lettuce/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/v5_0/rx/LettuceMonoCreationAdvice.java @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.opentelemetry.auto.instrumentation.lettuce.rx; +package io.opentelemetry.auto.instrumentation.lettuce.v5_0.rx; import io.lettuce.core.protocol.RedisCommand; -import io.opentelemetry.auto.instrumentation.lettuce.LettuceInstrumentationUtil; +import io.opentelemetry.auto.instrumentation.lettuce.v5_0.LettuceInstrumentationUtil; import java.util.function.Supplier; import net.bytebuddy.asm.Advice; import reactor.core.publisher.Mono; diff --git a/instrumentation/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/rx/LettuceMonoDualConsumer.java b/instrumentation/lettuce/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/v5_0/rx/LettuceMonoDualConsumer.java similarity index 85% rename from instrumentation/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/rx/LettuceMonoDualConsumer.java rename to instrumentation/lettuce/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/v5_0/rx/LettuceMonoDualConsumer.java index ec342876ed..05b25b43b2 100644 --- a/instrumentation/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/rx/LettuceMonoDualConsumer.java +++ b/instrumentation/lettuce/lettuce-5.0/src/main/java8/io/opentelemetry/auto/instrumentation/lettuce/v5_0/rx/LettuceMonoDualConsumer.java @@ -13,14 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.opentelemetry.auto.instrumentation.lettuce.rx; +package io.opentelemetry.auto.instrumentation.lettuce.v5_0.rx; -import static io.opentelemetry.auto.instrumentation.lettuce.LettuceClientDecorator.DECORATE; -import static io.opentelemetry.auto.instrumentation.lettuce.LettuceClientDecorator.TRACER; +import static io.opentelemetry.auto.instrumentation.lettuce.v5_0.LettuceClientDecorator.DECORATE; +import static io.opentelemetry.auto.instrumentation.lettuce.v5_0.LettuceClientDecorator.TRACER; import static io.opentelemetry.trace.Span.Kind.CLIENT; import io.lettuce.core.protocol.RedisCommand; -import io.opentelemetry.auto.instrumentation.lettuce.LettuceInstrumentationUtil; +import io.opentelemetry.auto.instrumentation.lettuce.v5_0.LettuceInstrumentationUtil; import io.opentelemetry.trace.Span; import java.util.function.BiConsumer; import java.util.function.Consumer; diff --git a/instrumentation/lettuce-5.0/src/test/groovy/LettuceAsyncClientTest.groovy b/instrumentation/lettuce/lettuce-5.0/src/test/groovy/LettuceAsyncClientTest.groovy similarity index 100% rename from instrumentation/lettuce-5.0/src/test/groovy/LettuceAsyncClientTest.groovy rename to instrumentation/lettuce/lettuce-5.0/src/test/groovy/LettuceAsyncClientTest.groovy diff --git a/instrumentation/lettuce-5.0/src/test/groovy/LettuceReactiveClientTest.groovy b/instrumentation/lettuce/lettuce-5.0/src/test/groovy/LettuceReactiveClientTest.groovy similarity index 100% rename from instrumentation/lettuce-5.0/src/test/groovy/LettuceReactiveClientTest.groovy rename to instrumentation/lettuce/lettuce-5.0/src/test/groovy/LettuceReactiveClientTest.groovy diff --git a/instrumentation/lettuce-5.0/src/test/groovy/LettuceSyncClientTest.groovy b/instrumentation/lettuce/lettuce-5.0/src/test/groovy/LettuceSyncClientTest.groovy similarity index 100% rename from instrumentation/lettuce-5.0/src/test/groovy/LettuceSyncClientTest.groovy rename to instrumentation/lettuce/lettuce-5.0/src/test/groovy/LettuceSyncClientTest.groovy diff --git a/instrumentation/rediscala-1.8/rediscala-1.8.gradle b/instrumentation/rediscala-1.8/rediscala-1.8.gradle new file mode 100644 index 0000000000..88dabcc1e5 --- /dev/null +++ b/instrumentation/rediscala-1.8/rediscala-1.8.gradle @@ -0,0 +1,65 @@ +ext { + minJavaVersionForTests = JavaVersion.VERSION_1_8 +} + +apply from: "${rootDir}/gradle/instrumentation.gradle" +apply plugin: 'org.unbroken-dome.test-sets' + +muzzle { + pass { + group = "com.github.etaty" + module = "rediscala_2.11" + versions = "[1.5.0,)" + assertInverse = true + } + + pass { + group = "com.github.etaty" + module = "rediscala_2.12" + versions = "[1.8.0,)" + assertInverse = true + } + + pass { + group = "com.github.etaty" + module = "rediscala_2.13" + versions = "[1.9.0,)" + assertInverse = true + } + + pass { + group = "com.github.Ma27" + module = "rediscala_2.11" + versions = "[1.8.1,)" + assertInverse = true + } + + pass { + group = "com.github.Ma27" + module = "rediscala_2.12" + versions = "[1.8.1,)" + assertInverse = true + } + + pass { + group = "com.github.Ma27" + module = "rediscala_2.13" + versions = "[1.9.0,)" + assertInverse = true + } +} + +testSets { + latestDepTest { + dirName = 'test' + } +} + +dependencies { + compileOnly group: 'com.github.etaty', name: 'rediscala_2.11', version: '1.8.0' + + testCompile group: 'com.github.etaty', name: 'rediscala_2.11', version: '1.8.0' + testCompile group: 'com.github.kstyrc', name: 'embedded-redis', version: '0.6' + + latestDepTestCompile group: 'com.github.etaty', name: 'rediscala_2.11', version: '+' +} diff --git a/instrumentation/rediscala-1.8/src/main/java/io/opentelemetry/auto/instrumentation/rediscala/RediscalaClientDecorator.java b/instrumentation/rediscala-1.8/src/main/java/io/opentelemetry/auto/instrumentation/rediscala/RediscalaClientDecorator.java new file mode 100644 index 0000000000..703aa02abf --- /dev/null +++ b/instrumentation/rediscala-1.8/src/main/java/io/opentelemetry/auto/instrumentation/rediscala/RediscalaClientDecorator.java @@ -0,0 +1,46 @@ +/* + * 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.rediscala; + +import io.opentelemetry.OpenTelemetry; +import io.opentelemetry.auto.bootstrap.instrumentation.decorator.DatabaseClientDecorator; +import io.opentelemetry.trace.Tracer; +import redis.RedisCommand; +import redis.protocol.RedisReply; + +public class RediscalaClientDecorator + extends DatabaseClientDecorator> { + + public static final RediscalaClientDecorator DECORATE = new RediscalaClientDecorator(); + + public static final Tracer TRACER = + OpenTelemetry.getTracerProvider().get("io.opentelemetry.auto.rediscala-1.8"); + + @Override + protected String dbType() { + return "redis"; + } + + @Override + protected String dbUser(final RedisCommand session) { + return null; + } + + @Override + protected String dbInstance(final RedisCommand session) { + return null; + } +} 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 new file mode 100644 index 0000000000..70f97af19d --- /dev/null +++ b/instrumentation/rediscala-1.8/src/main/java/io/opentelemetry/auto/instrumentation/rediscala/RediscalaInstrumentation.java @@ -0,0 +1,133 @@ +/* + * 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.rediscala; + +import static io.opentelemetry.auto.instrumentation.rediscala.RediscalaClientDecorator.DECORATE; +import static io.opentelemetry.auto.instrumentation.rediscala.RediscalaClientDecorator.TRACER; +import static io.opentelemetry.auto.tooling.bytebuddy.matcher.AgentElementMatchers.safeHasSuperType; +import static io.opentelemetry.trace.Span.Kind.CLIENT; +import static io.opentelemetry.trace.TracingContextUtils.currentContextWith; +import static java.util.Collections.singletonMap; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.isPublic; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.returns; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; + +import com.google.auto.service.AutoService; +import io.opentelemetry.auto.instrumentation.api.SpanWithScope; +import io.opentelemetry.auto.tooling.Instrumenter; +import io.opentelemetry.trace.Span; +import java.util.Map; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; +import redis.RedisCommand; +import scala.concurrent.ExecutionContext; +import scala.concurrent.Future; +import scala.runtime.AbstractFunction1; +import scala.util.Try; + +@AutoService(Instrumenter.class) +public final class RediscalaInstrumentation extends Instrumenter.Default { + + public RediscalaInstrumentation() { + super("rediscala", "redis"); + } + + @Override + public ElementMatcher typeMatcher() { + return safeHasSuperType(named("redis.ActorRequest")) + .or(safeHasSuperType(named("redis.Request"))) + .or(safeHasSuperType(named("redis.BufferedRequest"))) + .or(safeHasSuperType(named("redis.RoundRobinPoolRequest"))); + } + + @Override + public String[] helperClassNames() { + return new String[] { + RediscalaInstrumentation.class.getName() + "$OnCompleteHandler", + "io.opentelemetry.auto.bootstrap.instrumentation.decorator.BaseDecorator", + "io.opentelemetry.auto.bootstrap.instrumentation.decorator.ClientDecorator", + "io.opentelemetry.auto.bootstrap.instrumentation.decorator.DatabaseClientDecorator", + packageName + ".RediscalaClientDecorator", + }; + } + + @Override + public Map, String> transformers() { + return singletonMap( + isMethod() + .and(isPublic()) + .and(named("send")) + .and(takesArgument(0, named("redis.RedisCommand"))) + .and(returns(named("scala.concurrent.Future"))), + RediscalaInstrumentation.class.getName() + "$RediscalaAdvice"); + } + + public static class RediscalaAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static SpanWithScope onEnter(@Advice.Argument(0) final RedisCommand cmd) { + final Span span = + TRACER.spanBuilder(cmd.getClass().getName()).setSpanKind(CLIENT).startSpan(); + DECORATE.afterStart(span); + DECORATE.onStatement(span, cmd.getClass().getName()); + return new SpanWithScope(span, currentContextWith(span)); + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void stopSpan( + @Advice.Enter final SpanWithScope scope, + @Advice.Thrown final Throwable throwable, + @Advice.FieldValue("executionContext") final ExecutionContext ctx, + @Advice.Return(readOnly = false) final Future responseFuture) { + + final Span span = scope.getSpan(); + + if (throwable == null) { + responseFuture.onComplete(new OnCompleteHandler(span), ctx); + } else { + DECORATE.onError(span, throwable); + DECORATE.beforeFinish(span); + span.end(); + } + scope.closeScope(); + } + } + + public static class OnCompleteHandler extends AbstractFunction1, Void> { + private final Span span; + + public OnCompleteHandler(final Span span) { + this.span = span; + } + + @Override + public Void apply(final Try result) { + try { + if (result.isFailure()) { + DECORATE.onError(span, result.failed().get()); + } + DECORATE.beforeFinish(span); + } finally { + span.end(); + } + return null; + } + } +} diff --git a/instrumentation/rediscala-1.8/src/test/groovy/RediscalaClientTest.groovy b/instrumentation/rediscala-1.8/src/test/groovy/RediscalaClientTest.groovy new file mode 100644 index 0000000000..441caed9e3 --- /dev/null +++ b/instrumentation/rediscala-1.8/src/test/groovy/RediscalaClientTest.groovy @@ -0,0 +1,135 @@ +/* + * 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 akka.actor.ActorSystem +import io.opentelemetry.auto.instrumentation.api.Tags +import io.opentelemetry.auto.test.AgentTestRunner +import io.opentelemetry.auto.test.utils.PortUtils +import redis.ByteStringDeserializerDefault +import redis.ByteStringSerializerLowPriority +import redis.RedisClient +import redis.RedisDispatcher +import redis.embedded.RedisServer +import scala.Option +import scala.concurrent.Await +import scala.concurrent.duration.Duration +import spock.lang.Shared + +import static io.opentelemetry.trace.Span.Kind.CLIENT + +class RediscalaClientTest extends AgentTestRunner { + + @Shared + int port = PortUtils.randomOpenPort() + + @Shared + RedisServer redisServer = RedisServer.builder() + // bind to localhost to avoid firewall popup + .setting("bind 127.0.0.1") + // set max memory to avoid problems in CI + .setting("maxmemory 128M") + .port(port).build() + + @Shared + ActorSystem system + + @Shared + RedisClient redisClient + + def setupSpec() { + system = ActorSystem.create() + redisClient = new RedisClient("localhost", + port, + Option.apply(null), + Option.apply(null), + "RedisClient", + Option.apply(null), + system, + new RedisDispatcher("rediscala.rediscala-client-worker-dispatcher")) + + println "Using redis: $redisServer.args" + redisServer.start() + } + + def cleanupSpec() { + redisServer.stop() + system?.terminate() + } + + def "set command"() { + when: + def value = redisClient.set("foo", + "bar", + Option.apply(null), + Option.apply(null), + false, + false, + new ByteStringSerializerLowPriority.String$()) + + + then: + Await.result(value, Duration.apply("3 second")) == true + assertTraces(1) { + trace(0, 1) { + span(0) { + operationName "redis.api.strings.Set" + spanKind CLIENT + tags { + "$Tags.DB_TYPE" "redis" + "$Tags.DB_STATEMENT" "redis.api.strings.Set" + } + } + } + } + } + + def "get command"() { + when: + def write = redisClient.set("bar", + "baz", + Option.apply(null), + Option.apply(null), + false, + false, + new ByteStringSerializerLowPriority.String$()) + def value = redisClient.get("bar", new ByteStringDeserializerDefault.String$()) + + then: + Await.result(write, Duration.apply("3 second")) == true + Await.result(value, Duration.apply("3 second")) == Option.apply("baz") + assertTraces(2) { + trace(0, 1) { + span(0) { + operationName "redis.api.strings.Set" + spanKind CLIENT + tags { + "$Tags.DB_TYPE" "redis" + "$Tags.DB_STATEMENT" "redis.api.strings.Set" + } + } + } + trace(1, 1) { + span(0) { + operationName "redis.api.strings.Get" + spanKind CLIENT + tags { + "$Tags.DB_TYPE" "redis" + "$Tags.DB_STATEMENT" "redis.api.strings.Get" + } + } + } + } + } +} diff --git a/instrumentation/servlet/request-2.3/request-2.3.gradle b/instrumentation/servlet/request-2.3/request-2.3.gradle index da4bb0297b..fddad8af2f 100644 --- a/instrumentation/servlet/request-2.3/request-2.3.gradle +++ b/instrumentation/servlet/request-2.3/request-2.3.gradle @@ -5,7 +5,7 @@ muzzle { pass { group = "javax.servlet" module = "servlet-api" - versions = "[2.3, 3.0)" + versions = "[2.2, 3.0)" assertInverse = true } @@ -23,7 +23,7 @@ testSets { } dependencies { - compileOnly group: 'javax.servlet', name: 'servlet-api', version: '2.3' + compileOnly group: 'javax.servlet', name: 'servlet-api', version: '2.2' testCompile(project(':testing')) { exclude group: 'org.eclipse.jetty', module: 'jetty-server' 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 617ed30e82..7f0fba512b 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 @@ -43,8 +43,8 @@ public class Servlet2Advice { public static SpanWithScope onEnter( @Advice.This final Object servlet, @Advice.Argument(0) final ServletRequest request, - @Advice.Argument(value = 1, readOnly = false, typing = Assigner.Typing.DYNAMIC) - ServletResponse response) { + @Advice.Argument(value = 1, typing = Assigner.Typing.DYNAMIC) + final ServletResponse response) { final boolean hasServletTrace = request.getAttribute(SPAN_ATTRIBUTE) instanceof Span; final boolean invalidRequest = !(request instanceof HttpServletRequest); if (invalidRequest || hasServletTrace) { @@ -59,7 +59,8 @@ public class Servlet2Advice { InstrumentationContext.get(HttpServletResponse.class, HttpServletRequest.class) .put((HttpServletResponse) response, httpServletRequest); - response = new StatusSavingHttpServletResponseWrapper((HttpServletResponse) response); + // Default value for checking for uncaught error later + InstrumentationContext.get(ServletResponse.class, Integer.class).put(response, 200); } final Span.Builder builder = @@ -99,10 +100,17 @@ public class Servlet2Advice { return; } final Span span = spanWithScope.getSpan(); - DECORATE.onResponse(span, response); + + if (response instanceof HttpServletResponse) { + DECORATE.onResponse( + span, InstrumentationContext.get(ServletResponse.class, Integer.class).get(response)); + } else { + DECORATE.onResponse(span, null); + } + if (throwable != null) { - if (response instanceof StatusSavingHttpServletResponseWrapper - && ((StatusSavingHttpServletResponseWrapper) response).status + if (response instanceof HttpServletResponse + && InstrumentationContext.get(ServletResponse.class, Integer.class).get(response) == HttpServletResponse.SC_OK) { // exception was thrown but status code wasn't set span.setAttribute(Tags.HTTP_STATUS, 500); diff --git a/instrumentation/servlet/request-2.3/src/main/java/io/opentelemetry/auto/instrumentation/servlet/v2_3/Servlet2Decorator.java b/instrumentation/servlet/request-2.3/src/main/java/io/opentelemetry/auto/instrumentation/servlet/v2_3/Servlet2Decorator.java index d4acddce20..897ee668c7 100644 --- a/instrumentation/servlet/request-2.3/src/main/java/io/opentelemetry/auto/instrumentation/servlet/v2_3/Servlet2Decorator.java +++ b/instrumentation/servlet/request-2.3/src/main/java/io/opentelemetry/auto/instrumentation/servlet/v2_3/Servlet2Decorator.java @@ -21,11 +21,10 @@ import io.opentelemetry.trace.Span; import io.opentelemetry.trace.Tracer; import java.net.URI; import java.net.URISyntaxException; -import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; public class Servlet2Decorator - extends HttpServerDecorator { + extends HttpServerDecorator { public static final Servlet2Decorator DECORATE = new Servlet2Decorator(); public static final Tracer TRACER = OpenTelemetry.getTracerProvider().get("io.opentelemetry.auto.servlet-2.3"); @@ -59,13 +58,8 @@ public class Servlet2Decorator } @Override - protected Integer status(final ServletResponse httpServletResponse) { - if (httpServletResponse instanceof StatusSavingHttpServletResponseWrapper) { - return ((StatusSavingHttpServletResponseWrapper) httpServletResponse).status; - } else { - // HttpServletResponse doesn't have accessor for status code. - return null; - } + protected Integer status(final Integer status) { + return status; } @Override diff --git a/instrumentation/servlet/request-2.3/src/main/java/io/opentelemetry/auto/instrumentation/servlet/v2_3/Servlet2Instrumentation.java b/instrumentation/servlet/request-2.3/src/main/java/io/opentelemetry/auto/instrumentation/servlet/v2_3/Servlet2Instrumentation.java index a4c3ef429c..35af63590a 100644 --- a/instrumentation/servlet/request-2.3/src/main/java/io/opentelemetry/auto/instrumentation/servlet/v2_3/Servlet2Instrumentation.java +++ b/instrumentation/servlet/request-2.3/src/main/java/io/opentelemetry/auto/instrumentation/servlet/v2_3/Servlet2Instrumentation.java @@ -25,6 +25,7 @@ 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; @@ -52,16 +53,17 @@ public final class Servlet2Instrumentation extends Instrumenter.Default { @Override public String[] helperClassNames() { return new String[] { - packageName + ".Servlet2Decorator", - packageName + ".HttpServletRequestExtractAdapter", - packageName + ".StatusSavingHttpServletResponseWrapper", + packageName + ".Servlet2Decorator", packageName + ".HttpServletRequestExtractAdapter", }; } @Override public Map contextStore() { - return singletonMap( + final Map contextStores = new HashMap<>(); + contextStores.put( "javax.servlet.http.HttpServletResponse", "javax.servlet.http.HttpServletRequest"); + contextStores.put("javax.servlet.ServletResponse", Integer.class.getName()); + return contextStores; } /** diff --git a/instrumentation/servlet/request-2.3/src/main/java/io/opentelemetry/auto/instrumentation/servlet/v2_3/Servlet2ResponseRedirectAdvice.java b/instrumentation/servlet/request-2.3/src/main/java/io/opentelemetry/auto/instrumentation/servlet/v2_3/Servlet2ResponseRedirectAdvice.java new file mode 100644 index 0000000000..374818ca74 --- /dev/null +++ b/instrumentation/servlet/request-2.3/src/main/java/io/opentelemetry/auto/instrumentation/servlet/v2_3/Servlet2ResponseRedirectAdvice.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.servlet.v2_3; + +import io.opentelemetry.auto.bootstrap.InstrumentationContext; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletResponse; +import net.bytebuddy.asm.Advice; + +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); + } +} diff --git a/instrumentation/servlet/request-2.3/src/main/java/io/opentelemetry/auto/instrumentation/servlet/v2_3/Servlet2ResponseStatusAdvice.java b/instrumentation/servlet/request-2.3/src/main/java/io/opentelemetry/auto/instrumentation/servlet/v2_3/Servlet2ResponseStatusAdvice.java new file mode 100644 index 0000000000..7f32a45da7 --- /dev/null +++ b/instrumentation/servlet/request-2.3/src/main/java/io/opentelemetry/auto/instrumentation/servlet/v2_3/Servlet2ResponseStatusAdvice.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.servlet.v2_3; + +import io.opentelemetry.auto.bootstrap.InstrumentationContext; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletResponse; +import net.bytebuddy.asm.Advice; + +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); + } +} 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 new file mode 100644 index 0000000000..b326e882af --- /dev/null +++ b/instrumentation/servlet/request-2.3/src/main/java/io/opentelemetry/auto/instrumentation/servlet/v2_3/Servlet2ResponseStatusInstrumentation.java @@ -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.instrumentation.servlet.v2_3; + +import static io.opentelemetry.auto.tooling.ClassLoaderMatcher.hasClassesNamed; +import static io.opentelemetry.auto.tooling.bytebuddy.matcher.AgentElementMatchers.safeHasSuperType; +import static java.util.Collections.singletonMap; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.not; + +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 Servlet2ResponseStatusInstrumentation extends Instrumenter.Default { + public Servlet2ResponseStatusInstrumentation() { + super("servlet", "servlet-2"); + } + + // this is required to make sure servlet 2 instrumentation won't apply to servlet 3 + @Override + public ElementMatcher classLoaderMatcher() { + return not(hasClassesNamed("javax.servlet.AsyncEvent", "javax.servlet.AsyncListener")); + } + + @Override + public ElementMatcher typeMatcher() { + return safeHasSuperType(named("javax.servlet.http.HttpServletResponse")); + } + + @Override + 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"); + return transformers; + } +} diff --git a/instrumentation/servlet/request-2.3/src/main/java/io/opentelemetry/auto/instrumentation/servlet/v2_3/StatusSavingHttpServletResponseWrapper.java b/instrumentation/servlet/request-2.3/src/main/java/io/opentelemetry/auto/instrumentation/servlet/v2_3/StatusSavingHttpServletResponseWrapper.java deleted file mode 100644 index f4dc0d64eb..0000000000 --- a/instrumentation/servlet/request-2.3/src/main/java/io/opentelemetry/auto/instrumentation/servlet/v2_3/StatusSavingHttpServletResponseWrapper.java +++ /dev/null @@ -1,58 +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. - */ -package io.opentelemetry.auto.instrumentation.servlet.v2_3; - -import java.io.IOException; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpServletResponseWrapper; - -public class StatusSavingHttpServletResponseWrapper extends HttpServletResponseWrapper { - public int status = 200; - - public StatusSavingHttpServletResponseWrapper(final HttpServletResponse response) { - super(response); - } - - @Override - public void sendError(final int status) throws IOException { - this.status = status; - super.sendError(status); - } - - @Override - public void sendError(final int status, final String message) throws IOException { - this.status = status; - super.sendError(status, message); - } - - @Override - public void sendRedirect(final String location) throws IOException { - status = 302; - super.sendRedirect(location); - } - - @Override - public void setStatus(final int status) { - this.status = status; - super.setStatus(status); - } - - @Override - public void setStatus(final int status, final String message) { - this.status = status; - super.setStatus(status, message); - } -} diff --git a/instrumentation/servlet/request-3.0/src/main/java/io/opentelemetry/auto/instrumentation/servlet/v3_0/Servlet3Decorator.java b/instrumentation/servlet/request-3.0/src/main/java/io/opentelemetry/auto/instrumentation/servlet/v3_0/Servlet3Decorator.java index 1cc4458521..16af87a709 100644 --- a/instrumentation/servlet/request-3.0/src/main/java/io/opentelemetry/auto/instrumentation/servlet/v3_0/Servlet3Decorator.java +++ b/instrumentation/servlet/request-3.0/src/main/java/io/opentelemetry/auto/instrumentation/servlet/v3_0/Servlet3Decorator.java @@ -88,7 +88,7 @@ public class Servlet3Decorator */ private void onContext( final Span span, final HttpServletRequest request, final ServletContext context) { - final Object attribute = context.getAttribute("ota.dispatcher-filter"); + final Object attribute = context.getAttribute("io.opentelemetry.auto.dispatcher-filter"); if (attribute instanceof Filter) { final Object priorAttr = request.getAttribute(SPAN_ATTRIBUTE); request.setAttribute(SPAN_ATTRIBUTE, span); diff --git a/instrumentation/spring-webmvc-3.1/src/main/java/io/opentelemetry/auto/instrumentation/springwebmvc/DispatcherServletInstrumentation.java b/instrumentation/spring-webmvc-3.1/src/main/java/io/opentelemetry/auto/instrumentation/springwebmvc/DispatcherServletInstrumentation.java index 880239a1ec..9bdd2c8266 100644 --- a/instrumentation/spring-webmvc-3.1/src/main/java/io/opentelemetry/auto/instrumentation/springwebmvc/DispatcherServletInstrumentation.java +++ b/instrumentation/spring-webmvc-3.1/src/main/java/io/opentelemetry/auto/instrumentation/springwebmvc/DispatcherServletInstrumentation.java @@ -120,8 +120,8 @@ public final class DispatcherServletInstrumentation extends Instrumenter.Default contextStore.put(dispatcher, filter); } filter.setHandlerMappings(handlerMappings); - servletContext.setAttribute( - "ota.dispatcher-filter", filter); // used by Servlet3Decorator.onContext + // attribute used by Servlet3Decorator.onContext + servletContext.setAttribute("io.opentelemetry.auto.dispatcher-filter", filter); } } } diff --git a/settings.gradle b/settings.gradle index 733ee9b9ef..0eeb5ef3f3 100644 --- a/settings.gradle +++ b/settings.gradle @@ -99,7 +99,8 @@ include ':instrumentation:jms-1.1' include ':instrumentation:jsp-2.3' include ':instrumentation:kafka-clients-0.11' include ':instrumentation:kafka-streams-0.11' -include ':instrumentation:lettuce-5.0' +include ':instrumentation:lettuce:lettuce-4.0' +include ':instrumentation:lettuce:lettuce-5.0' include ':instrumentation:log4j:log4j-1.1' include ':instrumentation:log4j:log4j-2.0' include ':instrumentation:logback-1.0' @@ -122,6 +123,7 @@ include ':instrumentation:play-ws:play-ws-common' include ':instrumentation:rabbitmq-amqp-2.7' include ':instrumentation:ratpack-1.5' include ':instrumentation:reactor-3.1' +include ':instrumentation:rediscala-1.8' include ':instrumentation:rmi' include ':instrumentation:rxjava-1.0' include ':instrumentation:servlet'