-
Notifications
You must be signed in to change notification settings - Fork 337
Axway API integration #1724
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Axway API integration #1724
Changes from all commits
546f8db
4d14221
6e5cb1f
05167f4
7a7e7f3
34c889b
a70312e
71b6ce4
2c0ed7d
f393238
c3e7b53
9b0816c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| apply from: "$rootDir/gradle/java.gradle" | ||
|
|
||
| dependencies { | ||
| // It's difficult to split jars from axway apigateway monolith: | ||
| // Jars are mostly frontend to native code and this native code doesn't run separately without licence. | ||
| // Moreover, there is no component which you can run `java -jar ...` nor add it to classpath. | ||
| // Attempt to load any Axway class it results in segmentation fault. | ||
|
|
||
| //compileOnly group: 'com.axway.apigw', name: 'com.axway.apigw', version: '7.5' | ||
| //latestDepTestCompile group: 'com.axway.apigw', name: 'com.axway.apigw', version: '+' | ||
|
|
||
| //testCompile group: 'com.axway.ats.framework', name: 'ats-core', version: '4.0.6' | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,236 @@ | ||
| package datadog.trace.instrumentation.axway; | ||
|
|
||
| import static java.lang.invoke.MethodType.methodType; | ||
|
|
||
| import datadog.trace.bootstrap.instrumentation.api.AgentSpan; | ||
| import datadog.trace.bootstrap.instrumentation.api.DefaultURIDataAdapter; | ||
| import datadog.trace.bootstrap.instrumentation.api.Tags; | ||
| import datadog.trace.bootstrap.instrumentation.api.URIDataAdapter; | ||
| import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString; | ||
| import datadog.trace.bootstrap.instrumentation.decorator.HttpServerDecorator; | ||
| import java.lang.invoke.MethodHandle; | ||
| import java.lang.invoke.MethodHandles; | ||
| import java.lang.reflect.Field; | ||
| import java.lang.reflect.Method; | ||
| import java.net.InetSocketAddress; | ||
| import java.net.URI; | ||
| import lombok.extern.slf4j.Slf4j; | ||
|
|
||
| // request = is com.vordel.circuit.net.State, connection = com.vordel.dwe.http.ServerTransaction | ||
| @Slf4j | ||
| public class AxwayHTTPPluginDecorator extends HttpServerDecorator<Object, Object, Object> { | ||
| public static final CharSequence AXWAY_REQUEST = UTF8BytesString.create("axway.request"); | ||
| public static final CharSequence AXWAY_TRY_TRANSACTION = | ||
| UTF8BytesString.create("axway.trytransaction"); | ||
|
|
||
| public static final AxwayHTTPPluginDecorator DECORATE = new AxwayHTTPPluginDecorator(); | ||
|
|
||
| private static final MethodHandles.Lookup lookup = MethodHandles.lookup(); | ||
|
|
||
| private static final String SERVERTRANSACTION_CLASSNAME = "com.vordel.dwe.http.ServerTransaction"; | ||
| private static final Class<?> classServerTransaction; | ||
| private static final MethodHandle getRemoteAddr_mh; | ||
| private static final MethodHandle getMethod_mh; | ||
| private static final MethodHandle getURI_mh; | ||
|
|
||
| private static final String STATE_CLASSNAME = "com.vordel.circuit.net.State"; | ||
| private static final Class<?> classState; | ||
| private static final MethodHandle hostField_mh; | ||
| private static final MethodHandle portField_mh; | ||
| private static final MethodHandle methodField_mh; | ||
| private static final MethodHandle uriField_mh; | ||
|
|
||
| static final String SERVER_TRANSACTION_CLASSNAME = "com.vordel.dwe.http.ServerTransaction"; | ||
| static final Class<Object> SERVER_TRANSACTION_CLASS = getServerTransactionClass(); | ||
|
|
||
| private static Class<Object> getServerTransactionClass() { | ||
| try { | ||
| return (Class<Object>) Class.forName(SERVER_TRANSACTION_CLASSNAME); | ||
| } catch (ClassNotFoundException e) { | ||
| log.debug("Can't get ServerTransaction class name", e); | ||
| } | ||
| return null; | ||
| } | ||
|
|
||
| static { | ||
| classServerTransaction = initClass(SERVERTRANSACTION_CLASSNAME); | ||
| getRemoteAddr_mh = | ||
| initNoArgServerTransactionMethodHandle("getRemoteAddr", InetSocketAddress.class); | ||
| getMethod_mh = initNoArgServerTransactionMethodHandle("getMethod", String.class); | ||
| getURI_mh = initGetURI(); | ||
|
|
||
| classState = initClass(STATE_CLASSNAME); | ||
| hostField_mh = initStateFieldGetter("host"); | ||
| portField_mh = initStateFieldGetter("port"); | ||
| methodField_mh = initStateFieldGetter("verb"); | ||
| uriField_mh = initStateFieldGetter("uri"); | ||
| } | ||
|
|
||
| private static Class<?> initClass(final String name) { | ||
| try { | ||
| return Class.forName(name, false, AxwayHTTPPluginDecorator.class.getClassLoader()); | ||
| } catch (ClassNotFoundException e) { | ||
| log.debug( | ||
| "Can't find class '{}': Axaway integration failed. ", SERVERTRANSACTION_CLASSNAME, e); | ||
| } | ||
| return null; | ||
| } | ||
|
|
||
| private static MethodHandle initNoArgServerTransactionMethodHandle(String name, Class<?> rtype) { | ||
| try { | ||
| return lookup.findVirtual(classServerTransaction, name, methodType(rtype)); | ||
| } catch (NoSuchMethodException | IllegalAccessException e) { | ||
| log.debug("Can't find method handler '{}' ", name, e); | ||
| } | ||
| return null; | ||
| } | ||
|
|
||
| private static MethodHandle initGetURI() { | ||
| Method m = null; | ||
| try { | ||
| m = classServerTransaction.getDeclaredMethod("getURI"); // private method | ||
| m.setAccessible(true); | ||
| return lookup.unreflect(m); | ||
| } catch (Throwable e) { | ||
| log.debug("Can't unreflect method '{}': ", m, e); | ||
| } | ||
| return null; | ||
| } | ||
|
|
||
| private static MethodHandle initStateFieldGetter(String fieldName) { | ||
| Field field = null; | ||
| MethodHandle mh = null; | ||
| try { | ||
| field = classState.getDeclaredField(fieldName); | ||
| field.setAccessible(true); | ||
| mh = lookup.unreflectGetter(field); | ||
| log.debug( | ||
| "Initialized field '{}' of class '{}' unreflected to {}", fieldName, classState, mh); | ||
| } catch (NoSuchFieldException | IllegalAccessException e) { | ||
| log.debug( | ||
| "Can't find and unreflect declared field '{}' with name '{}' for class '{}' to mh: '{}'", | ||
| field, | ||
| fieldName, | ||
| classState, | ||
| mh, | ||
| e); | ||
| } | ||
| return mh; | ||
| } | ||
|
|
||
| @Override | ||
| protected String[] instrumentationNames() { | ||
| return new String[] {"axway-http"}; | ||
| } | ||
|
|
||
| @Override | ||
| protected String component() { | ||
| return "axway-http"; | ||
| } | ||
|
|
||
| @Override | ||
| protected String method(final Object serverTransaction) { | ||
| try { | ||
| return (String) getMethod_mh.invoke(serverTransaction); | ||
| } catch (Throwable throwable) { | ||
| log.debug( | ||
| "Can't invoke invoke '{}' on instance '{}' of class '{}'", | ||
| getMethod_mh, | ||
| serverTransaction, | ||
| serverTransaction.getClass(), | ||
| throwable); | ||
| } | ||
| return "UNKNOWN"; | ||
| } | ||
|
|
||
| @Override | ||
| protected URIDataAdapter url(final Object serverTransaction) { | ||
| try { | ||
| return new DefaultURIDataAdapter((URI) getURI_mh.invoke(serverTransaction)); | ||
| } catch (Throwable e) { | ||
| log.debug("Can't find invoke '{}}' on '{}': ", getURI_mh, serverTransaction, e); | ||
| } | ||
| return null; | ||
| } | ||
|
|
||
| @Override | ||
| protected String peerHostIP(Object serverTransaction) { | ||
| return getRemoteAddr(serverTransaction).getHostString(); | ||
| } | ||
|
|
||
| @Override | ||
| protected int peerPort(Object serverTransaction) { | ||
| return getRemoteAddr(serverTransaction).getPort(); | ||
| } | ||
|
|
||
| /** @param serverTransaction instance of {@value #SERVERTRANSACTION_CLASSNAME} */ | ||
| @Override | ||
| protected int status(final Object serverTransaction) { | ||
| // TODO will be done manually | ||
| return 0; | ||
| } | ||
|
|
||
| /** @param stateInstance type com.vordel.circuit.net.State */ | ||
| public AgentSpan onTransaction(AgentSpan span, Object stateInstance) { | ||
| if (span != null) { | ||
| setStringTagFromStateField(span, Tags.PEER_HOSTNAME, stateInstance, hostField_mh); | ||
| setStringTagFromStateField(span, Tags.PEER_PORT, stateInstance, portField_mh); | ||
| setStringTagFromStateField(span, Tags.HTTP_METHOD, stateInstance, methodField_mh); | ||
| setURLTagFromUriStateField(span, stateInstance); | ||
| } | ||
| return span; | ||
| } | ||
|
|
||
| /** | ||
| * class hierarchy in package com.vordel.dwe.http : | ||
| * | ||
| * <pre><code> | ||
| * public class Transaction implements IMetricsTransaction { | ||
| * public native InetSocketAddress getLocalAddr(); | ||
| * public native InetSocketAddress getRemoteAddr(); | ||
| * } | ||
| * public abstract class HTTPTransaction extends Transaction { | ||
| * | ||
| * } | ||
| * public class ServerTransaction extends HTTPTransaction { | ||
| * | ||
| * } | ||
| * </code></pre> | ||
| * | ||
| * @param obj instance of {@value #SERVERTRANSACTION_CLASSNAME} | ||
| * @return result of {@value #SERVERTRANSACTION_CLASSNAME}::getRemoteAddr() | ||
| */ | ||
| private static InetSocketAddress getRemoteAddr(Object obj) { | ||
| try { | ||
| return (InetSocketAddress) getRemoteAddr_mh.invoke(obj); | ||
| } catch (Throwable throwable) { | ||
| log.debug("Can't invoke '{}' on instance '{}': ", getRemoteAddr_mh, obj, throwable); | ||
| } | ||
| return new InetSocketAddress(0); | ||
| } | ||
|
|
||
| private static void setStringTagFromStateField( | ||
| AgentSpan span, String tag, Object stateInstance, MethodHandle mh) { | ||
| String v = ""; | ||
| try { | ||
| v = (String) mh.invoke(stateInstance); | ||
| } catch (Throwable e) { | ||
| log.debug( | ||
| "Can't invoke '{}' on instance '{}'; ; Tag '{}' not set.", mh, stateInstance, tag, e); | ||
| } | ||
| span.setTag(tag, v); | ||
| } | ||
|
|
||
| private static void setURLTagFromUriStateField(AgentSpan span, Object stateInstance) { | ||
| try { | ||
| span.setTag(Tags.HTTP_URL, uriField_mh.invoke(stateInstance).toString()); | ||
| } catch (Throwable e) { | ||
| log.debug( | ||
| "Can't invoke '{}' on instance '{}'; Tag '{}' not set.", | ||
| uriField_mh, | ||
| stateInstance, | ||
| Tags.HTTP_URL, | ||
| e); | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| package datadog.trace.instrumentation.axway; | ||
|
|
||
| import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.namedOneOf; | ||
| 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 com.google.auto.service.AutoService; | ||
| import datadog.trace.agent.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 AxwayHTTPPluginInstrumentation extends Instrumenter.Tracing { | ||
|
|
||
| public AxwayHTTPPluginInstrumentation() { | ||
| super("axway-api"); | ||
| } | ||
|
|
||
| @Override | ||
| public Map<String, String> contextStore() { | ||
| return singletonMap("com.vordel.dwe.http.ServerTransaction", int.class.getName()); | ||
| } | ||
|
|
||
| @Override | ||
| public ElementMatcher<? super TypeDescription> typeMatcher() { | ||
| return namedOneOf( | ||
| "com.vordel.dwe.http.HTTPPlugin", | ||
| "com.vordel.dwe.http.ServerTransaction", | ||
| "com.vordel.circuit.net.State"); | ||
| } | ||
|
|
||
| @Override | ||
| public String[] helperClassNames() { | ||
| return new String[] { | ||
| packageName + ".StateAdvice", | ||
| packageName + ".AxwayHTTPPluginDecorator", | ||
| packageName + ".HTTPPluginAdvice", | ||
| packageName + ".ServerTransactionAdvice", | ||
| }; | ||
| } | ||
|
|
||
| @Override | ||
| public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() { | ||
| final Map<ElementMatcher<? super MethodDescription>, String> transformers = new HashMap<>(); | ||
| transformers.put( | ||
| isMethod().and(isPublic()).and(named("invokeDispose")), packageName + ".HTTPPluginAdvice"); | ||
| transformers.put( | ||
| isMethod().and(isPublic()).and(named("tryTransaction")), packageName + ".StateAdvice"); | ||
| transformers.put( | ||
| isMethod().and(isPublic()).and(named("sendResponse")), | ||
| packageName + ".ServerTransactionAdvice"); | ||
| return transformers; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| package datadog.trace.instrumentation.axway; | ||
|
|
||
| import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan; | ||
| import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.startSpan; | ||
| import static datadog.trace.instrumentation.axway.AxwayHTTPPluginDecorator.AXWAY_REQUEST; | ||
| import static datadog.trace.instrumentation.axway.AxwayHTTPPluginDecorator.DECORATE; | ||
| import static datadog.trace.instrumentation.axway.AxwayHTTPPluginDecorator.SERVER_TRANSACTION_CLASS; | ||
|
|
||
| import datadog.trace.bootstrap.InstrumentationContext; | ||
| import datadog.trace.bootstrap.instrumentation.api.AgentScope; | ||
| import datadog.trace.bootstrap.instrumentation.api.AgentSpan; | ||
| import datadog.trace.bootstrap.instrumentation.api.Tags; | ||
| import net.bytebuddy.asm.Advice; | ||
|
|
||
| public class HTTPPluginAdvice { | ||
|
|
||
| @Advice.OnMethodEnter(suppress = Throwable.class) | ||
| public static AgentScope onEnter(@Advice.Argument(value = 2) final Object serverTransaction) { | ||
| final AgentSpan span = startSpan(AXWAY_REQUEST); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This doesn't seem to be reading incoming headers to propagate the trace? Is this an acceptable tradeoff?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes for now. I might need to change it in next PR |
||
| final AgentScope scope = activateSpan(span); | ||
| span.setMeasured(true); | ||
| DECORATE.afterStart(span); | ||
| // serverTransaction is like request + connection in one object: | ||
| DECORATE.onRequest(span, serverTransaction, serverTransaction, null); | ||
| return scope; | ||
| } | ||
|
|
||
| @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) | ||
| public static void onExit( | ||
| @Advice.Enter final AgentScope scope, | ||
| @Advice.Argument(value = 2) final Object serverTransaction, | ||
| @Advice.Thrown final Throwable throwable) { | ||
| if (scope == null) { | ||
| return; | ||
| } | ||
| final AgentSpan span = scope.span(); | ||
| try { | ||
| if (null != serverTransaction) { | ||
| // manual DECORATE.onResponse(span, serverTransaction): | ||
| // TODO: It doesn't work. Rewriting of InstrumentationContext.get fails here, because both | ||
| // arguments should be | ||
| // class-literals (not runtime Class object) to make FieldBackedContextRequestRewriter | ||
| // work. | ||
| int respCode = | ||
| InstrumentationContext.get(SERVER_TRANSACTION_CLASS, int.class).get(serverTransaction); | ||
| span.setTag(Tags.HTTP_STATUS, respCode); | ||
| } | ||
| if (throwable != null) { | ||
| DECORATE.onError(span, throwable); | ||
| } | ||
| DECORATE.beforeFinish(span); | ||
| } finally { | ||
| scope.close(); | ||
| span.finish(); | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| package datadog.trace.instrumentation.axway; | ||
|
|
||
| import static datadog.trace.instrumentation.axway.AxwayHTTPPluginDecorator.SERVER_TRANSACTION_CLASS; | ||
|
|
||
| import datadog.trace.bootstrap.InstrumentationContext; | ||
| import net.bytebuddy.asm.Advice; | ||
|
|
||
| public class ServerTransactionAdvice { | ||
|
|
||
| @Advice.OnMethodExit(suppress = Throwable.class) | ||
| public static void onEnter( | ||
| @Advice.This Object thiz, @Advice.Argument(value = 0) final int responseCode) { | ||
| if (null != SERVER_TRANSACTION_CLASS) { | ||
| InstrumentationContext.get(SERVER_TRANSACTION_CLASS, int.class).put(thiz, responseCode); | ||
| } | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this happens, should we stop trying to the URL extraction?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this exception should never happen. At the point we instrumenting ServerTransaction it should have some valid URI behind it: otherwise it wouldn't be able to reach the server where we instrumenting it.