-
Notifications
You must be signed in to change notification settings - Fork 92
Initial tracing #15
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
Initial tracing #15
Changes from 4 commits
120fd61
8756e64
6f87a8a
3df9880
15026f7
32d61a0
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,29 @@ | ||
package software.aws.lambda.internal; | ||
|
||
import java.io.InputStream; | ||
import java.io.OutputStream; | ||
|
||
import com.amazonaws.services.lambda.runtime.Context; | ||
import com.amazonaws.services.lambda.runtime.RequestHandler; | ||
import com.amazonaws.services.lambda.runtime.RequestStreamHandler; | ||
import org.aspectj.lang.ProceedingJoinPoint; | ||
|
||
public final class LambdaHandlerProcessor { | ||
public static boolean isHandlerMethod(ProceedingJoinPoint pjp) { | ||
return "handleRequest".equals(pjp.getSignature().getName()); | ||
} | ||
|
||
public static boolean placedOnRequestHandler(ProceedingJoinPoint pjp) { | ||
return RequestHandler.class.isAssignableFrom(pjp.getSignature().getDeclaringType()) | ||
&& pjp.getArgs().length == 2 | ||
&& pjp.getArgs()[1] instanceof Context; | ||
} | ||
|
||
public static boolean placedOnStreamHandler(ProceedingJoinPoint pjp) { | ||
return RequestStreamHandler.class.isAssignableFrom(pjp.getSignature().getDeclaringType()) | ||
&& pjp.getArgs().length == 3 | ||
&& pjp.getArgs()[0] instanceof InputStream | ||
&& pjp.getArgs()[1] instanceof OutputStream | ||
&& pjp.getArgs()[2] instanceof Context; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,8 +10,6 @@ | |
import java.util.Optional; | ||
|
||
import com.amazonaws.services.lambda.runtime.Context; | ||
import com.amazonaws.services.lambda.runtime.RequestHandler; | ||
import com.amazonaws.services.lambda.runtime.RequestStreamHandler; | ||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
import org.apache.logging.log4j.LogManager; | ||
import org.apache.logging.log4j.Logger; | ||
|
@@ -25,6 +23,9 @@ | |
|
||
import static java.util.Optional.empty; | ||
import static java.util.Optional.of; | ||
import static software.aws.lambda.internal.LambdaHandlerProcessor.isHandlerMethod; | ||
import static software.aws.lambda.internal.LambdaHandlerProcessor.placedOnRequestHandler; | ||
import static software.aws.lambda.internal.LambdaHandlerProcessor.placedOnStreamHandler; | ||
|
||
@Aspect | ||
public final class LambdaAspect { | ||
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. Can this be renamed to LambdaLoggingAspect |
||
|
@@ -35,7 +36,7 @@ public final class LambdaAspect { | |
public void callAt(PowerToolsLogging powerToolsLogging) { | ||
} | ||
|
||
@Around(value = "callAt(powerToolsLogging)", argNames = "pjp,powerToolsLogging") | ||
@Around(value = "callAt(powerToolsLogging) && execution(@PowerToolsLogging * *.*(..))", argNames = "pjp,powerToolsLogging") | ||
public Object around(ProceedingJoinPoint pjp, | ||
PowerToolsLogging powerToolsLogging) throws Throwable { | ||
Object[] proceedArgs = pjp.getArgs(); | ||
|
@@ -113,19 +114,4 @@ private Object[] logFromInputStream(ProceedingJoinPoint pjp) { | |
private Logger logger(ProceedingJoinPoint pjp) { | ||
return LogManager.getLogger(pjp.getSignature().getDeclaringType()); | ||
} | ||
|
||
private boolean isHandlerMethod(ProceedingJoinPoint pjp) { | ||
return "handleRequest".equals(pjp.getSignature().getName()); | ||
} | ||
|
||
private boolean placedOnRequestHandler(ProceedingJoinPoint pjp) { | ||
return RequestHandler.class.isAssignableFrom(pjp.getSignature().getDeclaringType()) | ||
&& pjp.getArgs().length == 2 && pjp.getArgs()[1] instanceof Context; | ||
} | ||
|
||
private boolean placedOnStreamHandler(ProceedingJoinPoint pjp) { | ||
return RequestStreamHandler.class.isAssignableFrom(pjp.getSignature().getDeclaringType()) | ||
&& pjp.getArgs().length == 3 && pjp.getArgs()[0] instanceof InputStream | ||
&& pjp.getArgs()[2] instanceof Context; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package software.aws.lambda.tracing; | ||
|
||
import java.lang.annotation.ElementType; | ||
import java.lang.annotation.Retention; | ||
import java.lang.annotation.RetentionPolicy; | ||
import java.lang.annotation.Target; | ||
|
||
@Retention(RetentionPolicy.RUNTIME) | ||
@Target(ElementType.METHOD) | ||
public @interface PowerToolTracing { | ||
String namespace() default ""; | ||
boolean captureResponse() default true; | ||
boolean captureError() default true; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
package software.aws.lambda.tracing; | ||
|
||
import java.util.function.Consumer; | ||
|
||
import com.amazonaws.xray.AWSXRay; | ||
import com.amazonaws.xray.entities.Entity; | ||
import com.amazonaws.xray.entities.Subsegment; | ||
|
||
public final class PowerTracer { | ||
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. Helper utils for setting annotation and metadata on current segment 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. Can we directly do a good Javadoc documentation? 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. I belive its too early to spend effort on writing java docs. IMO we can delay it a bit until we have a but more structure |
||
public static final String SERVICE_NAME = null != System.getenv("POWERTOOLS_SERVICE_NAME") | ||
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. In the python impl this is "service_undefined" by default. Might be worth matching their project for consistency. 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. Xray recorder lib by default uses: private static final String DEFAULT_METADATA_NAMESPACE = "default"; But i can update it to match with that of powertools. |
||
? System.getenv("POWERTOOLS_SERVICE_NAME") : "service_undefined"; | ||
|
||
public static void putAnnotation(String key, String value) { | ||
AWSXRay.getCurrentSubsegmentOptional() | ||
.ifPresent(segment -> segment.putAnnotation(key, value)); | ||
} | ||
|
||
public static void putMetadata(String key, Object value) { | ||
String namespace = AWSXRay.getCurrentSubsegmentOptional() | ||
.map(Subsegment::getNamespace).orElse(SERVICE_NAME); | ||
|
||
putMetadata(namespace, key, value); | ||
} | ||
|
||
public static void putMetadata(String namespace, String key, Object value) { | ||
AWSXRay.getCurrentSubsegmentOptional() | ||
.ifPresent(segment -> segment.putMetadata(namespace, key, value)); | ||
} | ||
|
||
public static void withEntitySubsegment(String name, Entity entity, Consumer<Subsegment> subsegment) { | ||
AWSXRay.setTraceEntity(entity); | ||
withSubsegment(name, subsegment); | ||
} | ||
|
||
public static void withSubsegment(String name, Consumer<Subsegment> subsegment) { | ||
Subsegment segment = AWSXRay.beginSubsegment("## " + name); | ||
msailes marked this conversation as resolved.
Show resolved
Hide resolved
|
||
try { | ||
subsegment.accept(segment); | ||
} finally { | ||
AWSXRay.endSubsegment(); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
package software.aws.lambda.tracing.internal; | ||
|
||
import com.amazonaws.xray.AWSXRay; | ||
import com.amazonaws.xray.entities.Subsegment; | ||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
import org.aspectj.lang.ProceedingJoinPoint; | ||
import org.aspectj.lang.annotation.Around; | ||
import org.aspectj.lang.annotation.Aspect; | ||
import org.aspectj.lang.annotation.Pointcut; | ||
import software.aws.lambda.tracing.PowerToolTracing; | ||
|
||
import static software.aws.lambda.internal.LambdaHandlerProcessor.isHandlerMethod; | ||
import static software.aws.lambda.internal.LambdaHandlerProcessor.placedOnRequestHandler; | ||
import static software.aws.lambda.internal.LambdaHandlerProcessor.placedOnStreamHandler; | ||
import static software.aws.lambda.tracing.PowerTracer.SERVICE_NAME; | ||
|
||
@Aspect | ||
public final class LambdaTracingAspect { | ||
static Boolean IS_COLD_START = null; | ||
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. Can we have the cold start logic in a single place? |
||
private static final ObjectMapper mapper = new ObjectMapper(); | ||
|
||
@Pointcut("@annotation(powerToolsTracing)") | ||
public void callAt(PowerToolTracing powerToolsTracing) { | ||
} | ||
|
||
@Around(value = "callAt(powerToolsTracing) && execution(@PowerToolTracing * *.*(..))", argNames = "pjp,powerToolsTracing") | ||
public Object around(ProceedingJoinPoint pjp, | ||
PowerToolTracing powerToolsTracing) throws Throwable { | ||
Object[] proceedArgs = pjp.getArgs(); | ||
Subsegment segment; | ||
|
||
segment = AWSXRay.beginSubsegment("## " + pjp.getSignature().getName()); | ||
segment.setNamespace(namespace(powerToolsTracing)); | ||
|
||
boolean placedOnHandlerMethod = placedOnHandlerMethod(pjp); | ||
|
||
if (placedOnHandlerMethod) { | ||
segment.putAnnotation("ColdStart", IS_COLD_START == null); | ||
} | ||
|
||
IS_COLD_START = false; | ||
|
||
try { | ||
Object methodReturn = pjp.proceed(proceedArgs); | ||
if (powerToolsTracing.captureResponse()) { | ||
segment.putMetadata(namespace(powerToolsTracing), pjp.getSignature().getName() + " response", response(pjp, methodReturn)); | ||
} | ||
return methodReturn; | ||
} catch (Exception e) { | ||
if (powerToolsTracing.captureError()) { | ||
segment.putMetadata(namespace(powerToolsTracing), pjp.getSignature().getName() + " error", e); | ||
} | ||
throw e; | ||
} finally { | ||
AWSXRay.endSubsegment(); | ||
} | ||
} | ||
|
||
private Object response(ProceedingJoinPoint pjp, Object methodReturn) { | ||
// TODO should we try to parse output stream? or just not support it? | ||
if (placedOnStreamHandler(pjp)) { | ||
|
||
} | ||
|
||
return methodReturn; | ||
} | ||
|
||
private String namespace(PowerToolTracing powerToolsTracing) { | ||
return powerToolsTracing.namespace().isEmpty() ? SERVICE_NAME : powerToolsTracing.namespace(); | ||
} | ||
|
||
private boolean placedOnHandlerMethod(ProceedingJoinPoint pjp) { | ||
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. We can enrich this further like its done in logging part, but we can do that once we agree on approach here. |
||
return isHandlerMethod(pjp) | ||
&& (placedOnRequestHandler(pjp) || placedOnStreamHandler(pjp)); | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.