Skip to content

fixes #117 Add CloudWatchMetricsMiddleware for metrics reporting #118

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@
<version.lambda-core>1.2.3</version.lambda-core>
<version.lambda-events>3.11.6</version.lambda-events>
<version.lambda-awssdk>2.26.4</version.lambda-awssdk>
<version.aws-metrics>4.2.0</version.aws-metrics>
<version.dynamodb>1.12.667</version.dynamodb>
<version.lambda-iam>1.12.745</version.lambda-iam>
<version.jose4j>0.9.6</version.jose4j>
Expand Down Expand Up @@ -303,6 +304,11 @@
<artifactId>aws-lambda-java-core</artifactId>
<version>${version.lambda-core}</version>
</dependency>
<dependency>
<groupId>software.amazon.cloudwatchlogs</groupId>
<artifactId>aws-embedded-metrics</artifactId>
<version>${version.aws-metrics}</version>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-dynamodb</artifactId>
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,22 @@

import com.networknt.aws.lambda.handler.MiddlewareHandler;
import com.networknt.aws.lambda.LightLambdaExchange;
import com.networknt.aws.lambda.handler.middleware.audit.AuditMiddleware;
import com.networknt.config.JsonMapper;
import com.networknt.metrics.JVMMetricsDbReporter;
import com.networknt.metrics.MetricsConfig;
import com.networknt.metrics.TimeSeriesDbSender;
import com.networknt.status.Status;
import com.networknt.utility.Constants;
import io.dropwizard.metrics.Metric;
import io.dropwizard.metrics.MetricFilter;
import io.dropwizard.metrics.MetricName;
import io.dropwizard.metrics.MetricRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import software.amazon.cloudwatchlogs.emf.logger.MetricsLogger;
import software.amazon.cloudwatchlogs.emf.model.MetricsContext;
import software.amazon.cloudwatchlogs.emf.model.Unit;

import java.util.HashMap;
import java.util.Map;
Expand All @@ -24,43 +29,28 @@

