diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 14454ff..2debdb2 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -11,4 +11,6 @@ tag = True [bumpversion:file:src/main/java/io/lumigo/core/configuration/Configuration.java] +[bumpversion:file:layer/generate.sh] + [bumpversion:file:src/main/resources/lumigo-version.txt] diff --git a/README.md b/README.md index 48459db..5726dc1 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,13 @@ Find the latest version here (the format of the version will be n.n.n): } } ``` + +## Lambda Auto tracing with lambda layer + +* Add to your lambda a new layer with the arn from here +* Add environment variable `JAVA_TOOL_OPTIONS` and set it to `-javaagent:/opt/lumigo-java/lumigo-agent.jar` (This is instead of the flag for more than java11 support) +* Add the `LUMIGO_TRACER_TOKEN` env var. + ### Configuration @@ -120,7 +127,7 @@ class MyFunction implements RequestHandler { ### Support Java 11 and Above Add the environment variable `JAVA_TOOL_OPTIONS` to your Lambda functions and set it to -`-Djdk.attach.allowAttachSelf=true` in addition to the manual code mentioned above. +`-Djdk.attach.allowAttachSelf=true` in addition to the manual code mentioned above (This is not needed for the auto trace with lambda layer). ### Supported Instrumentation Libraries diff --git a/agent/pom.xml b/agent/pom.xml index aedafed..5f767ab 100644 --- a/agent/pom.xml +++ b/agent/pom.xml @@ -32,12 +32,6 @@ Lumigo https://lumigo.io/ - - Uri Parush - uri@lumigo.io - Lumigo - https://lumigo.io - @@ -104,9 +98,12 @@ io.lumigo.agent.Agent - false + io.lumigo.agent.Agent + io.lumigo.agent.Agent + true true false + Lumigo diff --git a/agent/src/main/java/io/lumigo/agent/Agent.java b/agent/src/main/java/io/lumigo/agent/Agent.java index 8645189..48b1f22 100644 --- a/agent/src/main/java/io/lumigo/agent/Agent.java +++ b/agent/src/main/java/io/lumigo/agent/Agent.java @@ -11,19 +11,39 @@ import java.nio.file.Paths; import java.util.LinkedList; import java.util.List; +import java.util.jar.JarFile; import java.util.stream.Collectors; import java.util.stream.Stream; public class Agent { + private static boolean initialized = false; + + public static String LUMIGO_JAVA_TRACER_PATH = "/opt/lumigo-java/lumigo-tracer.jar"; + + public static void premain(String agentArgs, Instrumentation inst) { + if (!isKillSwitchOn()) { + agentmain(agentArgs, inst); + } + } + public static void agentmain(String agentArgs, Instrumentation inst) { + if (initialized) { + return; + } + initialized = true; try { URL[] urls; if ("lib".equalsIgnoreCase(agentArgs)) { urls = getUrls(); } else { - urls = new URL[] {new File("/var/task/").toURI().toURL()}; + urls = + new URL[] { + new File("/var/task/").toURI().toURL(), + new File(LUMIGO_JAVA_TRACER_PATH).toURI().toURL() + }; } + installTracerJar(inst); URLClassLoader newClassLoader = new URLClassLoader(urls, null); Thread.currentThread().setContextClassLoader(newClassLoader); final Class loader = @@ -35,6 +55,14 @@ public static void agentmain(String agentArgs, Instrumentation inst) { } } + private static void installTracerJar(Instrumentation inst) { + try (JarFile jar = new JarFile(new File(new File(LUMIGO_JAVA_TRACER_PATH).toURI()))) { + inst.appendToSystemClassLoaderSearch(jar); + } catch (Exception e) { + e.printStackTrace(); + } + } + public static URL[] getUrls() { List jars = new LinkedList<>(); try (Stream paths = Files.walk(Paths.get("/var/task/lib"))) { @@ -56,4 +84,9 @@ public static URL[] getUrls() { } return jars.toArray(new URL[jars.size()]); } + + public static boolean isKillSwitchOn() { + String value = System.getenv("LUMIGO_SWITCH_OFF"); + return "true".equalsIgnoreCase(value); + } } diff --git a/layers/LAYERS.md b/layers/LAYERS.md new file mode 100644 index 0000000..34d9289 --- /dev/null +++ b/layers/LAYERS.md @@ -0,0 +1,4 @@ +Java Layers +---- +| Region | ARN | +| --- | --- | diff --git a/pom.xml b/pom.xml index 0fc6760..a08a40d 100644 --- a/pom.xml +++ b/pom.xml @@ -116,21 +116,25 @@ software.amazon.awssdk dynamodb 2.25.45 + provided software.amazon.awssdk sqs 2.25.45 + provided software.amazon.awssdk sns 2.25.45 + provided software.amazon.awssdk kinesis 2.25.45 + provided @@ -138,6 +142,7 @@ org.apache.kafka kafka-clients 3.1.0 + provided @@ -182,6 +187,7 @@ json 20210307 + org.junit.jupiter @@ -372,4 +378,32 @@ + + + shade + + + includeShade + true + + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.2.4 + + + package + + shade + + + + + + + + \ No newline at end of file diff --git a/scripts/bd_to_prod.sh b/scripts/bd_to_prod.sh index de09605..ac67d64 100755 --- a/scripts/bd_to_prod.sh +++ b/scripts/bd_to_prod.sh @@ -40,6 +40,26 @@ mvn -f agent/pom.xml nexus-staging:release mvn -Dmaven.test.skip=true -Dfindbugs.skip=true clean deploy mvn nexus-staging:release +echo "Creating lumigo-java-tracer layer" +./scripts/prepare_layer_files.sh + +echo "Creating layer latest version arn table md file (LAYERS.md)" +commit_version="$(git describe --abbrev=0 --tags)" +../utils/common_bash/create_layer.sh \ + --layer-name lumigo-java-tracer \ + --region ALL \ + --package-folder lumigo-java \ + --version "$commit_version" \ + --runtimes "java11 java17 java21" + +cd ../larn && npm i -g +larn -r java11 -n layers/LAYERS.md --filter lumigo-java-tracer -p ~/java-tracer +cd ../java-tracer + +git add layers/LAYERS.md +git commit -m "docs: update layers md [skip ci]" +git push origin master + echo "Create release tag" push_tags diff --git a/scripts/prepare_layer_files.sh b/scripts/prepare_layer_files.sh new file mode 100755 index 0000000..56b4528 --- /dev/null +++ b/scripts/prepare_layer_files.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +mkdir -p lumigo-java + +MVN_DEFAULT_FLAGS="-Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false -Dmaven.wagon.httpconnectionManager.ttlSeconds=120 -Dmaven.wagon.http.retryHandler.requestSentEnabled=true -Dmaven.wagon.http.retryHandler.count=10" + +mvn "$MVN_DEFAULT_FLAGS" -Dmaven.test.skip=true -Dfindbugs.skip=true -DincludeShade=true clean package --quiet +mvn "$MVN_DEFAULT_FLAGS" -f agent/pom.xml clean package --quiet + +cp target/java-tracer-1.0.47.jar lumigo-java/lumigo-tracer.jar +cp agent/target/lumigo-agent-1.0.47.jar lumigo-java/lumigo-agent.jar diff --git a/src/main/java/io/lumigo/core/instrumentation/agent/Loader.java b/src/main/java/io/lumigo/core/instrumentation/agent/Loader.java index 77be428..3d82db0 100644 --- a/src/main/java/io/lumigo/core/instrumentation/agent/Loader.java +++ b/src/main/java/io/lumigo/core/instrumentation/agent/Loader.java @@ -1,12 +1,10 @@ package io.lumigo.core.instrumentation.agent; -import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith; -import static net.bytebuddy.matcher.ElementMatchers.not; - import io.lumigo.core.instrumentation.impl.*; import net.bytebuddy.agent.builder.AgentBuilder; import org.pmw.tinylog.Logger; +@SuppressWarnings("unused") public class Loader { public static void instrument(java.lang.instrument.Instrumentation inst) { Logger.debug("Start Instrumentation"); @@ -19,28 +17,12 @@ public static void instrument(java.lang.instrument.Instrumentation inst) { new ApacheKafkaProducerInstrumentation(); ApacheKafkaConsumerInstrumentation apacheKafkaConsumerInstrumentation = new ApacheKafkaConsumerInstrumentation(); + AwsLambdaRequestHandlerInstrumentation awsLambdaRequestHandlerInstrumentation = + new AwsLambdaRequestHandlerInstrumentation(); AgentBuilder builder = new AgentBuilder.Default() .disableClassFormatChanges() .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION) - .ignore( - not(nameStartsWith("com.amazonaws.http.AmazonHttpClient")) - .and(not(nameStartsWith("org.apache.http.impl.client"))) - .and( - not( - nameStartsWith( - AmazonHttpClientV2Instrumentation - .INSTRUMENTATION_PACKAGE_PREFIX))) - .and( - not( - nameStartsWith( - ApacheKafkaProducerInstrumentation - .INSTRUMENTATION_PACKAGE_PREFIX))) - .and( - not( - nameStartsWith( - ApacheKafkaConsumerInstrumentation - .INSTRUMENTATION_PACKAGE_PREFIX)))) .type(apacheHttpInstrumentation.getTypeMatcher()) .transform(apacheHttpInstrumentation.getTransformer()) .type(amazonHttpClientInstrumentation.getTypeMatcher()) @@ -50,7 +32,9 @@ public static void instrument(java.lang.instrument.Instrumentation inst) { .type(apacheKafkaInstrumentation.getTypeMatcher()) .transform(apacheKafkaInstrumentation.getTransformer()) .type(apacheKafkaConsumerInstrumentation.getTypeMatcher()) - .transform(apacheKafkaConsumerInstrumentation.getTransformer()); + .transform(apacheKafkaConsumerInstrumentation.getTransformer()) + .type(awsLambdaRequestHandlerInstrumentation.getTypeMatcher()) + .transform(awsLambdaRequestHandlerInstrumentation.getTransformer()); builder.installOn(inst); Logger.debug("Finish Instrumentation"); diff --git a/src/main/java/io/lumigo/core/instrumentation/impl/AmazonHttpClientInstrumentation.java b/src/main/java/io/lumigo/core/instrumentation/impl/AmazonHttpClientInstrumentation.java index a624812..255a59a 100644 --- a/src/main/java/io/lumigo/core/instrumentation/impl/AmazonHttpClientInstrumentation.java +++ b/src/main/java/io/lumigo/core/instrumentation/impl/AmazonHttpClientInstrumentation.java @@ -43,7 +43,7 @@ public static void methodEnter(@Advice.Argument(0) final Request request) { String patchedRoot = spansContainer.getPatchedRoot(); request.getHeaders().put("X-Amzn-Trace-Id", patchedRoot); startTimeMap.put(request.hashCode(), System.currentTimeMillis()); - } catch (Exception e) { + } catch (Throwable e) { Logger.error(e, "Failed to send data on http requests"); } } diff --git a/src/main/java/io/lumigo/core/instrumentation/impl/AmazonHttpClientV2Instrumentation.java b/src/main/java/io/lumigo/core/instrumentation/impl/AmazonHttpClientV2Instrumentation.java index 03e2e14..434eb4b 100644 --- a/src/main/java/io/lumigo/core/instrumentation/impl/AmazonHttpClientV2Instrumentation.java +++ b/src/main/java/io/lumigo/core/instrumentation/impl/AmazonHttpClientV2Instrumentation.java @@ -36,17 +36,20 @@ public AgentBuilder.Transformer.ForAdvice getTransformer() { AmazonHttpClientV2Advice.class.getName()); } + @SuppressWarnings("unused") public static class AmazonHttpClientV2Advice { @Advice.OnMethodExit(suppress = Throwable.class) public static void methodExit( @Advice.Return final List interceptors) { - Logger.debug("Added Lumigo TracingExecutionInterceptor"); + Logger.debug("At AmazonHttpClientV2Instrumentation$AmazonHttpClientV2Advice"); for (ExecutionInterceptor interceptor : interceptors) { if (interceptor instanceof TracingExecutionInterceptor) { + Logger.debug("Lumigo TracingExecutionInterceptor already exists, skipping..."); return; // list already has our interceptor, return to builder } } interceptors.add(new TracingExecutionInterceptor()); + Logger.debug("Added Lumigo TracingExecutionInterceptor"); } public static class TracingExecutionInterceptor implements ExecutionInterceptor { diff --git a/src/main/java/io/lumigo/core/instrumentation/impl/ApacheHttpInstrumentation.java b/src/main/java/io/lumigo/core/instrumentation/impl/ApacheHttpInstrumentation.java index c96b7ce..e72fbb4 100644 --- a/src/main/java/io/lumigo/core/instrumentation/impl/ApacheHttpInstrumentation.java +++ b/src/main/java/io/lumigo/core/instrumentation/impl/ApacheHttpInstrumentation.java @@ -59,7 +59,7 @@ public static void methodEnter(@Advice.Argument(0) final HttpUriRequest request) return; } startTimeMap.put(request.hashCode(), System.currentTimeMillis()); - } catch (Exception e) { + } catch (Throwable e) { Logger.error(e); } } diff --git a/src/main/java/io/lumigo/core/instrumentation/impl/ApacheKafkaConsumerInstrumentation.java b/src/main/java/io/lumigo/core/instrumentation/impl/ApacheKafkaConsumerInstrumentation.java index e5345ef..bbb05ce 100644 --- a/src/main/java/io/lumigo/core/instrumentation/impl/ApacheKafkaConsumerInstrumentation.java +++ b/src/main/java/io/lumigo/core/instrumentation/impl/ApacheKafkaConsumerInstrumentation.java @@ -16,9 +16,6 @@ import org.pmw.tinylog.Logger; public class ApacheKafkaConsumerInstrumentation implements LumigoInstrumentationApi { - - public static final String INSTRUMENTATION_PACKAGE_PREFIX = "org.apache.kafka.clients.consumer"; - @Override public ElementMatcher getTypeMatcher() { return named("org.apache.kafka.clients.consumer.KafkaConsumer"); @@ -48,7 +45,7 @@ public static class ApacheKafkaConsumerAdvice { public static void methodEnter(@Advice.FieldValue("clientId") String clientId) { try { startTimeMap.put(clientId, System.currentTimeMillis()); - } catch (Exception e) { + } catch (Throwable e) { Logger.error(e); } } diff --git a/src/main/java/io/lumigo/core/instrumentation/impl/ApacheKafkaProducerInstrumentation.java b/src/main/java/io/lumigo/core/instrumentation/impl/ApacheKafkaProducerInstrumentation.java index c6b3b37..829baa6 100644 --- a/src/main/java/io/lumigo/core/instrumentation/impl/ApacheKafkaProducerInstrumentation.java +++ b/src/main/java/io/lumigo/core/instrumentation/impl/ApacheKafkaProducerInstrumentation.java @@ -21,9 +21,6 @@ import org.pmw.tinylog.Logger; public class ApacheKafkaProducerInstrumentation implements LumigoInstrumentationApi { - - public static final String INSTRUMENTATION_PACKAGE_PREFIX = "org.apache.kafka.clients.producer"; - @Override public ElementMatcher getTypeMatcher() { return named("org.apache.kafka.clients.producer.KafkaProducer"); @@ -50,6 +47,7 @@ public AgentBuilder.Transformer.ForAdvice getTransformer() { ApacheKafkaProducerAdvice.class.getName()); } + @SuppressWarnings("unused") public static class ApacheKafkaProducerAdvice { public static final SpansContainer spansContainer = SpansContainer.getInstance(); @@ -78,7 +76,7 @@ public static void methodEnter( .toString() .substring(0, 10) .getBytes(StandardCharsets.UTF_8)); - } catch (Exception e) { + } catch (Throwable e) { Logger.error(e); } } diff --git a/src/main/java/io/lumigo/core/instrumentation/impl/AwsLambdaRequestHandlerInstrumentation.java b/src/main/java/io/lumigo/core/instrumentation/impl/AwsLambdaRequestHandlerInstrumentation.java new file mode 100644 index 0000000..65f2015 --- /dev/null +++ b/src/main/java/io/lumigo/core/instrumentation/impl/AwsLambdaRequestHandlerInstrumentation.java @@ -0,0 +1,82 @@ +package io.lumigo.core.instrumentation.impl; + +import static net.bytebuddy.matcher.ElementMatchers.*; + +import com.amazonaws.services.lambda.runtime.Context; +import io.lumigo.core.SpansContainer; +import io.lumigo.core.instrumentation.LumigoInstrumentationApi; +import io.lumigo.core.instrumentation.agent.Loader; +import io.lumigo.core.network.Reporter; +import io.lumigo.core.utils.EnvUtil; +import net.bytebuddy.agent.builder.AgentBuilder; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.implementation.bytecode.assign.Assigner.Typing; +import net.bytebuddy.matcher.ElementMatcher; +import org.pmw.tinylog.Logger; + +public class AwsLambdaRequestHandlerInstrumentation implements LumigoInstrumentationApi { + @Override + public ElementMatcher getTypeMatcher() { + return hasSuperType(named("com.amazonaws.services.lambda.runtime.RequestHandler")) + // we don't want to instrument handlers that implement our interfaces because they + // are already instrumented + .and( + not(hasSuperType(named("io.lumigo.handlers.LumigoRequestHandler"))) + .and( + not( + hasSuperType( + named( + "io.lumigo.handlers.LumigoRequestStreamHandler"))))); + } + + @Override + public AgentBuilder.Transformer.ForAdvice getTransformer() { + return new AgentBuilder.Transformer.ForAdvice() + .include(Loader.class.getClassLoader()) + .advice( + isMethod() + .and(isPublic()) + .and(named("handleRequest")) + .and( + takesArgument( + 1, + named( + "com.amazonaws.services.lambda.runtime.Context"))), + HandleRequestAdvice.class.getName()); + } + + @SuppressWarnings("unused") + public static class HandleRequestAdvice { + public static final SpansContainer spansContainer = SpansContainer.getInstance(); + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void methodEnter( + @Advice.Argument(value = 0, typing = Typing.DYNAMIC) Object input, + @Advice.Argument(1) Context context) { + try { + Logger.debug("Start AwsLambdaRequestHandlerInstrumentation$HandleRequestAdvice"); + spansContainer.init(new EnvUtil().getEnv(), new Reporter(), context, input); + spansContainer.start(); + Logger.debug("Finish sending start message and instrumentation"); + } catch (Throwable e) { + Logger.error(e, "Failed to init span container"); + } + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void methodExit( + @Advice.Return(readOnly = false) Object returnValue, + @Advice.Thrown Throwable throwable) { + try { + if (throwable != null) { + spansContainer.endWithException(throwable); + } else { + spansContainer.end(returnValue); + } + } catch (Throwable e) { + Logger.error(e, "Failed to create end span"); + } + } + } +}