Skip to content

feat(v2): Add GraalVM reachability metadata for core utilities #1753

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

Merged
merged 24 commits into from
Mar 13, 2025

Conversation

rr-on-gh
Copy link

@rr-on-gh rr-on-gh commented Dec 3, 2024

Issue #, if available: #764

Description of changes:

Adding GraalVM reachability metadata files for logging, metrics and tracing modules. Also added a SAM based sample application to demonstrate the usage.

Checklist

Breaking change checklist

RFC issue #:

  • Migration process documented
  • Implement warnings (if it can live side by side)

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

Copy link

sonarqubecloud bot commented Dec 5, 2024

@phipag
Copy link
Contributor

phipag commented Feb 20, 2025

Hi @rr-on-gh, I tested the @Metrics part of your PR today and found it working with one minor change. I created a basic Quarkus app following the AWS Lambda documentation and compiled it to a native image.

For reference, this is my test Lambda handler:

@Named("test")
public class TestLambda implements RequestHandler<InputObject, OutputObject> {

    @Inject
    ProcessingService service;

    private final static Logger logger = Logger.getLogger(TestLambda.class);
    private final static MetricsLogger metricsLogger = MetricsUtils.metricsLogger();

    @Override
    @Metrics(namespace = "QuarkusApp", service = "powertools")
    public OutputObject handleRequest(InputObject input, Context context) {
        logger.info("Quarkus Jboss Log.");  // Logging with Jboss is not yet supported by Powertools

        long startTime = System.currentTimeMillis();
        final OutputObject output = service.process(input).setRequestId(context.getAwsRequestId());
        metricsLogger.putMetric("ExecutionTime", System.currentTimeMillis() - startTime, Unit.MILLISECONDS);
        metricsLogger.putMetric("Invocation", 1, Unit.COUNT);
        return output;
    }
}

When I compile this using Java version: 21.0.2+13, vendor version: GraalVM CE 21.0.2+13.1 it will raise an error coming from CloudWatch EMF:

2025-02-20 09:26:02,585 INFO  [qua.TestLambda] (Lambda Thread (NORMAL)) Quarkus Jboss Log.
2025-02-20 09:26:02,585 INFO  [sof.ama.clo.emf.env.AgentBasedEnvironment] (Lambda Thread (NORMAL)) Endpoint is not defined. Using default: tcp://127.0.0.1:25888
2025-02-20 09:26:02,585 WARN  [sof.ama.clo.emf.env.AgentBasedEnvironment] (Lambda Thread (NORMAL)) Unknown ServiceName.
2025-02-20 09:26:02,585 WARN  [sof.ama.clo.emf.env.AgentBasedEnvironment] (Lambda Thread (NORMAL)) Unknown ServiceName.
2025-02-20 09:26:02,585 WARN  [sof.ama.clo.emf.env.AgentBasedEnvironment] (Lambda Thread (NORMAL)) Unknown ServiceName.
2025-02-20 09:26:02,585 INFO  [sof.ama.clo.emf.env.DefaultEnvironment] (Lambda Thread (NORMAL)) Unknown ServiceType

The CloudWatch EMF dependency seems to think we are running in an agent-based environment and tries to connect to a CloudWatch agent endpoint on localhost. If I instruct the MetricsLogger manually to use the LambdaEnvironment the error is gone and the metrics are emitted as expected when deployed as native image.

--- a/powertools-metrics/src/main/java/software/amazon/lambda/powertools/metrics/MetricsUtils.java
+++ b/powertools-metrics/src/main/java/software/amazon/lambda/powertools/metrics/MetricsUtils.java
@@ -22,8 +22,10 @@ import static software.amazon.lambda.powertools.metrics.internal.LambdaMetricsAs
 import java.util.Arrays;
 import java.util.Optional;
 import java.util.function.Consumer;
+
 import software.amazon.cloudwatchlogs.emf.config.SystemWrapper;
 import software.amazon.cloudwatchlogs.emf.environment.EnvironmentProvider;
+import software.amazon.cloudwatchlogs.emf.environment.LambdaEnvironment;
 import software.amazon.cloudwatchlogs.emf.logger.MetricsLogger;
 import software.amazon.cloudwatchlogs.emf.model.DimensionSet;
 import software.amazon.cloudwatchlogs.emf.model.MetricsContext;