public abstract class AbstractMetricsMiddleware implements MiddlewareHandler {
static final Logger logger = LoggerFactory.getLogger(AbstractMetricsMiddleware.class);
public static final LightLambdaExchange.Attachable<AbstractMetricsMiddleware> METRICS_LOGGER_ATTACHMENT_KEY = LightLambdaExchange.Attachable.createAttachable(AbstractMetricsMiddleware.class);
// The metrics.yml configuration that supports reload.
public static MetricsConfig config;
static Pattern pattern;
// The structure that collect all the metrics entries. Even others will be using this structure to inject.
public static final MetricRegistry registry = new MetricRegistry();
public Map<String, String> commonTags = new HashMap<>();

public AbstractMetricsMiddleware() {
}


@Override
public boolean isEnabled() {
return config.isEnabled();
}

public void createJVMMetricsReporter(final TimeSeriesDbSender sender) {
JVMMetricsDbReporter jvmReporter = new JVMMetricsDbReporter(new MetricRegistry(), sender, "jvm-reporter",
MetricFilter.ALL, TimeUnit.SECONDS, TimeUnit.MILLISECONDS, commonTags);
jvmReporter.start(config.getReportInMinutes(), TimeUnit.MINUTES);
}

public void incCounterForStatusCode(int statusCode, Map<String, String> commonTags, Map<String, String> tags) {
MetricName metricName = new MetricName("request").tagged(commonTags).tagged(tags);
registry.getOrAdd(metricName, MetricRegistry.MetricBuilder.COUNTERS).inc();
public void incCounterForStatusCode(MetricsLogger metricsLogger, int statusCode) {
metricsLogger.putMetric("request", 1, Unit.COUNT);
if (statusCode >= 200 && statusCode < 400) {
metricName = new MetricName("success").tagged(commonTags).tagged(tags);
registry.getOrAdd(metricName, MetricRegistry.MetricBuilder.COUNTERS).inc();
metricsLogger.putMetric("success", 1, Unit.COUNT);
} else if (statusCode == 401 || statusCode == 403) {
metricName = new MetricName("auth_error").tagged(commonTags).tagged(tags);
registry.getOrAdd(metricName, MetricRegistry.MetricBuilder.COUNTERS).inc();
metricsLogger.putMetric("auth_error", 1, Unit.COUNT);
} else if (statusCode >= 400 && statusCode < 500) {
metricName = new MetricName("request_error").tagged(commonTags).tagged(tags);
registry.getOrAdd(metricName, MetricRegistry.MetricBuilder.COUNTERS).inc();
metricsLogger.putMetric("request_error", 1, Unit.COUNT);
} else if (statusCode >= 500) {
metricName = new MetricName("server_error").tagged(commonTags).tagged(tags);
registry.getOrAdd(metricName, MetricRegistry.MetricBuilder.COUNTERS).inc();
metricsLogger.putMetric("server_error", 1, Unit.COUNT);
}
}

Expand All @@ -70,69 +60,17 @@ public void incCounterForStatusCode(int statusCode, Map<String, String> commonTa
* @param exchange the LightLambdaExchange that is used to get the auditInfo to collect the metrics tag.
* @param startTime the start time passed in to calculate the response time.
* @param metricsName the name of the metrics that is collected.
* @param endpoint the endpoint that is used to collect the metrics. It is optional and only provided by the external handlers.
*/
public void injectMetrics(LightLambdaExchange exchange, long startTime, String metricsName, String endpoint) {
Map<String, Object> auditInfo = (Map<String, Object>)exchange.getAttachment(AUDIT_ATTACHMENT_KEY);
if(logger.isTraceEnabled()) logger.trace("auditInfo = " + auditInfo);
Map<String, String> tags = new HashMap<>();
if (auditInfo != null) {
// for external handlers, the endpoint must be unknown in the auditInfo. If that is the case, use the endpoint passed in.
if (endpoint != null) {
tags.put(Constants.ENDPOINT_STRING, endpoint);
} else {
tags.put(Constants.ENDPOINT_STRING, (String) auditInfo.get(Constants.ENDPOINT_STRING));
}
String clientId = auditInfo.get(Constants.CLIENT_ID_STRING) != null ? (String) auditInfo.get(Constants.CLIENT_ID_STRING) : "unknown";
if(logger.isTraceEnabled()) logger.trace("clientId = {}", clientId);
tags.put("clientId", clientId);
// scope client id will only be available if two token is used. For example, authorization code flow.
if (config.isSendScopeClientId()) {
tags.put("scopeClientId", auditInfo.get(Constants.SCOPE_CLIENT_ID_STRING) != null ? (String) auditInfo.get(Constants.SCOPE_CLIENT_ID_STRING) : "unknown");
}
// caller id is the calling serviceId that is passed from the caller. It is not always available but some organizations enforce it.
if (config.isSendCallerId()) {
tags.put("callerId", auditInfo.get(Constants.CALLER_ID_STRING) != null ? (String) auditInfo.get(Constants.CALLER_ID_STRING) : "unknown");
}
if (config.isSendIssuer()) {
String issuer = (String) auditInfo.get(Constants.ISSUER_CLAIMS);
if (issuer != null) {
// we need to send issuer as a tag. Do we need to apply regex to extract only a part of the issuer?
if(config.getIssuerRegex() != null) {
Matcher matcher = pattern.matcher(issuer);
if (matcher.find()) {
String iss = matcher.group(1);
if(logger.isTraceEnabled()) logger.trace("Extracted issuer {} from Original issuer {] is sent.", iss, issuer);
tags.put("issuer", iss != null ? iss : "unknown");
}
} else {
if(logger.isTraceEnabled()) logger.trace("Original issuer {} is sent.", issuer);
tags.put("issuer", issuer);
}
}
}
} else {
// for MRAS and Salesforce handlers that do not have auditInfo in the exchange as they may be called anonymously.
tags.put(Constants.ENDPOINT_STRING, endpoint == null ? "unknown" : endpoint);
tags.put("clientId", "unknown");
if (config.isSendScopeClientId()) {
tags.put("scopeClientId", "unknown");
}
if (config.isSendCallerId()) {
tags.put("callerId", "unknown");
}
if (config.isSendIssuer()) {
tags.put("issuer", "unknown");
}
public void injectMetrics(LightLambdaExchange exchange, long startTime, String metricsName) {
MetricsLogger metricsLogger = (exchange.getAttachment(METRICS_LOGGER_ATTACHMENT_KEY) != null) ? (MetricsLogger) exchange.getAttachment(METRICS_LOGGER_ATTACHMENT_KEY) : null;
if(metricsLogger == null) {
if(logger.isTraceEnabled()) logger.trace("metricsContext is null, create one.");
metricsLogger = new MetricsLogger();
exchange.addAttachment(METRICS_LOGGER_ATTACHMENT_KEY, metricsLogger);
}
MetricName metricName = new MetricName(metricsName);
metricName = metricName.tagged(commonTags);
metricName = metricName.tagged(tags);
long time = System.nanoTime() - startTime;
registry.getOrAdd(metricName, MetricRegistry.MetricBuilder.TIMERS).update(time, TimeUnit.NANOSECONDS);
long time = System.currentTimeMillis() - startTime;
metricsLogger.putMetric(metricsName, time, Unit.MILLISECONDS);
if(logger.isTraceEnabled())
logger.trace("metricName = {} commonTags = {} tags = {}", metricName, JsonMapper.toJson(commonTags), JsonMapper.toJson(tags));
// the metrics handler will collect the status code metrics and increase the counter. Here we don't want to increase it again.
// incCounterForStatusCode(httpServerExchange.getStatusCode(), commonTags, tags);
logger.trace("metricName {} is injected with time {}", metricsName, time);
}
}
Loading