WIP Twilio SDK Instrumentation
Missed Gradle file Updates to handle async calls, which have broken all tests
This commit is contained in:
parent
e5e2c5b9dc
commit
13aa267d84
|
@ -69,6 +69,9 @@ public abstract class AbstractExecutorInstrumentation extends Instrumenter.Defau
|
||||||
"io.netty.util.concurrent.SingleThreadEventExecutor",
|
"io.netty.util.concurrent.SingleThreadEventExecutor",
|
||||||
"io.netty.channel.nio.NioEventLoop",
|
"io.netty.channel.nio.NioEventLoop",
|
||||||
"io.netty.channel.SingleThreadEventLoop",
|
"io.netty.channel.SingleThreadEventLoop",
|
||||||
|
"com.google.common.util.concurrent.AbstractListeningExecutorService",
|
||||||
|
"com.google.common.util.concurrent.MoreExecutors$ListeningDecorator",
|
||||||
|
"com.google.common.util.concurrent.MoreExecutors$ScheduledListeningDecorator",
|
||||||
};
|
};
|
||||||
WHITELISTED_EXECUTORS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(whitelist)));
|
WHITELISTED_EXECUTORS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(whitelist)));
|
||||||
|
|
||||||
|
|
|
@ -62,7 +62,8 @@ public final class FutureInstrumentation extends Instrumenter.Default {
|
||||||
"com.google.common.util.concurrent.SettableFuture",
|
"com.google.common.util.concurrent.SettableFuture",
|
||||||
"com.google.common.util.concurrent.AbstractFuture$TrustedFuture",
|
"com.google.common.util.concurrent.AbstractFuture$TrustedFuture",
|
||||||
"com.google.common.util.concurrent.AbstractFuture",
|
"com.google.common.util.concurrent.AbstractFuture",
|
||||||
"io.netty.util.concurrent.ScheduledFutureTask"
|
"io.netty.util.concurrent.ScheduledFutureTask",
|
||||||
|
"com.google.common.util.concurrent.ListenableFutureTask"
|
||||||
};
|
};
|
||||||
WHITELISTED_FUTURES = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(whitelist)));
|
WHITELISTED_FUTURES = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(whitelist)));
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,95 @@
|
||||||
|
package datadog.trace.instrumentation.twilio;
|
||||||
|
|
||||||
|
import com.twilio.rest.api.v2010.account.Call;
|
||||||
|
import com.twilio.rest.api.v2010.account.Message;
|
||||||
|
import datadog.trace.agent.decorator.ClientDecorator;
|
||||||
|
import datadog.trace.api.DDSpanTypes;
|
||||||
|
import datadog.trace.api.DDTags;
|
||||||
|
import io.opentracing.Span;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
|
/** Decorate Twilio span's with relevant contextual information. */
|
||||||
|
public class TwilioClientDecorator extends ClientDecorator {
|
||||||
|
|
||||||
|
public static final TwilioClientDecorator DECORATE = new TwilioClientDecorator();
|
||||||
|
|
||||||
|
static final String COMPONENT_NAME = "twilio-sdk";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String spanType() {
|
||||||
|
return DDSpanTypes.HTTP_CLIENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String[] instrumentationNames() {
|
||||||
|
return new String[] {COMPONENT_NAME};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String component() {
|
||||||
|
return COMPONENT_NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String service() {
|
||||||
|
return COMPONENT_NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Decorate trace based on service execution metadata */
|
||||||
|
public Span onServiceExecution(
|
||||||
|
final Span span, final Object serviceExecutor, final String methodName) {
|
||||||
|
|
||||||
|
// Drop common package prefix (com.twilio.rest)
|
||||||
|
final String simpleClassName =
|
||||||
|
serviceExecutor.getClass().getCanonicalName().replaceFirst("^com\\.twilio\\.rest\\.", "");
|
||||||
|
|
||||||
|
span.setTag(DDTags.RESOURCE_NAME, String.format("%s.%s", simpleClassName, methodName));
|
||||||
|
|
||||||
|
return span;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Annotate the span with the results of the operation. */
|
||||||
|
public Span onResult(final Span span, final Object result) {
|
||||||
|
|
||||||
|
// Provide helpful metadata for some of the more common response types
|
||||||
|
span.setTag("twilio.type", result.getClass().getCanonicalName());
|
||||||
|
// Instrument the most popular resource types directly
|
||||||
|
if (result instanceof Message) {
|
||||||
|
final Message message = (Message) result;
|
||||||
|
span.setTag("twilio.type", result.getClass().getCanonicalName());
|
||||||
|
span.setTag("twilio.account", message.getAccountSid());
|
||||||
|
span.setTag("twilio.sid", message.getSid());
|
||||||
|
span.setTag("twilio.status", message.getStatus().toString());
|
||||||
|
} else if (result instanceof Call) {
|
||||||
|
final Call call = (Call) result;
|
||||||
|
span.setTag("twilio.account", call.getAccountSid());
|
||||||
|
span.setTag("twilio.sid", call.getSid());
|
||||||
|
span.setTag("twilio.parentSid", call.getParentCallSid());
|
||||||
|
span.setTag("twilio.status", call.getStatus().toString());
|
||||||
|
} else {
|
||||||
|
// Use reflection to gather insight from other types; note that Twilio requests take close to
|
||||||
|
// 1 second, so the added hit from reflection here is relatively minimal in the grand scheme
|
||||||
|
// of things
|
||||||
|
setTagIfPresent(span, result, "twilio.sid", "getSid");
|
||||||
|
setTagIfPresent(span, result, "twilio.account", "getAccountSid");
|
||||||
|
setTagIfPresent(span, result, "twilio.status", "getStatus");
|
||||||
|
}
|
||||||
|
|
||||||
|
return span;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setTagIfPresent(
|
||||||
|
final Span span, final Object result, final String tag, final String getter) {
|
||||||
|
try {
|
||||||
|
final Method method = result.getClass().getMethod(getter);
|
||||||
|
final Object value = method.invoke(result);
|
||||||
|
|
||||||
|
if (value != null) {
|
||||||
|
span.setTag(tag, value.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (final Exception e) {
|
||||||
|
// Expected that this won't work for all result types
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,165 @@
|
||||||
|
package datadog.trace.instrumentation.twilio;
|
||||||
|
|
||||||
|
import static datadog.trace.agent.tooling.ByteBuddyElementMatchers.safeHasSuperType;
|
||||||
|
import static datadog.trace.instrumentation.twilio.TwilioClientDecorator.DECORATE;
|
||||||
|
import static java.util.Collections.singletonMap;
|
||||||
|
import static net.bytebuddy.matcher.ElementMatchers.isAbstract;
|
||||||
|
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
|
||||||
|
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
|
||||||
|
import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith;
|
||||||
|
import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||||
|
import static net.bytebuddy.matcher.ElementMatchers.not;
|
||||||
|
|
||||||
|
import com.google.auto.service.AutoService;
|
||||||
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
|
import com.google.common.util.concurrent.Futures;
|
||||||
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
import com.twilio.Twilio;
|
||||||
|
import datadog.trace.agent.tooling.Instrumenter;
|
||||||
|
import datadog.trace.bootstrap.CallDepthThreadLocalMap;
|
||||||
|
import datadog.trace.context.TraceScope;
|
||||||
|
import io.opentracing.Scope;
|
||||||
|
import io.opentracing.Span;
|
||||||
|
import io.opentracing.Tracer;
|
||||||
|
import io.opentracing.util.GlobalTracer;
|
||||||
|
import java.util.Map;
|
||||||
|
import net.bytebuddy.asm.Advice;
|
||||||
|
import net.bytebuddy.description.method.MethodDescription;
|
||||||
|
import net.bytebuddy.matcher.ElementMatcher;
|
||||||
|
|
||||||
|
/** Instrument the Twilio SDK to identify calls as a seperate service. */
|
||||||
|
@AutoService(Instrumenter.class)
|
||||||
|
public class TwilioInstrumentation extends Instrumenter.Default {
|
||||||
|
|
||||||
|
public TwilioInstrumentation() {
|
||||||
|
super("twilio-sdk");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public net.bytebuddy.matcher.ElementMatcher<
|
||||||
|
? super net.bytebuddy.description.type.TypeDescription>
|
||||||
|
typeMatcher() {
|
||||||
|
return safeHasSuperType(
|
||||||
|
named("com.twilio.base.Creator")
|
||||||
|
.or(named("com.twilio.base.Deleter"))
|
||||||
|
.or(named("com.twilio.base.Fetcher"))
|
||||||
|
.or(named("com.twilio.base.Reader"))
|
||||||
|
.or(named("com.twilio.base.Updater")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] helperClassNames() {
|
||||||
|
return new String[] {
|
||||||
|
"datadog.trace.agent.decorator.BaseDecorator",
|
||||||
|
"datadog.trace.agent.decorator.ClientDecorator",
|
||||||
|
packageName + ".TwilioClientDecorator",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
|
||||||
|
|
||||||
|
return singletonMap(
|
||||||
|
isMethod()
|
||||||
|
.and(isPublic())
|
||||||
|
.and(not(isAbstract()))
|
||||||
|
.and(
|
||||||
|
nameStartsWith("create")
|
||||||
|
.or(nameStartsWith("delete"))
|
||||||
|
.or(nameStartsWith("read"))
|
||||||
|
.or(nameStartsWith("fetch"))
|
||||||
|
.or(nameStartsWith("update"))),
|
||||||
|
TwilioClientAdvice.class.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class TwilioClientAdvice {
|
||||||
|
|
||||||
|
/** Method entry instrumentation */
|
||||||
|
@Advice.OnMethodEnter(suppress = Throwable.class)
|
||||||
|
public static Scope methodEnter(
|
||||||
|
@Advice.This final Object that, @Advice.Origin("#m") final String methodName) {
|
||||||
|
|
||||||
|
final int callDepth = CallDepthThreadLocalMap.incrementCallDepth(Twilio.class);
|
||||||
|
if (callDepth > 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final boolean isAsync = methodName.endsWith("Async");
|
||||||
|
|
||||||
|
final Tracer tracer = GlobalTracer.get();
|
||||||
|
final Scope scope = tracer.buildSpan("twilio.sdk").startActive(!isAsync);
|
||||||
|
final Span span = scope.span();
|
||||||
|
|
||||||
|
DECORATE.afterStart(span);
|
||||||
|
DECORATE.onServiceExecution(span, that, methodName);
|
||||||
|
|
||||||
|
if (scope instanceof TraceScope) {
|
||||||
|
((TraceScope) scope).setAsyncPropagation(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return scope;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Method exit instrumentation */
|
||||||
|
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
|
||||||
|
public static void methodExit(
|
||||||
|
@Advice.Enter final Scope scope,
|
||||||
|
@Advice.Thrown final Throwable throwable,
|
||||||
|
@Advice.Return final Object response,
|
||||||
|
@Advice.Origin("#m") final String methodName) {
|
||||||
|
if (scope != null) {
|
||||||
|
try {
|
||||||
|
final boolean isAsync = methodName.endsWith("Async");
|
||||||
|
System.err.println("isAsync = " + isAsync);
|
||||||
|
|
||||||
|
final Span span = scope.span();
|
||||||
|
|
||||||
|
DECORATE.onError(span, throwable);
|
||||||
|
DECORATE.beforeFinish(span);
|
||||||
|
|
||||||
|
// If we're calling an async operation, we still need to finish the span when it's
|
||||||
|
// complete; set an appropriate callback
|
||||||
|
if (isAsync && response instanceof ListenableFuture) {
|
||||||
|
Futures.addCallback(
|
||||||
|
(ListenableFuture) response,
|
||||||
|
new SpanFinishingCallback(span),
|
||||||
|
Twilio.getExecutorService());
|
||||||
|
} else {
|
||||||
|
DECORATE.onResult(span, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
scope.close();
|
||||||
|
CallDepthThreadLocalMap.reset(Twilio.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FutureCallback, which automatically finishes the span and annotates with any appropriate
|
||||||
|
* metadata on a potential failure.
|
||||||
|
*/
|
||||||
|
private static class SpanFinishingCallback implements FutureCallback {
|
||||||
|
|
||||||
|
private final Span span;
|
||||||
|
|
||||||
|
SpanFinishingCallback(final Span span) {
|
||||||
|
this.span = span;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(final Object result) {
|
||||||
|
DECORATE.beforeFinish(span);
|
||||||
|
DECORATE.onResult(span, result);
|
||||||
|
span.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(final Throwable t) {
|
||||||
|
DECORATE.onError(span, t);
|
||||||
|
DECORATE.beforeFinish(span);
|
||||||
|
span.finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,244 @@
|
||||||
|
package test
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
|
import com.google.common.util.concurrent.ListenableFuture
|
||||||
|
import com.twilio.Twilio
|
||||||
|
import com.twilio.exception.ApiException
|
||||||
|
import com.twilio.http.Response
|
||||||
|
import com.twilio.http.TwilioRestClient
|
||||||
|
import com.twilio.rest.api.v2010.account.Message
|
||||||
|
import com.twilio.type.PhoneNumber
|
||||||
|
import datadog.trace.agent.test.AgentTestRunner
|
||||||
|
import datadog.trace.api.DDSpanTypes
|
||||||
|
import io.opentracing.util.GlobalTracer
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
class TwilioClientTest extends AgentTestRunner {
|
||||||
|
|
||||||
|
final static String ACCOUNT_SID = "abc"
|
||||||
|
final static String AUTH_TOKEN = "efg"
|
||||||
|
|
||||||
|
final static String MESSAGE_RESPONSE_BODY = """
|
||||||
|
{
|
||||||
|
"account_sid": "AC14984e09e497506cf0d5eb59b1f6ace7",
|
||||||
|
"api_version": "2010-04-01",
|
||||||
|
"body": "Hello, World!",
|
||||||
|
"date_created": "Thu, 30 Jul 2015 20:12:31 +0000",
|
||||||
|
"date_sent": "Thu, 30 Jul 2015 20:12:33 +0000",
|
||||||
|
"date_updated": "Thu, 30 Jul 2015 20:12:33 +0000",
|
||||||
|
"direction": "outbound-api",
|
||||||
|
"from": "+14155552345",
|
||||||
|
"messaging_service_sid": "MGXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
|
||||||
|
"num_media": "0",
|
||||||
|
"num_segments": "1",
|
||||||
|
"price": -0.00750,
|
||||||
|
"price_unit": "USD",
|
||||||
|
"sid": "MMXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
|
||||||
|
"status": "sent",
|
||||||
|
"subresource_uris": {
|
||||||
|
"media": "/2010-04-01/Accounts/AC14984e09e497506cf0d5eb59b1f6ace7/Messages/SMXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Media.json"
|
||||||
|
},
|
||||||
|
"to": "+14155552345",
|
||||||
|
"uri": "/2010-04-01/Accounts/AC14984e09e497506cf0d5eb59b1f6ace7/Messages/SMXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.json"
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
TwilioRestClient twilioRestClient = Mock()
|
||||||
|
|
||||||
|
def setupSpec() {
|
||||||
|
Twilio.init(ACCOUNT_SID, AUTH_TOKEN)
|
||||||
|
}
|
||||||
|
|
||||||
|
def "synchronous call"() {
|
||||||
|
setup:
|
||||||
|
twilioRestClient.getObjectMapper() >> new ObjectMapper()
|
||||||
|
|
||||||
|
1 * twilioRestClient.request(_) >> new Response(new ByteArrayInputStream(MESSAGE_RESPONSE_BODY.getBytes()), 200)
|
||||||
|
|
||||||
|
GlobalTracer.get().buildSpan("test").startActive(true)
|
||||||
|
|
||||||
|
Message message = Message.creator(
|
||||||
|
new PhoneNumber("+1 555 720 5913"), // To number
|
||||||
|
new PhoneNumber("+1 555 555 5215"), // From number
|
||||||
|
"Hello world!" // SMS body
|
||||||
|
).create(twilioRestClient)
|
||||||
|
|
||||||
|
Thread.sleep(1000);
|
||||||
|
|
||||||
|
def scope = GlobalTracer.get().scopeManager().active()
|
||||||
|
if (scope) {
|
||||||
|
scope.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
expect:
|
||||||
|
|
||||||
|
message.body == "Hello, World!"
|
||||||
|
|
||||||
|
assertTraces(1) {
|
||||||
|
trace(0, 2) {
|
||||||
|
span(0) {
|
||||||
|
serviceName "unnamed-java-app"
|
||||||
|
operationName "test"
|
||||||
|
resourceName "test"
|
||||||
|
errored false
|
||||||
|
parent()
|
||||||
|
}
|
||||||
|
span(1) {
|
||||||
|
serviceName "twilio-sdk"
|
||||||
|
operationName "twilio.sdk"
|
||||||
|
resourceName "api.v2010.account.MessageCreator.create"
|
||||||
|
spanType DDSpanTypes.HTTP_CLIENT
|
||||||
|
errored false
|
||||||
|
}
|
||||||
|
tags {
|
||||||
|
"twilio.sid" "MMXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def "500 Error"() {
|
||||||
|
setup:
|
||||||
|
final String errorResponse = """
|
||||||
|
{
|
||||||
|
"code": 123,
|
||||||
|
"message": "Testing Failure",
|
||||||
|
"code": 567,
|
||||||
|
"more_info": "Testing"
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
twilioRestClient.getObjectMapper() >> new ObjectMapper()
|
||||||
|
|
||||||
|
1 * twilioRestClient.request(_) >> new Response(new ByteArrayInputStream(errorResponse.getBytes()), 500)
|
||||||
|
|
||||||
|
GlobalTracer.get().buildSpan("test").startActive(true)
|
||||||
|
|
||||||
|
when:
|
||||||
|
Message.creator(
|
||||||
|
new PhoneNumber("+1 555 720 5913"), // To number
|
||||||
|
new PhoneNumber("+1 555 555 5215"), // From number
|
||||||
|
"Hello world!" // SMS body
|
||||||
|
).create(twilioRestClient)
|
||||||
|
|
||||||
|
then:
|
||||||
|
thrown(ApiException)
|
||||||
|
|
||||||
|
Thread.sleep(1000);
|
||||||
|
|
||||||
|
def scope = GlobalTracer.get().scopeManager().active()
|
||||||
|
if (scope) {
|
||||||
|
scope.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
expect:
|
||||||
|
|
||||||
|
assertTraces(1) {
|
||||||
|
trace(0, 2) {
|
||||||
|
span(0) {
|
||||||
|
serviceName "unnamed-java-app"
|
||||||
|
operationName "test"
|
||||||
|
resourceName "test"
|
||||||
|
errored false
|
||||||
|
parent()
|
||||||
|
}
|
||||||
|
span(1) {
|
||||||
|
serviceName "twilio-sdk"
|
||||||
|
operationName "twilio.sdk"
|
||||||
|
resourceName "api.v2010.account.MessageCreator.create"
|
||||||
|
spanType DDSpanTypes.HTTP_CLIENT
|
||||||
|
errored true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def "root span"() {
|
||||||
|
setup:
|
||||||
|
twilioRestClient.getObjectMapper() >> new ObjectMapper()
|
||||||
|
|
||||||
|
1 * twilioRestClient.request(_) >> new Response(new ByteArrayInputStream(MESSAGE_RESPONSE_BODY.getBytes()), 200)
|
||||||
|
|
||||||
|
Message message = Message.creator(
|
||||||
|
new PhoneNumber("+1 555 720 5913"), // To number
|
||||||
|
new PhoneNumber("+1 555 555 5215"), // From number
|
||||||
|
"Hello world!" // SMS body
|
||||||
|
).create(twilioRestClient)
|
||||||
|
|
||||||
|
expect:
|
||||||
|
|
||||||
|
Thread.sleep(1000);
|
||||||
|
|
||||||
|
message.body == "Hello, World!"
|
||||||
|
|
||||||
|
assertTraces(1) {
|
||||||
|
trace(0, 1) {
|
||||||
|
span(0) {
|
||||||
|
serviceName "twilio-sdk"
|
||||||
|
operationName "twilio.sdk"
|
||||||
|
resourceName "api.v2010.account.MessageCreator.create"
|
||||||
|
parent()
|
||||||
|
spanType DDSpanTypes.HTTP_CLIENT
|
||||||
|
errored false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def "asynchronous call"() {
|
||||||
|
setup:
|
||||||
|
twilioRestClient.getObjectMapper() >> new ObjectMapper()
|
||||||
|
|
||||||
|
1 * twilioRestClient.request(_) >> new Response(new ByteArrayInputStream(MESSAGE_RESPONSE_BODY.getBytes()), 200)
|
||||||
|
|
||||||
|
GlobalTracer.get().buildSpan("test").startActive(true)
|
||||||
|
|
||||||
|
ListenableFuture<Message> future = Message.creator(
|
||||||
|
new PhoneNumber("+1 555 720 5913"), // To number
|
||||||
|
new PhoneNumber("+1 555 555 5215"), // From number
|
||||||
|
"Hello world!" // SMS body
|
||||||
|
).createAsync(twilioRestClient)
|
||||||
|
|
||||||
|
Message message = future.get(10, TimeUnit.SECONDS)
|
||||||
|
|
||||||
|
Thread.sleep(1000);
|
||||||
|
|
||||||
|
def scope = GlobalTracer.get().scopeManager().active()
|
||||||
|
if (scope) {
|
||||||
|
scope.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
expect:
|
||||||
|
|
||||||
|
message != null
|
||||||
|
message.body == "Hello, World!"
|
||||||
|
|
||||||
|
assertTraces(1) {
|
||||||
|
trace(0, 3) {
|
||||||
|
span(0) {
|
||||||
|
serviceName "unnamed-java-app"
|
||||||
|
operationName "test"
|
||||||
|
resourceName "test"
|
||||||
|
errored false
|
||||||
|
parent()
|
||||||
|
}
|
||||||
|
span(1) {
|
||||||
|
serviceName "twilio-sdk"
|
||||||
|
operationName "twilio.sdk"
|
||||||
|
resourceName "api.v2010.account.MessageCreator.createAsync"
|
||||||
|
spanType DDSpanTypes.HTTP_CLIENT
|
||||||
|
errored false
|
||||||
|
}
|
||||||
|
span(2) {
|
||||||
|
serviceName "twilio-sdk"
|
||||||
|
operationName "twilio.sdk"
|
||||||
|
resourceName "api.v2010.account.MessageCreator.create"
|
||||||
|
spanType DDSpanTypes.HTTP_CLIENT
|
||||||
|
errored false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
muzzle {
|
||||||
|
pass {
|
||||||
|
group = 'com.twilio.sdk'
|
||||||
|
module = 'twilio'
|
||||||
|
versions = "(,)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: "${rootDir}/gradle/java.gradle"
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compileOnly group: 'com.twilio.sdk', name: 'twilio', version: '7.36.2'
|
||||||
|
|
||||||
|
compile project(':dd-java-agent:agent-tooling')
|
||||||
|
|
||||||
|
compile deps.bytebuddy
|
||||||
|
compile deps.opentracing
|
||||||
|
annotationProcessor deps.autoservice
|
||||||
|
implementation deps.autoservice
|
||||||
|
|
||||||
|
testCompile group: 'com.twilio.sdk', name: 'twilio', version: '7.36.2'
|
||||||
|
testCompile project(':dd-java-agent:testing')
|
||||||
|
testCompile project(':dd-java-agent:instrumentation:java-concurrent')
|
||||||
|
testCompile group: 'org.objenesis', name: 'objenesis', version: '3.0.1'
|
||||||
|
}
|
|
@ -83,6 +83,7 @@ include ':dd-java-agent:instrumentation:spring-webflux'
|
||||||
include ':dd-java-agent:instrumentation:spymemcached-2.12'
|
include ':dd-java-agent:instrumentation:spymemcached-2.12'
|
||||||
include ':dd-java-agent:instrumentation:tomcat-classloading'
|
include ':dd-java-agent:instrumentation:tomcat-classloading'
|
||||||
include ':dd-java-agent:instrumentation:trace-annotation'
|
include ':dd-java-agent:instrumentation:trace-annotation'
|
||||||
|
include ':dd-java-agent:instrumentation:twilio'
|
||||||
include ':dd-java-agent:instrumentation:vertx'
|
include ':dd-java-agent:instrumentation:vertx'
|
||||||
|
|
||||||
// benchmark
|
// benchmark
|
||||||
|
@ -99,3 +100,4 @@ def setBuildFile(project) {
|
||||||
}
|
}
|
||||||
|
|
||||||
setBuildFile(rootProject)
|
setBuildFile(rootProject)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue