Skip to content

feat: single metric utility method to pick default namespace #305

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 4 commits into from
Mar 1, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

import static java.util.Optional.empty;
import static java.util.Optional.of;
import static software.amazon.lambda.powertools.core.internal.SystemWrapper.getenv;

public final class LambdaHandlerProcessor {
// SERVICE_NAME cannot be final for testing purposes
Expand Down Expand Up @@ -85,4 +86,12 @@ public static void coldStartDone() {
public static boolean isSamLocal() {
return "true".equals(System.getenv("AWS_SAM_LOCAL"));
}

public static Optional<String> getXrayTraceId() {
final String X_AMZN_TRACE_ID = getenv("_X_AMZN_TRACE_ID");
if(X_AMZN_TRACE_ID != null) {
return of(X_AMZN_TRACE_ID.split(";")[0].replace("Root=", ""));
}
return empty();
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package software.amazon.lambda.powertools.logging.internal;
package software.amazon.lambda.powertools.core.internal;

class SystemWrapper {
public class SystemWrapper {
private SystemWrapper() {
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,10 @@

import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Optional.empty;
import static java.util.Optional.of;
import static java.util.Optional.ofNullable;
import static software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor.coldStartDone;
import static software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor.extractContext;
import static software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor.getXrayTraceId;
import static software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor.isColdStart;
import static software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor.isHandlerMethod;
import static software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor.placedOnRequestHandler;
Expand All @@ -50,7 +50,6 @@
import static software.amazon.lambda.powertools.logging.LoggingUtils.appendKey;
import static software.amazon.lambda.powertools.logging.LoggingUtils.appendKeys;
import static software.amazon.lambda.powertools.logging.LoggingUtils.objectMapper;
import static software.amazon.lambda.powertools.logging.internal.SystemWrapper.getenv;

@Aspect
public final class LambdaLoggingAspect {
Expand Down Expand Up @@ -199,12 +198,4 @@ private Optional<String> asJson(final ProceedingJoinPoint pjp,
private Logger logger(final ProceedingJoinPoint pjp) {
return LogManager.getLogger(pjp.getSignature().getDeclaringType());
}

private static Optional<String> getXrayTraceId() {
final String X_AMZN_TRACE_ID = getenv("_X_AMZN_TRACE_ID");
if(X_AMZN_TRACE_ID != null) {
return of(X_AMZN_TRACE_ID.split(";")[0].replace("Root=", ""));
}
return empty();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import org.mockito.Mock;
import org.mockito.MockedStatic;
import software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor;
import software.amazon.lambda.powertools.core.internal.SystemWrapper;
import software.amazon.lambda.powertools.logging.handlers.PowerLogToolEnabled;
import software.amazon.lambda.powertools.logging.handlers.PowerLogToolEnabledForStream;
import software.amazon.lambda.powertools.logging.handlers.PowerToolDisabled;
Expand All @@ -66,7 +67,7 @@
import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.openMocks;
import static org.skyscreamer.jsonassert.JSONAssert.assertEquals;
import static software.amazon.lambda.powertools.logging.internal.SystemWrapper.getenv;
import static software.amazon.lambda.powertools.core.internal.SystemWrapper.getenv;

class LambdaLoggingAspectTest {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public static long dimensionsCount() {
return metricsContext().getDimensions().size();
}

private static MetricsContext metricsContext() {
public static MetricsContext metricsContext() {
try {
Field f = metricsLogger().getClass().getDeclaredField("context");
f.setAccessible(true);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
package software.amazon.lambda.powertools.metrics;

import java.util.Optional;
import java.util.function.Consumer;

import software.amazon.cloudwatchlogs.emf.config.SystemWrapper;
import software.amazon.cloudwatchlogs.emf.logger.MetricsLogger;
import software.amazon.cloudwatchlogs.emf.model.MetricsContext;
import software.amazon.cloudwatchlogs.emf.model.MetricsLoggerHelper;
import software.amazon.cloudwatchlogs.emf.model.Unit;

import static java.util.Optional.ofNullable;
import static software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor.getXrayTraceId;
import static software.amazon.lambda.powertools.metrics.internal.LambdaMetricsAspect.REQUEST_ID_PROPERTY;
import static software.amazon.lambda.powertools.metrics.internal.LambdaMetricsAspect.TRACE_ID_PROPERTY;

/**
* A class used to retrieve the instance of the {@code MetricsLogger} used by
* {@code Metrics}.
Expand All @@ -26,8 +35,36 @@ public static MetricsLogger metricsLogger() {
return metricsLogger;
}

/**
* Add and immediately flush a single metric. It will use the default namespace
* specified either on {@link Metrics} annotation or via POWERTOOLS_METRICS_NAMESPACE env var.
* It by default captures AwsRequestId as property if used together with {@link Metrics} annotation. It will also
* capture XrayTraceId as property if tracing is enabled.
*
* @param name the name of the metric
* @param value the value of the metric
* @param unit the unit type of the metric
* @param logger the MetricsLogger
*/
public static void withSingleMetric(final String name,
final double value,
final Unit unit,
final Consumer<MetricsLogger> logger) {
MetricsLogger metricsLogger = new MetricsLogger();
try {
metricsLogger.setNamespace(defaultNameSpace());
metricsLogger.putMetric(name, value, unit);
captureRequestAndTraceId(metricsLogger);
logger.accept(metricsLogger);
} finally {
metricsLogger.flush();
}
}

/**
* Add and immediately flush a single metric.
* It by default captures AwsRequestId as property if used together with {@link Metrics} annotation. It will also
* capture XrayTraceId as property if tracing is enabled.
*
* @param name the name of the metric
* @param value the value of the metric
Expand All @@ -44,9 +81,30 @@ public static void withSingleMetric(final String name,
try {
metricsLogger.setNamespace(namespace);
metricsLogger.putMetric(name, value, unit);
captureRequestAndTraceId(metricsLogger);
logger.accept(metricsLogger);
} finally {
metricsLogger.flush();
}
}

private static void captureRequestAndTraceId(MetricsLogger metricsLogger) {
awsRequestId().
ifPresent(requestId -> metricsLogger.putProperty(REQUEST_ID_PROPERTY, requestId));

getXrayTraceId()
.ifPresent(traceId -> metricsLogger.putProperty(TRACE_ID_PROPERTY, traceId));
}

private static String defaultNameSpace() {
MetricsContext context = MetricsLoggerHelper.metricsContext();
return "aws-embedded-metrics".equals(context.getNamespace()) ?
SystemWrapper.getenv("POWERTOOLS_METRICS_NAMESPACE"): context.getNamespace();
}

private static Optional<String> awsRequestId() {
MetricsContext context = MetricsLoggerHelper.metricsContext();
return ofNullable(context.getProperty(REQUEST_ID_PROPERTY))
.map(Object::toString);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import software.amazon.cloudwatchlogs.emf.model.DimensionSet;
import software.amazon.cloudwatchlogs.emf.model.MetricsContext;
import software.amazon.cloudwatchlogs.emf.model.Unit;
import software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor;
import software.amazon.lambda.powertools.metrics.Metrics;
import software.amazon.lambda.powertools.metrics.ValidationException;

Expand All @@ -27,6 +28,8 @@
@Aspect
public class LambdaMetricsAspect {
private static final String NAMESPACE = System.getenv("POWERTOOLS_METRICS_NAMESPACE");
public static final String TRACE_ID_PROPERTY = "XrayTraceId";
public static final String REQUEST_ID_PROPERTY = "AwsRequestId";

@SuppressWarnings({"EmptyMethod"})
@Pointcut("@annotation(metrics)")
Expand All @@ -49,9 +52,12 @@ public Object around(ProceedingJoinPoint pjp,

extractContext(pjp).ifPresent((context) -> {
coldStartSingleMetricIfApplicable(context.getAwsRequestId(), context.getFunctionName(), metrics);
logger.putProperty("AwsRequestId", context.getAwsRequestId());
logger.putProperty(REQUEST_ID_PROPERTY, context.getAwsRequestId());
});

LambdaHandlerProcessor.getXrayTraceId()
.ifPresent(traceId -> logger.putProperty(TRACE_ID_PROPERTY, traceId));

try {
return pjp.proceed(proceedArgs);

Expand All @@ -75,7 +81,7 @@ && isColdStart()) {
metricsLogger.setNamespace(namespace(metrics));
metricsLogger.putMetric("ColdStart", 1, Unit.COUNT);
metricsLogger.setDimensions(DimensionSet.of("Service", service(metrics), "FunctionName", functionName));
metricsLogger.putProperty("AwsRequestId", awsRequestId);
metricsLogger.putProperty(REQUEST_ID_PROPERTY, awsRequestId);
metricsLogger.flush();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import static java.util.Collections.*;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mockStatic;
import static software.amazon.lambda.powertools.core.internal.SystemWrapper.getenv;

class MetricsLoggerTest {

Expand All @@ -44,8 +45,10 @@ static void beforeAll() {

@Test
void singleMetricsCaptureUtility() {
try (MockedStatic<SystemWrapper> mocked = mockStatic(SystemWrapper.class)) {
try (MockedStatic<SystemWrapper> mocked = mockStatic(SystemWrapper.class);
MockedStatic<software.amazon.lambda.powertools.core.internal.SystemWrapper> internalWrapper = mockStatic(software.amazon.lambda.powertools.core.internal.SystemWrapper.class)) {
mocked.when(() -> SystemWrapper.getenv("AWS_EMF_ENVIRONMENT")).thenReturn("Lambda");
internalWrapper.when(() -> getenv("_X_AMZN_TRACE_ID")).thenReturn("Root=1-5759e988-bd862e3fe1be46a994272793;Parent=53995c3f42cd8ad8;Sampled=1\"");

MetricsUtils.withSingleMetric("Metric1", 1, Unit.COUNT, "test",
metricsLogger -> metricsLogger.setDimensions(DimensionSet.of("Dimension1", "Value1")));
Expand All @@ -57,7 +60,38 @@ void singleMetricsCaptureUtility() {
assertThat(logAsJson)
.containsEntry("Metric1", 1.0)
.containsEntry("Dimension1", "Value1")
.containsKey("_aws");
.containsKey("_aws")
.containsEntry("XrayTraceId", "1-5759e988-bd862e3fe1be46a994272793");
});
}
}

@Test
void singleMetricsCaptureUtilityWithDefaultNameSpace() {
try (MockedStatic<SystemWrapper> mocked = mockStatic(SystemWrapper.class);
MockedStatic<software.amazon.lambda.powertools.core.internal.SystemWrapper> internalWrapper = mockStatic(software.amazon.lambda.powertools.core.internal.SystemWrapper.class)) {
mocked.when(() -> SystemWrapper.getenv("AWS_EMF_ENVIRONMENT")).thenReturn("Lambda");
mocked.when(() -> SystemWrapper.getenv("POWERTOOLS_METRICS_NAMESPACE")).thenReturn("GlobalName");
internalWrapper.when(() -> getenv("_X_AMZN_TRACE_ID")).thenReturn("Root=1-5759e988-bd862e3fe1be46a994272793;Parent=53995c3f42cd8ad8;Sampled=1\"");

MetricsUtils.withSingleMetric("Metric1", 1, Unit.COUNT,
metricsLogger -> metricsLogger.setDimensions(DimensionSet.of("Dimension1", "Value1")));

assertThat(out.toString())
.satisfies(s -> {
Map<String, Object> logAsJson = readAsJson(s);

assertThat(logAsJson)
.containsEntry("Metric1", 1.0)
.containsEntry("Dimension1", "Value1")
.containsKey("_aws")
.containsEntry("XrayTraceId", "1-5759e988-bd862e3fe1be46a994272793");

Map<String, Object> aws = (Map<String, Object>) logAsJson.get("_aws");

assertThat(aws.get("CloudWatchMetrics"))
.asString()
.contains("Namespace=GlobalName");
});
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import software.amazon.cloudwatchlogs.emf.logger.MetricsLogger;
import software.amazon.cloudwatchlogs.emf.model.DimensionSet;
import software.amazon.cloudwatchlogs.emf.model.Unit;
import software.amazon.lambda.powertools.metrics.Metrics;

import static software.amazon.lambda.powertools.metrics.MetricsUtils.metricsLogger;
import static software.amazon.lambda.powertools.metrics.MetricsUtils.withSingleMetric;

public class PowertoolsMetricsEnabledHandler implements RequestHandler<Object, Object> {

Expand All @@ -16,6 +18,10 @@ public Object handleRequest(Object input, Context context) {
MetricsLogger metricsLogger = metricsLogger();
metricsLogger.putMetric("Metric1", 1, Unit.BYTES);


withSingleMetric("Metric2", 1, Unit.COUNT,
Copy link
Contributor

Choose a reason for hiding this comment

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

nice!

log -> log.setDimensions(DimensionSet.of("Dimension1", "Value1")));

return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.openMocks;
import static software.amazon.lambda.powertools.core.internal.SystemWrapper.getenv;

public class LambdaMetricsAspectTest {
@Mock
Expand Down Expand Up @@ -71,13 +72,33 @@ void tearDown() {

@Test
public void metricsWithoutColdStart() {
try (MockedStatic<SystemWrapper> mocked = mockStatic(SystemWrapper.class)) {
try (MockedStatic<SystemWrapper> mocked = mockStatic(SystemWrapper.class);
MockedStatic<software.amazon.lambda.powertools.core.internal.SystemWrapper> internalWrapper = mockStatic(software.amazon.lambda.powertools.core.internal.SystemWrapper.class)) {

mocked.when(() -> SystemWrapper.getenv("AWS_EMF_ENVIRONMENT")).thenReturn("Lambda");
internalWrapper.when(() -> getenv("_X_AMZN_TRACE_ID")).thenReturn("Root=1-5759e988-bd862e3fe1be46a994272793;Parent=53995c3f42cd8ad8;Sampled=1\"");

requestHandler.handleRequest("input", context);

assertThat(out.toString())
assertThat(out.toString().split("\n"))
.hasSize(2)
.satisfies(s -> {
Map<String, Object> logAsJson = readAsJson(s);
Map<String, Object> logAsJson = readAsJson(s[0]);

assertThat(logAsJson)
.containsEntry("Metric2", 1.0)
.containsEntry("Dimension1", "Value1")
.containsKey("_aws")
.containsEntry("XrayTraceId", "1-5759e988-bd862e3fe1be46a994272793")
.containsEntry("AwsRequestId", "123ABC");

Map<String, Object> aws = (Map<String, Object>) logAsJson.get("_aws");

assertThat(aws.get("CloudWatchMetrics"))
.asString()
.contains("Namespace=ExampleApplication");

logAsJson = readAsJson(s[1]);

assertThat(logAsJson)
.containsEntry("Metric1", 1.0)
Expand Down