Merge tag 'v0.50.0' into correct-history

This commit is contained in:
Trask Stalnaker 2020-05-05 12:39:58 -07:00
commit 6d1a58d151
51 changed files with 1998 additions and 151 deletions

View File

@ -142,11 +142,21 @@ jobs:
environment: environment:
- TEST_TASK: testJava13 - TEST_TASK: testJava13
test_zulu13:
<<: *default_test_job
environment:
- TEST_TASK: testJavaZULU13
test_14: test_14:
<<: *default_test_job <<: *default_test_job
environment: environment:
- TEST_TASK: testJava14 - TEST_TASK: testJava14
test_zulu14:
<<: *default_test_job
environment:
- TEST_TASK: testJavaZULU14
check: check:
<<: *defaults <<: *defaults
@ -243,25 +253,25 @@ workflows:
only: /.*/ only: /.*/
# - test_ibm8: # - test_ibm8:
# requires: # requires:
# - build # - build
# filters: # filters:
# tags: # tags:
# only: /.*/ # only: /.*/
# - test_zulu8: # - test_zulu8:
# requires: # requires:
# - build # - build
# filters: # filters:
# tags: # tags:
# only: /.*/ # only: /.*/
# - test_9: # - test_9:
# requires: # requires:
# - build # - build
# filters: # filters:
# tags: # tags:
# only: /.*/ # only: /.*/
# - test_10: # - test_10:
# requires: # requires:
# - build # - build
# filters: # filters:
# tags: # tags:
# only: /.*/ # only: /.*/
@ -273,13 +283,13 @@ workflows:
only: /.*/ only: /.*/
# - test_zulu11: # - test_zulu11:
# requires: # requires:
# - build # - build
# filters: # filters:
# tags: # tags:
# only: /.*/ # only: /.*/
# - test_12: # - test_12:
# requires: # requires:
# - build # - build
# filters: # filters:
# tags: # tags:
# only: /.*/ # only: /.*/
@ -289,12 +299,24 @@ workflows:
# filters: # filters:
# tags: # tags:
# only: /.*/ # only: /.*/
# - test_zulu13:
# requires:
# - build
# filters:
# tags:
# only: /.*/
- test_14: - test_14:
requires: requires:
- build - build
filters: filters:
tags: tags:
only: /.*/ only: /.*/
# - test_zulu14:
# requires:
# - build
# filters:
# tags:
# only: /.*/
- check: - check:
requires: requires:
@ -323,7 +345,9 @@ workflows:
# - test_zulu11 # - test_zulu11
# - test_12 # - test_12
# - test_13 # - test_13
# - test_zulu13
- test_14 - test_14
# - test_zulu14
- check - check
filters: filters:
branches: branches:
@ -344,7 +368,9 @@ workflows:
# - test_zulu11 # - test_zulu11
# - test_12 # - test_12
# - test_13 # - test_13
# - test_zulu13
- test_14 - test_14
# - test_zulu14
- check - check
filters: filters:
branches: branches:

View File