@@ -37,7 +39,7 @@ import software.amazon.cloudwatchlogs.emf.model.Unit;
  * {@see Metrics}
  */
 public final class MetricsUtils {
-    private static final MetricsLogger metricsLogger = new MetricsLogger();
+    private static final MetricsLogger metricsLogger = new MetricsLogger(new LambdaEnvironment());
     private static DimensionSet[] defaultDimensions;
 
     private MetricsUtils() {

Output:

2025-02-20 09:30:14,196 INFO  [qua.TestLambda] (Lambda Thread (NORMAL)) Quarkus Jboss Log.
{"_aws":{"Timestamp":1740043814196,"CloudWatchMetrics":[{"Namespace":"QuarkusApp","Metrics":[{"Name":"ExecutionTime","Unit":"Milliseconds"},{"Name":"Invocation","Unit":"Count"}],"Dimensions":[["Service"]]}]},"function_request_id":"f724bc2d-3c25-4721-b24c-291b086f822e","ExecutionTime":0.0,"xray_trace_id":"1-67b6f625-3fc6683351ac598520a2ce43","functionVersion":"$LATEST","Invocation":1.0,"Service":"powertools","logStreamId":"2025/02/20/[$LATEST]941343d9f3924a079878a93813e8ae84"}

Note: This behavior does not occur when using JVM.

@phipag phipag self-requested a review February 20, 2025 09:32
Copy link
Contributor

@phipag phipag left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @rr-on-gh for updating the PR and providing the GraalVM.md as documentation. It is clear now how you generated the GraalVM files. I only added a few minor comments regarding markdown issues.

I also tested the example you provided without problems.

Comment on lines 13 to 15
```shell
export JAVA_HOME=<path to GraalVM>
````
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is one backtick too much at the end. And can you remove the indentation inside the code block?


- Build the Docker image that will be used as the environment for SAM build:
```shell
docker build --platform linux/amd64 . -t powertools-examples-core-sam-graalvm
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove indentation.


- Build the SAM project using the docker image
```shell
sam build --use-container --build-image powertools-examples-core-sam-graalvm
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove indentation.

@phipag
Copy link
Contributor

phipag commented Mar 10, 2025

I also resolved the log4j version conflict with the v2 branch.

@phipag phipag moved this to Pending review in Powertools for AWS Lambda (Java) Mar 10, 2025
@phipag phipag added v2 Version 2 feature-request New feature or request and removed feature-request New feature or request v2 Version 2 labels Mar 10, 2025
…tEnvironmentVariable annotation to override env variables.
Copy link
Contributor

@phipag phipag left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great. Removing the static mocks is a big improvement and allows us to avoid skipping the unit tests using static mock.

Next step is to update the GraalVM.md documentation with the updated process of generating the metadata files.

@@ -62,6 +59,9 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junitpioneer.jupiter.ClearEnvironmentVariable;
import org.junitpioneer.jupiter.SetEnvironmentVariable;
import org.junitpioneer.jupiter.SetSystemProperty;
import org.mockito.Mock;
import org.mockito.MockedStatic;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This import is unused now.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same for SystemWrapper in line 73.

@phipag
Copy link
Contributor

phipag commented Mar 12, 2025

I pushed a small fix to this PR e879e06. This unit test was failing in native mode (mvn -Pgraalvm-native clean test). I assume GraalVM has problems with the getContextClassLoader inside the test – I replaced with a temp file creation which passes both on JVM and native image mode.

Now, all tests pass when I run

mvn -Pgenerate-graalvm-files clean test && mvn -Pgraalvm-native clean test

@phipag
Copy link
Contributor

phipag commented Mar 12, 2025

Next steps for this PR:

  • Finalize documentation in GraalVM.md (all tests should now pass without skipping any such as StaticMock like in the initial version of the PR).
  • Create follow-up issues:
    • Integrate GraalVM compilation and unit tests with v2 GitHub workflows (we should run them for both JVM and GraalVM)
    • Update end-to-end tests to run both against JVM and GraalVM (for the modules that already support GraalVM)
  • Merge PR into v2 branch

@phipag
Copy link
Contributor

phipag commented Mar 13, 2025

I pushed some smaller cosmetic changes. This PR is ready to be merged as an initial version of GraalVM support for the core utilities. We need to iterate more on GraalVM, such as debugging support with Quarkus native images and adding GraalVM as additional tests in the GitHub workflows and end-to-end tests. I will create follow-up issues for these topics.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

GraalVM Support for Core Utilities (Logging, Metrics, Tracing)
2 participants