Transition twilio-6.6 module to instrumenter API (#4045)

* Convert twilio-6.6 module to instrumenter API

* Rename TwilioTracer to TwilioSingletons
This commit is contained in:
jack-berg 2021-09-08 15:10:03 -05:00 committed by GitHub
parent 4820ec4855
commit 28e5cb5dd6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 163 additions and 152 deletions

View File

@ -8,7 +8,8 @@ package io.opentelemetry.javaagent.instrumentation.twilio;
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.extendsClass;
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
import static io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge.currentContext;
import static io.opentelemetry.javaagent.instrumentation.twilio.TwilioTracer.tracer;
import static io.opentelemetry.javaagent.instrumentation.twilio.TwilioSingletons.instrumenter;
import static io.opentelemetry.javaagent.instrumentation.twilio.TwilioSingletons.spanName;
import static net.bytebuddy.matcher.ElementMatchers.isAbstract;
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
@ -76,13 +77,15 @@ public class TwilioAsyncInstrumentation implements TypeInstrumentation {
@Advice.This Object that,
@Advice.Origin("#m") String methodName,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {
@Advice.Local("otelScope") Scope scope,
@Advice.Local("otelSpanName") String spanName) {
Context parentContext = currentContext();
if (!tracer().shouldStartSpan(parentContext)) {
spanName = spanName(that, methodName);
if (!instrumenter().shouldStart(parentContext, spanName)) {
return;
}
context = tracer().startSpan(parentContext, that, methodName);
context = instrumenter().start(parentContext, spanName);
scope = context.makeCurrent();
}
@ -92,7 +95,8 @@ public class TwilioAsyncInstrumentation implements TypeInstrumentation {
@Advice.Thrown Throwable throwable,
@Advice.Return ListenableFuture<?> response,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {
@Advice.Local("otelScope") Scope scope,
@Advice.Local("otelSpanName") String spanName) {
if (scope == null) {
return;
}
@ -101,12 +105,12 @@ public class TwilioAsyncInstrumentation implements TypeInstrumentation {
if (throwable != null) {
// There was an synchronous error,
// which means we shouldn't wait for a callback to close the span.
tracer().endExceptionally(context, throwable);
instrumenter().end(context, spanName, null, throwable);
} else {
// We're calling an async operation, we still need to finish the span when it's
// complete and report the results; set an appropriate callback
Futures.addCallback(
response, new SpanFinishingCallback<>(context), Twilio.getExecutorService());
response, new SpanFinishingCallback<>(context, spanName), Twilio.getExecutorService());
}
}
}
@ -120,18 +124,21 @@ public class TwilioAsyncInstrumentation implements TypeInstrumentation {
/** Span that we should finish and annotate when the future is complete. */
private final Context context;
public SpanFinishingCallback(Context context) {
private final String spanName;
public SpanFinishingCallback(Context context, String spanName) {
this.context = context;
this.spanName = spanName;
}
@Override
public void onSuccess(Object result) {
tracer().end(context, result);
instrumenter().end(context, spanName, result, null);
}
@Override
public void onFailure(Throwable t) {
tracer().endExceptionally(context, t);
instrumenter().end(context, spanName, null, t);
}
}
}

View File

@ -0,0 +1,94 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.twilio;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.Uninterruptibles;
import com.twilio.rest.api.v2010.account.Call;
import com.twilio.rest.api.v2010.account.Message;
import io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
class TwilioExperimentalAttributesExtractor extends AttributesExtractor<String, Object> {
private static final Logger logger =
LoggerFactory.getLogger(TwilioExperimentalAttributesExtractor.class);
@Override
protected void onStart(AttributesBuilder attributes, String s) {}
@Override
protected void onEnd(AttributesBuilder attributes, String s, @Nullable Object result) {
if (result == null) {
return;
}
// Unwrap ListenableFuture (if present)
if (result instanceof ListenableFuture) {
try {
result =
Uninterruptibles.getUninterruptibly(
(ListenableFuture<?>) result, 0, TimeUnit.MICROSECONDS);
} catch (Exception e) {
logger.debug("Error unwrapping result", e);
}
}
// Provide helpful metadata for some of the more common response types
attributes.put("twilio.type", result.getClass().getCanonicalName());
// Instrument the most popular resource types directly
if (result instanceof Message) {
Message message = (Message) result;
attributes.put("twilio.account", message.getAccountSid());
attributes.put("twilio.sid", message.getSid());
Message.Status status = message.getStatus();
if (status != null) {
attributes.put("twilio.status", status.toString());
}
} else if (result instanceof Call) {
Call call = (Call) result;
attributes.put("twilio.account", call.getAccountSid());
attributes.put("twilio.sid", call.getSid());
attributes.put("twilio.parentSid", call.getParentCallSid());
Call.Status status = call.getStatus();
if (status != null) {
attributes.put("twilio.status", status.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(attributes, result, "twilio.sid", "getSid");
setTagIfPresent(attributes, result, "twilio.account", "getAccountSid");
setTagIfPresent(attributes, result, "twilio.status", "getStatus");
}
}
/**
* Helper method for calling a getter using reflection. This will be slow, so only use when
* required.
*/
private static void setTagIfPresent(
AttributesBuilder attributes, Object result, String tag, String getter) {
try {
Method method = result.getClass().getMethod(getter);
Object value = method.invoke(result);
if (value != null) {
attributes.put(tag, value.toString());
}
} catch (Exception e) {
// Expected that this won't work for all result types
}
}
}

View File

@ -0,0 +1,43 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.twilio;
import static io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor.alwaysClient;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.instrumentation.api.config.Config;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder;
import io.opentelemetry.instrumentation.api.tracer.SpanNames;
public class TwilioSingletons {
private static final boolean CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES =
Config.get().getBoolean("otel.instrumentation.twilio.experimental-span-attributes", false);
private static final Instrumenter<String, Object> INSTRUMENTER;
static {
InstrumenterBuilder<String, Object> instrumenterBuilder =
Instrumenter.<String, Object>newBuilder(
GlobalOpenTelemetry.get(), "io.opentelemetry.twilio-6.6", str -> str);
if (CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES) {
instrumenterBuilder.addAttributesExtractor(new TwilioExperimentalAttributesExtractor());
}
INSTRUMENTER = instrumenterBuilder.newInstrumenter(alwaysClient());
}
public static Instrumenter<String, Object> instrumenter() {
return INSTRUMENTER;
}
/** Derive span name from service execution metadata. */
public static String spanName(Object serviceExecutor, String methodName) {
return SpanNames.fromMethod(serviceExecutor.getClass(), methodName);
}
}

View File

@ -8,7 +8,7 @@ package io.opentelemetry.javaagent.instrumentation.twilio;
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.extendsClass;
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
import static io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge.currentContext;
import static io.opentelemetry.javaagent.instrumentation.twilio.TwilioTracer.tracer;
import static io.opentelemetry.javaagent.instrumentation.twilio.TwilioSingletons.instrumenter;
import static net.bytebuddy.matcher.ElementMatchers.isAbstract;
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
@ -69,13 +69,15 @@ public class TwilioSyncInstrumentation implements TypeInstrumentation {
@Advice.This Object that,
@Advice.Origin("#m") String methodName,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {
@Advice.Local("otelScope") Scope scope,
@Advice.Local("otelSpanName") String spanName) {
Context parentContext = currentContext();
if (!tracer().shouldStartSpan(parentContext)) {
spanName = TwilioSingletons.spanName(that, methodName);
if (!instrumenter().shouldStart(parentContext, spanName)) {
return;
}
context = tracer().startSpan(parentContext, that, methodName);
context = instrumenter().start(parentContext, spanName);
scope = context.makeCurrent();
}
@ -85,17 +87,14 @@ public class TwilioSyncInstrumentation implements TypeInstrumentation {
@Advice.Thrown Throwable throwable,
@Advice.Return Object response,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {
@Advice.Local("otelScope") Scope scope,
@Advice.Local("otelSpanName") String spanName) {
if (scope == null) {
return;
}
scope.close();
if (throwable != null) {
tracer().endExceptionally(context, throwable);
} else {
tracer().end(context, response);
}
instrumenter().end(context, spanName, response, throwable);
}
}
}

View File

@ -1,132 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.twilio;
import static io.opentelemetry.api.trace.SpanKind.CLIENT;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.Uninterruptibles;
import com.twilio.rest.api.v2010.account.Call;
import com.twilio.rest.api.v2010.account.Message;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.context.Context;
import io.opentelemetry.instrumentation.api.config.Config;
import io.opentelemetry.instrumentation.api.tracer.BaseTracer;
import io.opentelemetry.instrumentation.api.tracer.SpanNames;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TwilioTracer extends BaseTracer {
private static final boolean CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES =
Config.get().getBoolean("otel.instrumentation.twilio.experimental-span-attributes", false);
private static final Logger logger = LoggerFactory.getLogger(TwilioTracer.class);
public static final TwilioTracer TRACER = new TwilioTracer();
public static TwilioTracer tracer() {
return TRACER;
}
public boolean shouldStartSpan(Context parentContext) {
return shouldStartSpan(parentContext, CLIENT);
}
public Context startSpan(Context parentContext, Object serviceExecutor, String methodName) {
String spanName = spanNameOnServiceExecution(serviceExecutor, methodName);
Span span = spanBuilder(parentContext, spanName, CLIENT).startSpan();
return withClientSpan(parentContext, span);
}
/** Decorate trace based on service execution metadata. */
private static String spanNameOnServiceExecution(Object serviceExecutor, String methodName) {
return SpanNames.fromMethod(serviceExecutor.getClass(), methodName);
}
/** Annotate the span with the results of the operation. */
public void end(Context context, Object result) {
// Unwrap ListenableFuture (if present)
if (result instanceof ListenableFuture) {
try {
result =
Uninterruptibles.getUninterruptibly(
(ListenableFuture<?>) result, 0, TimeUnit.MICROSECONDS);
} catch (Exception e) {
logger.debug("Error unwrapping result", e);
}
}
Span span = Span.fromContext(context);
// Nothing to do here, so return
if (result == null) {
span.end();
return;
}
if (CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES) {
// Provide helpful metadata for some of the more common response types
span.setAttribute("twilio.type", result.getClass().getCanonicalName());
// Instrument the most popular resource types directly
if (result instanceof Message) {
Message message = (Message) result;
span.setAttribute("twilio.account", message.getAccountSid());
span.setAttribute("twilio.sid", message.getSid());
Message.Status status = message.getStatus();
if (status != null) {
span.setAttribute("twilio.status", status.toString());
}
} else if (result instanceof Call) {
Call call = (Call) result;
span.setAttribute("twilio.account", call.getAccountSid());
span.setAttribute("twilio.sid", call.getSid());
span.setAttribute("twilio.parentSid", call.getParentCallSid());
Call.Status status = call.getStatus();
if (status != null) {
span.setAttribute("twilio.status", status.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");
}
}
super.end(context);
}
/**
* Helper method for calling a getter using reflection. This will be slow, so only use when
* required.
*/
private static void setTagIfPresent(Span span, Object result, String tag, String getter) {
try {
Method method = result.getClass().getMethod(getter);
Object value = method.invoke(result);
if (value != null) {
span.setAttribute(tag, value.toString());
}
} catch (Exception e) {
// Expected that this won't work for all result types
}
}
@Override
protected String getInstrumentationName() {
return "io.opentelemetry.twilio-6.6";
}
}