@ -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 }}`
})

View File

@ -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 }}

View File

@ -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+ | | [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+ | | [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+ | | [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+ | | [Log4j](https://logging.apache.org/log4j/2.x/) | 1.1+ |
| [Logback](https://github.com/qos-ch/logback) | 1.0+ | | [Logback](https://github.com/qos-ch/logback) | 1.0+ |
| [MongoDB Drivers](https://mongodb.github.io/mongo-java-driver/) | 3.3+ | | [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+ | | [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+ | | [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+ | | [RabbitMQ Client](https://github.com/rabbitmq/rabbitmq-java-client) | 2.7+ |
| [Ratpack](https://github.com/ratpack/ratpack) | 1.5+ | | [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+ | | [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+ | | [RxJava](https://github.com/ReactiveX/RxJava) | 1.0+ |
| [Servlet](https://javaee.github.io/javaee-spec/javadocs/javax/servlet/package-summary.html) | 2.3+ | | [Servlet](https://javaee.github.io/javaee-spec/javadocs/javax/servlet/package-summary.html) | 2.3+ |

View File

@ -37,7 +37,7 @@ public class State {
public void setParentSpan(final Span parentSpan) { public void setParentSpan(final Span parentSpan) {
final boolean result = parentSpanRef.compareAndSet(null, parentSpan); final boolean result = parentSpanRef.compareAndSet(null, parentSpan);
if (!result && parentSpanRef.get() != parentSpan) { if (!result) {
log.debug( log.debug(
"Failed to set parent span because another parent span is already set {}: new: {}, old: {}", "Failed to set parent span because another parent span is already set {}: new: {}, old: {}",
this, this,

View File

@ -47,6 +47,7 @@ import lombok.extern.slf4j.Slf4j;
@Slf4j @Slf4j
@ToString(includeFieldNames = true) @ToString(includeFieldNames = true)
public class Config { public class Config {
private static final MethodHandles.Lookup PUBLIC_LOOKUP = MethodHandles.publicLookup();
/** Config keys below */ /** Config keys below */
private static final String PREFIX = "ota."; private static final String PREFIX = "ota.";
@ -404,7 +405,7 @@ public class Config {
} }
try { try {
return (T) return (T)
MethodHandles.publicLookup() PUBLIC_LOOKUP
.findStatic(tClass, "valueOf", MethodType.methodType(tClass, String.class)) .findStatic(tClass, "valueOf", MethodType.methodType(tClass, String.class))
.invoke(value); .invoke(value);
} catch (final NumberFormatException e) { } catch (final NumberFormatException e) {

View File

@ -71,9 +71,11 @@ class DropwizardAsyncTest extends DropwizardTest {
@GET @GET
@Path("query") @Path("query")
Response query_param(@QueryParam("some") String param) { Response query_param(@QueryParam("some") String param, @Suspended final AsyncResponse asyncResponse) {
controller(QUERY_PARAM) { executor.execute {
Response.status(QUERY_PARAM.status).entity("some=$param".toString()).build() controller(QUERY_PARAM) {
asyncResponse.resume(Response.status(QUERY_PARAM.status).entity("some=$param".toString()).build())
}
} }
} }

View File

@ -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.nameMatches;
import static net.bytebuddy.matcher.ElementMatchers.named; import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument; import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
import com.google.auto.service.AutoService; import com.google.auto.service.AutoService;
import io.opentelemetry.auto.bootstrap.ContextStore; import io.opentelemetry.auto.bootstrap.ContextStore;
@ -59,7 +60,7 @@ public final class JavaExecutorInstrumentation extends AbstractExecutorInstrumen
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() { public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
final Map<ElementMatcher<? super MethodDescription>, String> transformers = new HashMap<>(); final Map<ElementMatcher<? super MethodDescription>, String> transformers = new HashMap<>();
transformers.put( transformers.put(
named("execute").and(takesArgument(0, Runnable.class)), named("execute").and(takesArgument(0, Runnable.class)).and(takesArguments(1)),
JavaExecutorInstrumentation.class.getName() + "$SetExecuteRunnableStateAdvice"); JavaExecutorInstrumentation.class.getName() + "$SetExecuteRunnableStateAdvice");
transformers.put( transformers.put(
named("execute").and(takesArgument(0, ForkJoinTask.class)), named("execute").and(takesArgument(0, ForkJoinTask.class)),

View File

@ -29,6 +29,7 @@ import static net.bytebuddy.matcher.ElementMatchers.named;
import com.google.auto.service.AutoService; import com.google.auto.service.AutoService;
import io.opentelemetry.auto.bootstrap.CallDepthThreadLocalMap; import io.opentelemetry.auto.bootstrap.CallDepthThreadLocalMap;
import io.opentelemetry.auto.bootstrap.ContextStore;
import io.opentelemetry.auto.bootstrap.InstrumentationContext; import io.opentelemetry.auto.bootstrap.InstrumentationContext;
import io.opentelemetry.auto.instrumentation.api.SpanWithScope; import io.opentelemetry.auto.instrumentation.api.SpanWithScope;
import io.opentelemetry.auto.tooling.Instrumenter; import io.opentelemetry.auto.tooling.Instrumenter;
@ -99,7 +100,28 @@ public final class JaxRsAnnotationsInstrumentation extends Instrumenter.Default
@Advice.OnMethodEnter(suppress = Throwable.class) @Advice.OnMethodEnter(suppress = Throwable.class)
public static SpanWithScope nameSpan( 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<AsyncResponse, Span> 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) { if (CallDepthThreadLocalMap.incrementCallDepth(Path.class) > 0) {
return null; return null;
} }
@ -111,6 +133,10 @@ public final class JaxRsAnnotationsInstrumentation extends Instrumenter.Default
DECORATE.onJaxRsSpan(span, parent, target.getClass(), method); DECORATE.onJaxRsSpan(span, parent, target.getClass(), method);
DECORATE.afterStart(span); DECORATE.afterStart(span);
if (contextStore != null && asyncResponse != null) {
contextStore.put(asyncResponse, span);
}
return new SpanWithScope(span, currentContextWith(span)); return new SpanWithScope(span, currentContextWith(span));
} }
@ -118,7 +144,7 @@ public final class JaxRsAnnotationsInstrumentation extends Instrumenter.Default
public static void stopSpan( public static void stopSpan(
@Advice.Enter final SpanWithScope spanWithScope, @Advice.Enter final SpanWithScope spanWithScope,
@Advice.Thrown final Throwable throwable, @Advice.Thrown final Throwable throwable,
@Advice.AllArguments final Object[] args) { @Advice.Local("asyncResponse") final AsyncResponse asyncResponse) {
if (spanWithScope == null) { if (spanWithScope == null) {
return; return;
} }
@ -133,16 +159,11 @@ public final class JaxRsAnnotationsInstrumentation extends Instrumenter.Default
return; return;
} }
AsyncResponse asyncResponse = null; if (asyncResponse != null && !asyncResponse.isSuspended()) {
for (final Object arg : args) { // Clear span from the asyncResponse. Logically this should never happen. Added to be safe.
if (arg instanceof AsyncResponse) { InstrumentationContext.get(AsyncResponse.class, Span.class).put(asyncResponse, null);
asyncResponse = (AsyncResponse) arg;
break;
}
} }
if (asyncResponse != null && asyncResponse.isSuspended()) { if (asyncResponse == null || !asyncResponse.isSuspended()) {
InstrumentationContext.get(AsyncResponse.class, Span.class).put(asyncResponse, span);
} else {
DECORATE.beforeFinish(span); DECORATE.beforeFinish(span);
span.end(); span.end();
} }

View File

@ -76,7 +76,7 @@ public class JDBCDecorator extends DatabaseClientDecorator<DBInfo> {
if (url != null) { if (url != null) {
try { try {
dbInfo = JDBCConnectionUrlParser.parse(url, connection.getClientInfo()); dbInfo = JDBCConnectionUrlParser.parse(url, connection.getClientInfo());
} catch (final Exception ex) { } catch (final Throwable ex) {
// getClientInfo is likely not allowed. // getClientInfo is likely not allowed.
dbInfo = JDBCConnectionUrlParser.parse(url, null); dbInfo = JDBCConnectionUrlParser.parse(url, null);
} }

View File

@ -266,7 +266,7 @@ class TestConnection implements Connection {
@Override @Override
Properties getClientInfo() throws SQLException { Properties getClientInfo() throws SQLException {
throw new UnsupportedOperationException("Test 123") throw new Throwable("Test 123")
} }
@Override @Override

View File

@ -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.+'
}

View File

@ -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<TypeDescription> typeMatcher() {
return named("com.lambdaworks.redis.AbstractRedisAsyncCommands");
}
@Override
public String[] helperClassNames() {
return new String[] {
packageName + ".LettuceClientDecorator", packageName + ".InstrumentationPoints"
};
}
@Override
public Map<? extends ElementMatcher<? super MethodDescription>, 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");
}
}

View File

@ -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<TypeDescription> typeMatcher() {
return named("com.lambdaworks.redis.RedisClient");
}
@Override
public String[] helperClassNames() {
return new String[] {
packageName + ".LettuceClientDecorator", packageName + ".InstrumentationPoints"
};
}
@Override
public Map<? extends ElementMatcher<? super MethodDescription>, 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");
}
}

View File

@ -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<CommandType> 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;
}
}

View File

@ -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);
}
}

View File

@ -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<RedisURI> {
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);
}
}

View File

@ -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);
}
}

View File

@ -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<String, String> testHashMap = [
firstname: "John",
lastname : "Doe",
age : "53"
]
RedisClient redisClient
StatefulConnection connection
RedisAsyncCommands<String, ?> asyncCommands
RedisCommands<String, ?> 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<String> 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<String> consumer = new Consumer<String>() {
@Override
void accept(String res) {
conds.evaluate {
assert res == "TESTVAL"
}
}
}
when:
RedisFuture<String> 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<String, Throwable, String> firstStage = new BiFunction<String, Throwable, String>() {
@Override
String apply(String res, Throwable throwable) {
conds.evaluate {
assert res == null
assert throwable == null
}
return (res == null ? successStr : res)
}
}
Function<String, Object> secondStage = new Function<String, Object>() {
@Override
Object apply(String input) {
conds.evaluate {
assert input == successStr
}
return null
}
}
when:
RedisFuture<String> 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<String, Throwable> biConsumer = new BiConsumer<String, Throwable>() {
@Override
void accept(String keyRetrieved, Throwable throwable) {
conds.evaluate {
assert keyRetrieved != null
}
}
}
when:
RedisFuture<String> 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<String> hmsetFuture = asyncCommands.hmset("TESTHM", testHashMap)
hmsetFuture.thenApplyAsync(new Function<String, Object>() {
@Override
Object apply(String setResult) {
TEST_WRITER.waitForTraces(1) // Wait for 'hmset' trace to get written
conds.evaluate {
assert setResult == "OK"
}
RedisFuture<Map<String, String>> hmGetAllFuture = asyncCommands.hgetall("TESTHM")
hmGetAllFuture.exceptionally(new Function<Throwable, Map<String, String>>() {
@Override
Map<String, String> apply(Throwable throwable) {
println("unexpected:" + throwable.toString())
throwable.printStackTrace()
assert false
return null
}
})
hmGetAllFuture.thenAccept(new Consumer<Map<String, String>>() {
@Override
void accept(Map<String, String> 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"
}
}
}
}
}
}

View File

@ -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<String, String> testHashMap = [
firstname: "John",
lastname : "Doe",
age : "53"
]
RedisClient redisClient
StatefulConnection connection
RedisCommands<String, ?> 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<String, String> 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"
}
}
}
}
}
}

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * 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 java.util.Collections.singletonMap;
import static net.bytebuddy.matcher.ElementMatchers.isMethod; import static net.bytebuddy.matcher.ElementMatchers.isMethod;

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * 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 java.util.Collections.singletonMap;
import static net.bytebuddy.matcher.ElementMatchers.isMethod; import static net.bytebuddy.matcher.ElementMatchers.isMethod;

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * 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.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.nameEndsWith; import static net.bytebuddy.matcher.ElementMatchers.nameEndsWith;

View File

@ -13,10 +13,8 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * 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.Span.Kind.CLIENT;
import static io.opentelemetry.trace.TracingContextUtils.currentContextWith; import static io.opentelemetry.trace.TracingContextUtils.currentContextWith;
@ -30,9 +28,10 @@ public class ConnectionFutureAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class) @Advice.OnMethodEnter(suppress = Throwable.class)
public static SpanWithScope onEnter(@Advice.Argument(1) final RedisURI redisURI) { public static SpanWithScope onEnter(@Advice.Argument(1) final RedisURI redisURI) {
final Span span = TRACER.spanBuilder("CONNECT").setSpanKind(CLIENT).startSpan(); final Span span =
DECORATE.afterStart(span); LettuceClientDecorator.TRACER.spanBuilder("CONNECT").setSpanKind(CLIENT).startSpan();
DECORATE.onConnection(span, redisURI); LettuceClientDecorator.DECORATE.afterStart(span);
LettuceClientDecorator.DECORATE.onConnection(span, redisURI);
return new SpanWithScope(span, currentContextWith(span)); return new SpanWithScope(span, currentContextWith(span));
} }
@ -43,8 +42,8 @@ public class ConnectionFutureAdvice {
@Advice.Return final ConnectionFuture<?> connectionFuture) { @Advice.Return final ConnectionFuture<?> connectionFuture) {
final Span span = spanWithScope.getSpan(); final Span span = spanWithScope.getSpan();
if (throwable != null) { if (throwable != null) {
DECORATE.onError(span, throwable); LettuceClientDecorator.DECORATE.onError(span, throwable);
DECORATE.beforeFinish(span); LettuceClientDecorator.DECORATE.beforeFinish(span);
span.end(); span.end();
spanWithScope.closeScope(); spanWithScope.closeScope();
return; return;

View File

@ -13,9 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * 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 io.opentelemetry.trace.Span; import io.opentelemetry.trace.Span;
import java.util.concurrent.CancellationException; import java.util.concurrent.CancellationException;
@ -44,9 +42,9 @@ public class LettuceAsyncBiFunction<T extends Object, U extends Throwable, R ext
if (throwable instanceof CancellationException) { if (throwable instanceof CancellationException) {
span.setAttribute("db.command.cancelled", true); span.setAttribute("db.command.cancelled", true);
} else { } else {
DECORATE.onError(span, throwable); LettuceClientDecorator.DECORATE.onError(span, throwable);
} }
DECORATE.beforeFinish(span); LettuceClientDecorator.DECORATE.beforeFinish(span);
span.end(); span.end();
return null; return null;
} }

View File

@ -13,11 +13,8 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * 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.auto.instrumentation.lettuce.LettuceInstrumentationUtil.doFinishSpanEarly;
import static io.opentelemetry.trace.Span.Kind.CLIENT; import static io.opentelemetry.trace.Span.Kind.CLIENT;
import static io.opentelemetry.trace.TracingContextUtils.currentContextWith; import static io.opentelemetry.trace.TracingContextUtils.currentContextWith;
@ -33,11 +30,11 @@ public class LettuceAsyncCommandsAdvice {
public static SpanWithScope onEnter(@Advice.Argument(0) final RedisCommand command) { public static SpanWithScope onEnter(@Advice.Argument(0) final RedisCommand command) {
final Span span = final Span span =
TRACER LettuceClientDecorator.TRACER
.spanBuilder(LettuceInstrumentationUtil.getCommandName(command)) .spanBuilder(LettuceInstrumentationUtil.getCommandName(command))
.setSpanKind(CLIENT) .setSpanKind(CLIENT)
.startSpan(); .startSpan();
DECORATE.afterStart(span); LettuceClientDecorator.DECORATE.afterStart(span);
return new SpanWithScope(span, currentContextWith(span)); return new SpanWithScope(span, currentContextWith(span));
} }
@ -51,15 +48,15 @@ public class LettuceAsyncCommandsAdvice {
final Span span = spanWithScope.getSpan(); final Span span = spanWithScope.getSpan();
if (throwable != null) { if (throwable != null) {
DECORATE.onError(span, throwable); LettuceClientDecorator.DECORATE.onError(span, throwable);
DECORATE.beforeFinish(span); LettuceClientDecorator.DECORATE.beforeFinish(span);
span.end(); span.end();
spanWithScope.closeScope(); spanWithScope.closeScope();
return; return;
} }
// close spans on error or normal completion // close spans on error or normal completion
if (doFinishSpanEarly(command)) { if (LettuceInstrumentationUtil.doFinishSpanEarly(command)) {
span.end(); span.end();
} else { } else {
asyncCommand.handleAsync(new LettuceAsyncBiFunction<>(span)); asyncCommand.handleAsync(new LettuceAsyncBiFunction<>(span));

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * 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.lettuce.core.RedisURI;
import io.opentelemetry.OpenTelemetry; import io.opentelemetry.OpenTelemetry;

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * 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 io.lettuce.core.protocol.RedisCommand;
import java.util.Arrays; import java.util.Arrays;

View File

@ -13,9 +13,9 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * 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 io.lettuce.core.protocol.RedisCommand;
import java.util.function.Supplier; import java.util.function.Supplier;

View File

@ -13,14 +13,14 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * 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.v5_0.LettuceClientDecorator.DECORATE;
import static io.opentelemetry.auto.instrumentation.lettuce.LettuceClientDecorator.TRACER; import static io.opentelemetry.auto.instrumentation.lettuce.v5_0.LettuceClientDecorator.TRACER;
import static io.opentelemetry.trace.Span.Kind.CLIENT; import static io.opentelemetry.trace.Span.Kind.CLIENT;
import io.lettuce.core.protocol.RedisCommand; 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 io.opentelemetry.trace.Span;
import java.util.function.Consumer; import java.util.function.Consumer;
import org.reactivestreams.Subscription; import org.reactivestreams.Subscription;

View File

@ -13,10 +13,10 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * 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.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 java.util.function.Supplier;
import net.bytebuddy.asm.Advice; import net.bytebuddy.asm.Advice;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;

View File

@ -13,14 +13,14 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * 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.v5_0.LettuceClientDecorator.DECORATE;
import static io.opentelemetry.auto.instrumentation.lettuce.LettuceClientDecorator.TRACER; import static io.opentelemetry.auto.instrumentation.lettuce.v5_0.LettuceClientDecorator.TRACER;
import static io.opentelemetry.trace.Span.Kind.CLIENT; import static io.opentelemetry.trace.Span.Kind.CLIENT;
import io.lettuce.core.protocol.RedisCommand; 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 io.opentelemetry.trace.Span;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.Consumer; import java.util.function.Consumer;

View File

@ -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: '+'
}

View File

@ -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<RedisCommand<? extends RedisReply, ?>> {
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<? extends RedisReply, ?> session) {
return null;
}
@Override
protected String dbInstance(final RedisCommand<? extends RedisReply, ?> session) {
return null;
}
}

View File

@ -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<TypeDescription> 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<? extends ElementMatcher<? super MethodDescription>, 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<Object> 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<Try<Object>, Void> {
private final Span span;
public OnCompleteHandler(final Span span) {
this.span = span;
}
@Override
public Void apply(final Try<Object> result) {
try {
if (result.isFailure()) {
DECORATE.onError(span, result.failed().get());
}
DECORATE.beforeFinish(span);
} finally {
span.end();
}
return null;
}
}
}

View File

@ -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"
}
}
}
}
}
}

View File

@ -5,7 +5,7 @@ muzzle {
pass { pass {
group = "javax.servlet" group = "javax.servlet"
module = "servlet-api" module = "servlet-api"
versions = "[2.3, 3.0)" versions = "[2.2, 3.0)"
assertInverse = true assertInverse = true
} }
@ -23,7 +23,7 @@ testSets {
} }
dependencies { dependencies {
compileOnly group: 'javax.servlet', name: 'servlet-api', version: '2.3' compileOnly group: 'javax.servlet', name: 'servlet-api', version: '2.2'
testCompile(project(':testing')) { testCompile(project(':testing')) {
exclude group: 'org.eclipse.jetty', module: 'jetty-server' exclude group: 'org.eclipse.jetty', module: 'jetty-server'

View File

@ -43,8 +43,8 @@ public class Servlet2Advice {
public static SpanWithScope onEnter( public static SpanWithScope onEnter(
@Advice.This final Object servlet, @Advice.This final Object servlet,
@Advice.Argument(0) final ServletRequest request, @Advice.Argument(0) final ServletRequest request,
@Advice.Argument(value = 1, readOnly = false, typing = Assigner.Typing.DYNAMIC) @Advice.Argument(value = 1, typing = Assigner.Typing.DYNAMIC)
ServletResponse response) { final ServletResponse response) {
final boolean hasServletTrace = request.getAttribute(SPAN_ATTRIBUTE) instanceof Span; final boolean hasServletTrace = request.getAttribute(SPAN_ATTRIBUTE) instanceof Span;
final boolean invalidRequest = !(request instanceof HttpServletRequest); final boolean invalidRequest = !(request instanceof HttpServletRequest);
if (invalidRequest || hasServletTrace) { if (invalidRequest || hasServletTrace) {
@ -59,7 +59,8 @@ public class Servlet2Advice {
InstrumentationContext.get(HttpServletResponse.class, HttpServletRequest.class) InstrumentationContext.get(HttpServletResponse.class, HttpServletRequest.class)
.put((HttpServletResponse) response, httpServletRequest); .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 = final Span.Builder builder =
@ -99,10 +100,17 @@ public class Servlet2Advice {
return; return;
} }
final Span span = spanWithScope.getSpan(); 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 (throwable != null) {
if (response instanceof StatusSavingHttpServletResponseWrapper if (response instanceof HttpServletResponse
&& ((StatusSavingHttpServletResponseWrapper) response).status && InstrumentationContext.get(ServletResponse.class, Integer.class).get(response)
== HttpServletResponse.SC_OK) { == HttpServletResponse.SC_OK) {
// exception was thrown but status code wasn't set // exception was thrown but status code wasn't set
span.setAttribute(Tags.HTTP_STATUS, 500); span.setAttribute(Tags.HTTP_STATUS, 500);

View File

@ -21,11 +21,10 @@ import io.opentelemetry.trace.Span;
import io.opentelemetry.trace.Tracer; import io.opentelemetry.trace.Tracer;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
public class Servlet2Decorator public class Servlet2Decorator
extends HttpServerDecorator<HttpServletRequest, HttpServletRequest, ServletResponse> { extends HttpServerDecorator<HttpServletRequest, HttpServletRequest, Integer> {
public static final Servlet2Decorator DECORATE = new Servlet2Decorator(); public static final Servlet2Decorator DECORATE = new Servlet2Decorator();
public static final Tracer TRACER = public static final Tracer TRACER =
OpenTelemetry.getTracerProvider().get("io.opentelemetry.auto.servlet-2.3"); OpenTelemetry.getTracerProvider().get("io.opentelemetry.auto.servlet-2.3");
@ -59,13 +58,8 @@ public class Servlet2Decorator
} }
@Override @Override
protected Integer status(final ServletResponse httpServletResponse) { protected Integer status(final Integer status) {
if (httpServletResponse instanceof StatusSavingHttpServletResponseWrapper) { return status;
return ((StatusSavingHttpServletResponseWrapper) httpServletResponse).status;
} else {
// HttpServletResponse doesn't have accessor for status code.
return null;
}
} }
@Override @Override

View File

@ -25,6 +25,7 @@ import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
import com.google.auto.service.AutoService; import com.google.auto.service.AutoService;
import io.opentelemetry.auto.tooling.Instrumenter; import io.opentelemetry.auto.tooling.Instrumenter;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.description.type.TypeDescription;
@ -52,16 +53,17 @@ public final class Servlet2Instrumentation extends Instrumenter.Default {
@Override @Override
public String[] helperClassNames() { public String[] helperClassNames() {
return new String[] { return new String[] {
packageName + ".Servlet2Decorator", packageName + ".Servlet2Decorator", packageName + ".HttpServletRequestExtractAdapter",
packageName + ".HttpServletRequestExtractAdapter",
packageName + ".StatusSavingHttpServletResponseWrapper",
}; };
} }
@Override @Override
public Map<String, String> contextStore() { public Map<String, String> contextStore() {
return singletonMap( final Map<String, String> contextStores = new HashMap<>();
contextStores.put(
"javax.servlet.http.HttpServletResponse", "javax.servlet.http.HttpServletRequest"); "javax.servlet.http.HttpServletResponse", "javax.servlet.http.HttpServletRequest");
contextStores.put("javax.servlet.ServletResponse", Integer.class.getName());
return contextStores;
} }
/** /**

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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<ClassLoader> classLoaderMatcher() {
return not(hasClassesNamed("javax.servlet.AsyncEvent", "javax.servlet.AsyncListener"));
}
@Override
public ElementMatcher<? super TypeDescription> typeMatcher() {
return safeHasSuperType(named("javax.servlet.http.HttpServletResponse"));
}
@Override
public Map<String, String> contextStore() {
return singletonMap("javax.servlet.ServletResponse", Integer.class.getName());
}
/**
* Unlike Servlet2Instrumentation it doesn't matter if the HttpServletResponseInstrumentation
* applies first
*/
@Override
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
final Map<ElementMatcher<? super MethodDescription>, String> transformers = new HashMap<>();
transformers.put(
named("sendError").or(named("setStatus")), packageName + ".Servlet2ResponseStatusAdvice");
transformers.put(named("sendRedirect"), packageName + ".Servlet2ResponseRedirectAdvice");
return transformers;
}
}

View File

@ -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);
}
}

View File

@ -88,7 +88,7 @@ public class Servlet3Decorator
*/ */
private void onContext( private void onContext(
final Span span, final HttpServletRequest request, final ServletContext context) { 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) { if (attribute instanceof Filter) {
final Object priorAttr = request.getAttribute(SPAN_ATTRIBUTE); final Object priorAttr = request.getAttribute(SPAN_ATTRIBUTE);
request.setAttribute(SPAN_ATTRIBUTE, span); request.setAttribute(SPAN_ATTRIBUTE, span);

View File

@ -120,8 +120,8 @@ public final class DispatcherServletInstrumentation extends Instrumenter.Default
contextStore.put(dispatcher, filter); contextStore.put(dispatcher, filter);
} }
filter.setHandlerMappings(handlerMappings); filter.setHandlerMappings(handlerMappings);
servletContext.setAttribute( // attribute used by Servlet3Decorator.onContext
"ota.dispatcher-filter", filter); // used by Servlet3Decorator.onContext servletContext.setAttribute("io.opentelemetry.auto.dispatcher-filter", filter);
} }
} }
} }

View File

@ -99,7 +99,8 @@ include ':instrumentation:jms-1.1'
include ':instrumentation:jsp-2.3' include ':instrumentation:jsp-2.3'
include ':instrumentation:kafka-clients-0.11' include ':instrumentation:kafka-clients-0.11'
include ':instrumentation:kafka-streams-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-1.1'
include ':instrumentation:log4j:log4j-2.0' include ':instrumentation:log4j:log4j-2.0'
include ':instrumentation:logback-1.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:rabbitmq-amqp-2.7'
include ':instrumentation:ratpack-1.5' include ':instrumentation:ratpack-1.5'
include ':instrumentation:reactor-3.1' include ':instrumentation:reactor-3.1'
include ':instrumentation:rediscala-1.8'
include ':instrumentation:rmi' include ':instrumentation:rmi'
include ':instrumentation:rxjava-1.0' include ':instrumentation:rxjava-1.0'
include ':instrumentation:servlet' include ':instrumentation:servlet'