Merge tag 'v0.50.0' into correct-history
This commit is contained in:
commit
6d1a58d151
|
@ -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:
|
||||
|
|
|
@ -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 }}`
|
||||
})
|
|
@ -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 }}
|
|
@ -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+ |
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
|
||||
final Map<ElementMatcher<? super MethodDescription>, 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)),
|
||||
|
|
|
@ -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<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) {
|
||||
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();
|
||||
}
|
||||
|
|
|
@ -76,7 +76,7 @@ public class JDBCDecorator extends DatabaseClientDecorator<DBInfo> {
|
|||
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);
|
||||
}
|
||||
|
|
|
@ -266,7 +266,7 @@ class TestConnection implements Connection {
|
|||
|
||||
@Override
|
||||
Properties getClientInfo() throws SQLException {
|
||||
throw new UnsupportedOperationException("Test 123")
|
||||
throw new Throwable("Test 123")
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -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.+'
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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<T extends Object, U extends Throwable, R ext
|
|||
if (throwable instanceof CancellationException) {
|
||||
span.setAttribute("db.command.cancelled", true);
|
||||
} else {
|
||||
DECORATE.onError(span, throwable);
|
||||
LettuceClientDecorator.DECORATE.onError(span, throwable);
|
||||
}
|
||||
DECORATE.beforeFinish(span);
|
||||
LettuceClientDecorator.DECORATE.beforeFinish(span);
|
||||
span.end();
|
||||
return null;
|
||||
}
|
|
@ -13,11 +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.auto.instrumentation.lettuce.LettuceInstrumentationUtil.doFinishSpanEarly;
|
||||
import static io.opentelemetry.trace.Span.Kind.CLIENT;
|
||||
import static io.opentelemetry.trace.TracingContextUtils.currentContextWith;
|
||||
|
||||
|
@ -33,11 +30,11 @@ public class LettuceAsyncCommandsAdvice {
|
|||
public static SpanWithScope onEnter(@Advice.Argument(0) final RedisCommand command) {
|
||||
|
||||
final Span span =
|
||||
TRACER
|
||||
LettuceClientDecorator.TRACER
|
||||
.spanBuilder(LettuceInstrumentationUtil.getCommandName(command))
|
||||
.setSpanKind(CLIENT)
|
||||
.startSpan();
|
||||
DECORATE.afterStart(span);
|
||||
LettuceClientDecorator.DECORATE.afterStart(span);
|
||||
|
||||
return new SpanWithScope(span, currentContextWith(span));
|
||||
}
|
||||
|
@ -51,15 +48,15 @@ public class LettuceAsyncCommandsAdvice {
|
|||
|
||||
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;
|
||||
}
|
||||
|
||||
// close spans on error or normal completion
|
||||
if (doFinishSpanEarly(command)) {
|
||||
if (LettuceInstrumentationUtil.doFinishSpanEarly(command)) {
|
||||
span.end();
|
||||
} else {
|
||||
asyncCommand.handleAsync(new LettuceAsyncBiFunction<>(span));
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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: '+'
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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'
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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<HttpServletRequest, HttpServletRequest, ServletResponse> {
|
||||
extends HttpServerDecorator<HttpServletRequest, HttpServletRequest, Integer> {
|
||||
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
|
||||
|
|
|
@ -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<String, String> contextStore() {
|
||||
return singletonMap(
|
||||
final Map<String, String> contextStores = new HashMap<>();
|
||||
contextStores.put(
|
||||
"javax.servlet.http.HttpServletResponse", "javax.servlet.http.HttpServletRequest");
|
||||
contextStores.put("javax.servlet.ServletResponse", Integer.class.getName());
|
||||
return contextStores;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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'
|
||||
|
|
Loading…
Reference in New Issue