Make twilio instrumentation consistent with others (#1835)
This commit is contained in:
parent
b2f2c96904
commit
1ca562ca9c
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
package io.opentelemetry.javaagent.instrumentation.twilio;
|
package io.opentelemetry.javaagent.instrumentation.twilio;
|
||||||
|
|
||||||
|
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.TwilioTracer.tracer;
|
||||||
import static io.opentelemetry.javaagent.tooling.ClassLoaderMatcher.hasClassesNamed;
|
import static io.opentelemetry.javaagent.tooling.ClassLoaderMatcher.hasClassesNamed;
|
||||||
import static io.opentelemetry.javaagent.tooling.bytebuddy.matcher.AgentElementMatchers.extendsClass;
|
import static io.opentelemetry.javaagent.tooling.bytebuddy.matcher.AgentElementMatchers.extendsClass;
|
||||||
|
@ -21,9 +22,8 @@ import com.google.common.util.concurrent.FutureCallback;
|
||||||
import com.google.common.util.concurrent.Futures;
|
import com.google.common.util.concurrent.Futures;
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
import com.twilio.Twilio;
|
import com.twilio.Twilio;
|
||||||
import io.opentelemetry.api.trace.Span;
|
import io.opentelemetry.context.Context;
|
||||||
import io.opentelemetry.context.Scope;
|
import io.opentelemetry.context.Scope;
|
||||||
import io.opentelemetry.javaagent.instrumentation.api.CallDepthThreadLocalMap;
|
|
||||||
import io.opentelemetry.javaagent.tooling.TypeInstrumentation;
|
import io.opentelemetry.javaagent.tooling.TypeInstrumentation;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import net.bytebuddy.asm.Advice;
|
import net.bytebuddy.asm.Advice;
|
||||||
|
@ -78,20 +78,15 @@ public class TwilioAsyncInstrumentation implements TypeInstrumentation {
|
||||||
public static void methodEnter(
|
public static void methodEnter(
|
||||||
@Advice.This Object that,
|
@Advice.This Object that,
|
||||||
@Advice.Origin("#m") String methodName,
|
@Advice.Origin("#m") String methodName,
|
||||||
@Advice.Local("otelSpan") Span span,
|
@Advice.Local("otelContext") Context context,
|
||||||
@Advice.Local("otelScope") Scope scope) {
|
@Advice.Local("otelScope") Scope scope) {
|
||||||
|
Context parentContext = currentContext();
|
||||||
// Ensure that we only create a span for the top-level Twilio client method; except in the
|
if (!tracer().shouldStartSpan(parentContext)) {
|
||||||
// case of async operations where we want visibility into how long the task was delayed from
|
|
||||||
// starting. Our call depth checker does not span threads, so the async case is handled
|
|
||||||
// automatically for us.
|
|
||||||
if (CallDepthThreadLocalMap.incrementCallDepth(Twilio.class) > 0) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't automatically close the span with the scope if we're executing an async method
|
context = tracer().startSpan(parentContext, that, methodName);
|
||||||
span = tracer().startSpan(that, methodName);
|
scope = context.makeCurrent();
|
||||||
scope = tracer().startScope(span);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Method exit instrumentation. */
|
/** Method exit instrumentation. */
|
||||||
|
@ -99,24 +94,22 @@ public class TwilioAsyncInstrumentation implements TypeInstrumentation {
|
||||||
public static void methodExit(
|
public static void methodExit(
|
||||||
@Advice.Thrown Throwable throwable,
|
@Advice.Thrown Throwable throwable,
|
||||||
@Advice.Return ListenableFuture<?> response,
|
@Advice.Return ListenableFuture<?> response,
|
||||||
@Advice.Local("otelSpan") Span span,
|
@Advice.Local("otelContext") Context context,
|
||||||
@Advice.Local("otelScope") Scope scope) {
|
@Advice.Local("otelScope") Scope scope) {
|
||||||
if (scope == null) {
|
if (scope == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
CallDepthThreadLocalMap.reset(Twilio.class);
|
|
||||||
|
|
||||||
// span finished in SpanFinishingCallback
|
|
||||||
scope.close();
|
scope.close();
|
||||||
if (throwable != null) {
|
if (throwable != null) {
|
||||||
// There was an synchronous error,
|
// There was an synchronous error,
|
||||||
// which means we shouldn't wait for a callback to close the span.
|
// which means we shouldn't wait for a callback to close the span.
|
||||||
tracer().endExceptionally(span, throwable);
|
tracer().endExceptionally(context, throwable);
|
||||||
} else {
|
} else {
|
||||||
// We're calling an async operation, we still need to finish the span when it's
|
// 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
|
// complete and report the results; set an appropriate callback
|
||||||
Futures.addCallback(
|
Futures.addCallback(
|
||||||
response, new SpanFinishingCallback<>(span), Twilio.getExecutorService());
|
response, new SpanFinishingCallback<>(context), Twilio.getExecutorService());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -128,22 +121,20 @@ public class TwilioAsyncInstrumentation implements TypeInstrumentation {
|
||||||
public static class SpanFinishingCallback<T> implements FutureCallback<T> {
|
public static class SpanFinishingCallback<T> implements FutureCallback<T> {
|
||||||
|
|
||||||
/** Span that we should finish and annotate when the future is complete. */
|
/** Span that we should finish and annotate when the future is complete. */
|
||||||
private final Span span;
|
private final Context context;
|
||||||
|
|
||||||
public SpanFinishingCallback(Span span) {
|
public SpanFinishingCallback(Context context) {
|
||||||
this.span = span;
|
this.context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(Object result) {
|
public void onSuccess(Object result) {
|
||||||
tracer().end(span, result);
|
tracer().end(context, result);
|
||||||
span.end();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFailure(Throwable t) {
|
public void onFailure(Throwable t) {
|
||||||
tracer().endExceptionally(span, t);
|
tracer().endExceptionally(context, t);
|
||||||
span.end();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
package io.opentelemetry.javaagent.instrumentation.twilio;
|
package io.opentelemetry.javaagent.instrumentation.twilio;
|
||||||
|
|
||||||
|
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.TwilioTracer.tracer;
|
||||||
import static io.opentelemetry.javaagent.tooling.ClassLoaderMatcher.hasClassesNamed;
|
import static io.opentelemetry.javaagent.tooling.ClassLoaderMatcher.hasClassesNamed;
|
||||||
import static io.opentelemetry.javaagent.tooling.bytebuddy.matcher.AgentElementMatchers.extendsClass;
|
import static io.opentelemetry.javaagent.tooling.bytebuddy.matcher.AgentElementMatchers.extendsClass;
|
||||||
|
@ -15,10 +16,8 @@ import static net.bytebuddy.matcher.ElementMatchers.isMethod;
|
||||||
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
|
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
|
||||||
import static net.bytebuddy.matcher.ElementMatchers.not;
|
import static net.bytebuddy.matcher.ElementMatchers.not;
|
||||||
|
|
||||||
import com.twilio.Twilio;
|
import io.opentelemetry.context.Context;
|
||||||
import io.opentelemetry.api.trace.Span;
|
|
||||||
import io.opentelemetry.context.Scope;
|
import io.opentelemetry.context.Scope;
|
||||||
import io.opentelemetry.javaagent.instrumentation.api.CallDepthThreadLocalMap;
|
|
||||||
import io.opentelemetry.javaagent.tooling.TypeInstrumentation;
|
import io.opentelemetry.javaagent.tooling.TypeInstrumentation;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import net.bytebuddy.asm.Advice;
|
import net.bytebuddy.asm.Advice;
|
||||||
|
@ -74,19 +73,15 @@ public class TwilioSyncInstrumentation implements TypeInstrumentation {
|
||||||
public static void methodEnter(
|
public static void methodEnter(
|
||||||
@Advice.This Object that,
|
@Advice.This Object that,
|
||||||
@Advice.Origin("#m") String methodName,
|
@Advice.Origin("#m") String methodName,
|
||||||
@Advice.Local("otelSpan") Span span,
|
@Advice.Local("otelContext") Context context,
|
||||||
@Advice.Local("otelScope") Scope scope) {
|
@Advice.Local("otelScope") Scope scope) {
|
||||||
|
Context parentContext = currentContext();
|
||||||
// Ensure that we only create a span for the top-level Twilio client method; except in the
|
if (!tracer().shouldStartSpan(parentContext)) {
|
||||||
// case of async operations where we want visibility into how long the task was delayed from
|
|
||||||
// starting. Our call depth checker does not span threads, so the async case is handled
|
|
||||||
// automatically for us.
|
|
||||||
if (CallDepthThreadLocalMap.incrementCallDepth(Twilio.class) > 0) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
span = tracer().startSpan(that, methodName);
|
context = tracer().startSpan(parentContext, that, methodName);
|
||||||
scope = tracer().startScope(span);
|
scope = context.makeCurrent();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Method exit instrumentation. */
|
/** Method exit instrumentation. */
|
||||||
|
@ -94,18 +89,17 @@ public class TwilioSyncInstrumentation implements TypeInstrumentation {
|
||||||
public static void methodExit(
|
public static void methodExit(
|
||||||
@Advice.Thrown Throwable throwable,
|
@Advice.Thrown Throwable throwable,
|
||||||
@Advice.Return Object response,
|
@Advice.Return Object response,
|
||||||
@Advice.Local("otelSpan") Span span,
|
@Advice.Local("otelContext") Context context,
|
||||||
@Advice.Local("otelScope") Scope scope) {
|
@Advice.Local("otelScope") Scope scope) {
|
||||||
if (scope == null) {
|
if (scope == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
CallDepthThreadLocalMap.reset(Twilio.class);
|
|
||||||
|
|
||||||
scope.close();
|
scope.close();
|
||||||
if (throwable != null) {
|
if (throwable != null) {
|
||||||
tracer().endExceptionally(span, throwable);
|
tracer().endExceptionally(context, throwable);
|
||||||
} else {
|
} else {
|
||||||
tracer().end(span, response);
|
tracer().end(context, response);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,12 +5,13 @@
|
||||||
|
|
||||||
package io.opentelemetry.javaagent.instrumentation.twilio;
|
package io.opentelemetry.javaagent.instrumentation.twilio;
|
||||||
|
|
||||||
|
import static io.opentelemetry.api.trace.Span.Kind.CLIENT;
|
||||||
|
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
import com.twilio.rest.api.v2010.account.Call;
|
import com.twilio.rest.api.v2010.account.Call;
|
||||||
import com.twilio.rest.api.v2010.account.Message;
|
import com.twilio.rest.api.v2010.account.Message;
|
||||||
import io.opentelemetry.api.trace.Span;
|
import io.opentelemetry.api.trace.Span;
|
||||||
import io.opentelemetry.context.Context;
|
import io.opentelemetry.context.Context;
|
||||||
import io.opentelemetry.context.Scope;
|
|
||||||
import io.opentelemetry.instrumentation.api.tracer.BaseTracer;
|
import io.opentelemetry.instrumentation.api.tracer.BaseTracer;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
@ -27,15 +28,18 @@ public class TwilioTracer extends BaseTracer {
|
||||||
return TRACER;
|
return TRACER;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Span startSpan(Object serviceExecutor, String methodName) {
|
public boolean shouldStartSpan(Context parentContext) {
|
||||||
return tracer.spanBuilder(spanNameOnServiceExecution(serviceExecutor, methodName)).startSpan();
|
return parentContext.get(CONTEXT_CLIENT_SPAN_KEY) == null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public Context startSpan(Context parentContext, Object serviceExecutor, String methodName) {
|
||||||
public Scope startScope(Span span) {
|
Span span =
|
||||||
Context context = Context.current().with(span);
|
tracer
|
||||||
context = context.with(CONTEXT_CLIENT_SPAN_KEY, span);
|
.spanBuilder(spanNameOnServiceExecution(serviceExecutor, methodName))
|
||||||
return context.makeCurrent();
|
.setSpanKind(CLIENT)
|
||||||
|
.setParent(parentContext)
|
||||||
|
.startSpan();
|
||||||
|
return parentContext.with(span).with(CONTEXT_CLIENT_SPAN_KEY, span);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Decorate trace based on service execution metadata. */
|
/** Decorate trace based on service execution metadata. */
|
||||||
|
@ -44,7 +48,7 @@ public class TwilioTracer extends BaseTracer {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Annotate the span with the results of the operation. */
|
/** Annotate the span with the results of the operation. */
|
||||||
public void end(Span span, Object result) {
|
public void end(Context context, Object result) {
|
||||||
|
|
||||||
// Unwrap ListenableFuture (if present)
|
// Unwrap ListenableFuture (if present)
|
||||||
if (result instanceof ListenableFuture) {
|
if (result instanceof ListenableFuture) {
|
||||||
|
@ -55,6 +59,8 @@ public class TwilioTracer extends BaseTracer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Span span = Span.fromContext(context);
|
||||||
|
|
||||||
// Nothing to do here, so return
|
// Nothing to do here, so return
|
||||||
if (result == null) {
|
if (result == null) {
|
||||||
span.end();
|
span.end();
|
||||||
|
@ -94,6 +100,10 @@ public class TwilioTracer extends BaseTracer {
|
||||||
super.end(span);
|
super.end(span);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void endExceptionally(Context context, Throwable throwable) {
|
||||||
|
super.endExceptionally(Span.fromContext(context), throwable);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper method for calling a getter using reflection. This will be slow, so only use when
|
* Helper method for calling a getter using reflection. This will be slow, so only use when
|
||||||
* required.
|
* required.
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
package test
|
package test
|
||||||
|
|
||||||
import static io.opentelemetry.api.trace.Span.Kind.INTERNAL
|
import static io.opentelemetry.api.trace.Span.Kind.CLIENT
|
||||||
import static io.opentelemetry.instrumentation.test.utils.TraceUtils.runUnderTrace
|
import static io.opentelemetry.instrumentation.test.utils.TraceUtils.runUnderTrace
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
|
@ -142,7 +142,7 @@ class TwilioClientTest extends AgentTestRunner {
|
||||||
}
|
}
|
||||||
span(1) {
|
span(1) {
|
||||||
name "MessageCreator.create"
|
name "MessageCreator.create"
|
||||||
kind INTERNAL
|
kind CLIENT
|
||||||
errored false
|
errored false
|
||||||
attributes {
|
attributes {
|
||||||
"twilio.type" "com.twilio.rest.api.v2010.account.Message"
|
"twilio.type" "com.twilio.rest.api.v2010.account.Message"
|
||||||
|
@ -186,7 +186,7 @@ class TwilioClientTest extends AgentTestRunner {
|
||||||
}
|
}
|
||||||
span(1) {
|
span(1) {
|
||||||
name "CallCreator.create"
|
name "CallCreator.create"
|
||||||
kind INTERNAL
|
kind CLIENT
|
||||||
errored false
|
errored false
|
||||||
attributes {
|
attributes {
|
||||||
"twilio.type" "com.twilio.rest.api.v2010.account.Call"
|
"twilio.type" "com.twilio.rest.api.v2010.account.Call"
|
||||||
|
@ -252,7 +252,7 @@ class TwilioClientTest extends AgentTestRunner {
|
||||||
}
|
}
|
||||||
span(1) {
|
span(1) {
|
||||||
name "MessageCreator.create"
|
name "MessageCreator.create"
|
||||||
kind INTERNAL
|
kind CLIENT
|
||||||
childOf(span(0))
|
childOf(span(0))
|
||||||
errored false
|
errored false
|
||||||
attributes {
|
attributes {
|
||||||
|
@ -334,7 +334,7 @@ class TwilioClientTest extends AgentTestRunner {
|
||||||
}
|
}
|
||||||
span(1) {
|
span(1) {
|
||||||
name "MessageCreator.create"
|
name "MessageCreator.create"
|
||||||
kind INTERNAL
|
kind CLIENT
|
||||||
childOf(span(0))
|
childOf(span(0))
|
||||||
errored false
|
errored false
|
||||||
attributes {
|
attributes {
|
||||||
|
@ -413,7 +413,7 @@ class TwilioClientTest extends AgentTestRunner {
|
||||||
message.body == "Hello, World!"
|
message.body == "Hello, World!"
|
||||||
|
|
||||||
assertTraces(1) {
|
assertTraces(1) {
|
||||||
trace(0, 3) {
|
trace(0, 2) {
|
||||||
span(0) {
|
span(0) {
|
||||||
name "test"
|
name "test"
|
||||||
errored false
|
errored false
|
||||||
|
@ -423,7 +423,7 @@ class TwilioClientTest extends AgentTestRunner {
|
||||||
}
|
}
|
||||||
span(1) {
|
span(1) {
|
||||||
name "MessageCreator.createAsync"
|
name "MessageCreator.createAsync"
|
||||||
kind INTERNAL
|
kind CLIENT
|
||||||
childOf(span(0))
|
childOf(span(0))
|
||||||
errored false
|
errored false
|
||||||
attributes {
|
attributes {
|
||||||
|
@ -433,18 +433,6 @@ class TwilioClientTest extends AgentTestRunner {
|
||||||
"twilio.status" "sent"
|
"twilio.status" "sent"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
span(2) {
|
|
||||||
name "MessageCreator.create"
|
|
||||||
kind INTERNAL
|
|
||||||
childOf(span(1))
|
|
||||||
errored false
|
|
||||||
attributes {
|
|
||||||
"twilio.type" "com.twilio.rest.api.v2010.account.Message"
|
|
||||||
"twilio.account" "AC14984e09e497506cf0d5eb59b1f6ace7"
|
|
||||||
"twilio.sid" "MMXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
|
|
||||||
"twilio.status" "sent"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -484,7 +472,7 @@ class TwilioClientTest extends AgentTestRunner {
|
||||||
}
|
}
|
||||||
span(1) {
|
span(1) {
|
||||||
name "MessageCreator.create"
|
name "MessageCreator.create"
|
||||||
kind INTERNAL
|
kind CLIENT
|
||||||
errored true
|
errored true
|
||||||
errorEvent(ApiException, "Testing Failure")
|
errorEvent(ApiException, "Testing Failure")
|
||||||
}
|
}
|
||||||
|
@ -512,7 +500,7 @@ class TwilioClientTest extends AgentTestRunner {
|
||||||
trace(0, 1) {
|
trace(0, 1) {
|
||||||
span(0) {
|
span(0) {
|
||||||
name "MessageCreator.create"
|
name "MessageCreator.create"
|
||||||
kind INTERNAL
|
kind CLIENT
|
||||||
hasNoParent()
|
hasNoParent()
|
||||||
errored false
|
errored false
|
||||||
attributes {
|
attributes {
|
||||||
|
@ -556,7 +544,7 @@ class TwilioClientTest extends AgentTestRunner {
|
||||||
message.body == "Hello, World!"
|
message.body == "Hello, World!"
|
||||||
|
|
||||||
assertTraces(1) {
|
assertTraces(1) {
|
||||||
trace(0, 3) {
|
trace(0, 2) {
|
||||||
span(0) {
|
span(0) {
|
||||||
name "test"
|
name "test"
|
||||||
errored false
|
errored false
|
||||||
|
@ -566,18 +554,7 @@ class TwilioClientTest extends AgentTestRunner {
|
||||||
}
|
}
|
||||||
span(1) {
|
span(1) {
|
||||||
name "MessageCreator.createAsync"
|
name "MessageCreator.createAsync"
|
||||||
kind INTERNAL
|
kind CLIENT
|
||||||
errored false
|
|
||||||
attributes {
|
|
||||||
"twilio.type" "com.twilio.rest.api.v2010.account.Message"
|
|
||||||
"twilio.account" "AC14984e09e497506cf0d5eb59b1f6ace7"
|
|
||||||
"twilio.sid" "MMXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
|
|
||||||
"twilio.status" "sent"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
span(2) {
|
|
||||||
name "MessageCreator.create"
|
|
||||||
kind INTERNAL
|
|
||||||
errored false
|
errored false
|
||||||
attributes {
|
attributes {
|
||||||
"twilio.type" "com.twilio.rest.api.v2010.account.Message"
|
"twilio.type" "com.twilio.rest.api.v2010.account.Message"
|
||||||
|
@ -627,7 +604,7 @@ class TwilioClientTest extends AgentTestRunner {
|
||||||
expect:
|
expect:
|
||||||
|
|
||||||
assertTraces(1) {
|
assertTraces(1) {
|
||||||
trace(0, 3) {
|
trace(0, 2) {
|
||||||
span(0) {
|
span(0) {
|
||||||
name "test"
|
name "test"
|
||||||
errored true
|
errored true
|
||||||
|
@ -636,13 +613,7 @@ class TwilioClientTest extends AgentTestRunner {
|
||||||
}
|
}
|
||||||
span(1) {
|
span(1) {
|
||||||
name "MessageCreator.createAsync"
|
name "MessageCreator.createAsync"
|
||||||
kind INTERNAL
|
kind CLIENT
|
||||||
errored true
|
|
||||||
errorEvent(ApiException, "Testing Failure")
|
|
||||||
}
|
|
||||||
span(2) {
|
|
||||||
name "MessageCreator.create"
|
|
||||||
kind INTERNAL
|
|
||||||
errored true
|
errored true
|
||||||
errorEvent(ApiException, "Testing Failure")
|
errorEvent(ApiException, "Testing Failure")
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue