Skip to content
This repository has been archived by the owner on Jan 12, 2024. It is now read-only.

Commit

Permalink
Send API Exception Metrics to SignalFx (#72)
Browse files Browse the repository at this point in the history
* Optionally send API error metrics to SignalFx
  • Loading branch information
sdford authored Oct 3, 2017
1 parent f1303e3 commit 950c830
Show file tree
Hide file tree
Showing 3 changed files with 185 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*
* Copyright (c) 2017 Nike, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

package com.nike.cerberus.error;

import com.codahale.metrics.Counter;
import com.codahale.metrics.MetricRegistry;
import com.nike.backstopper.apierror.ApiError;
import com.nike.backstopper.handler.ApiExceptionHandlerUtils;
import com.nike.backstopper.handler.RequestInfoForLogging;
import com.nike.internal.util.Pair;
import com.signalfx.codahale.metrics.MetricBuilder;
import com.signalfx.codahale.reporter.MetricMetadata;
import com.signalfx.codahale.reporter.SignalFxReporter;

import java.util.Collection;
import java.util.List;

/**
* A SignalFx-aware ApiExceptionHandlerUtils that increments an api_errors {@link Counter}
* metric with the following dimensions (based on the error info that gets logged):
* response_code, contributing_errors, and exception_class.
*/
public class SfxAwareApiExceptionHandlerUtils extends ApiExceptionHandlerUtils {

/**
* The name of the API errors metric sent to SignalFx.
*/
public static final String API_ERRORS_METRIC_NAME = "api_errors";
/**
* The name/key of the HTTP response code dimension applied to the API errors metric.
*/
public static final String RESPONSE_CODE_DIM_KEY = "response_code";
/**
* The name/key of the contributing errors dimension applied to the API errors metric.
*/
public static final String CONTRIBUTING_ERRORS_DIM_KEY = "contributing_errors";
/**
* The name/key of the exception class dimension applied to the API errors metric.
*/
public static final String EXCEPTION_CLASS_DIM_KEY = "exception_class";

protected final MetricRegistry metricRegistry;
protected final MetricMetadata sfxMetricMetadata;

/**
* Creates a new instance.
*
* @param metricRegistry The {@link MetricRegistry} that is used to create metrics for reporting to SignalFx.
* Cannot be null.
* @param sfxMetricMetadata The SignalFx reporter's {@link MetricMetadata} for building dimensioned metrics -
* this can be retrieved by calling {@link SignalFxReporter#getMetricMetadata()} on your SignalFx reporter.
* Cannot be null.
*/
public SfxAwareApiExceptionHandlerUtils(MetricRegistry metricRegistry,
MetricMetadata sfxMetricMetadata) {
if (metricRegistry == null)
throw new IllegalArgumentException("metricRegistry cannot be null");

if (sfxMetricMetadata == null)
throw new IllegalArgumentException("sfxMetricMetadata cannot be null");

this.metricRegistry = metricRegistry;
this.sfxMetricMetadata = sfxMetricMetadata;
}

@Override
public String buildErrorMessageForLogs(StringBuilder sb, RequestInfoForLogging request,
Collection<ApiError> contributingErrors, Integer httpStatusCode,
Throwable cause,
List<Pair<String, String>> extraDetailsForLogging) {
try {
// Do the normal logging thing.
return super.buildErrorMessageForLogs(
sb, request, contributingErrors, httpStatusCode, cause, extraDetailsForLogging
);
}
finally {
// Update SignalFx metrics around API Errors.
String contributingErrorsString = contributingErrors == null
? "[NONE]"
: concatenateErrorCollection(contributingErrors);

Counter apiErrorsCounterMetric = sfxMetricMetadata
.forBuilder(MetricBuilder.COUNTERS)
.withMetricName(API_ERRORS_METRIC_NAME)
.withDimension(RESPONSE_CODE_DIM_KEY, String.valueOf(httpStatusCode))
.withDimension(CONTRIBUTING_ERRORS_DIM_KEY, contributingErrorsString)
.withDimension(EXCEPTION_CLASS_DIM_KEY, cause.getClass().getName())
.createOrGet(metricRegistry);

apiErrorsCounterMetric.inc();
}
}
}

4 changes: 3 additions & 1 deletion src/main/java/com/nike/cerberus/server/config/CmsConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import com.netflix.config.ConfigurationManager;
import com.netflix.config.DynamicPropertyFactory;
import com.nike.backstopper.handler.riposte.config.guice.BackstopperRiposteConfigGuiceModule;
import com.nike.cerberus.server.config.guice.CerberusBackstopperRiposteGuiceModule;
import com.nike.cerberus.server.config.guice.CmsFlywayModule;
import com.nike.cerberus.server.config.guice.CmsGuiceModule;
import com.nike.cerberus.server.config.guice.CmsMyBatisModule;
Expand Down Expand Up @@ -106,7 +107,8 @@ protected CmsConfig(Config appConfig, PropertiesRegistrationGuiceModule properti
new BackstopperRiposteConfigGuiceModule(),
new CmsFlywayModule(),
new OneLoginGuiceModule(),
new MetricsGuiceModule()
new MetricsGuiceModule(),
new CerberusBackstopperRiposteGuiceModule()
));

// bind the CMS Guice module last allowing the S3 props file to override any given application property
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* Copyright (c) 2017 Nike, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

package com.nike.cerberus.server.config.guice;

import com.codahale.metrics.MetricRegistry;
import com.google.inject.AbstractModule;
import com.google.inject.Provides;
import com.nike.backstopper.handler.ApiExceptionHandlerUtils;
import com.nike.cerberus.error.SfxAwareApiExceptionHandlerUtils;
import com.nike.riposte.metrics.codahale.CodahaleMetricsCollector;
import com.nike.riposte.metrics.codahale.contrib.SignalFxReporterFactory;
import com.signalfx.codahale.reporter.MetricMetadata;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nullable;
import javax.inject.Singleton;

public class CerberusBackstopperRiposteGuiceModule extends AbstractModule {
private final Logger logger = LoggerFactory.getLogger(this.getClass());

@Override
protected void configure() {
}

/**
* @param metricsCollector The {@link CodahaleMetricsCollector} being used for the app. Can be null - if null is
* passed in then {@link ApiExceptionHandlerUtils} will be returned.
* @param sfxReporterFactory The {@link SignalFxReporterFactory} being used for the app. Can be null - if null is
* passed in then {@link ApiExceptionHandlerUtils} will be returned.
* @return A {@link SfxAwareApiExceptionHandlerUtils} if the given args are not null, or {@link
* ApiExceptionHandlerUtils} if either arg is null.
*/
@Provides
@Singleton
public ApiExceptionHandlerUtils sfxAwareApiExceptionHandlerUtils(@Nullable CodahaleMetricsCollector metricsCollector,
@Nullable SignalFxReporterFactory sfxReporterFactory) {

MetricRegistry metricRegistry = metricsCollector == null ? null : metricsCollector.getMetricRegistry();
MetricMetadata sfxMetricMetadata = sfxReporterFactory == null || metricRegistry == null
? null
: sfxReporterFactory.getReporter(metricRegistry).getMetricMetadata();

if (metricRegistry == null || sfxMetricMetadata == null) {
logger.warn("Unable to do SignalFx metric gathering around API Errors - the CodahaleMetricsCollector "
+ "and/or SignalFxReporterFactory were null. Defaulting to ApiExceptionHandlerUtils. "
+ "metrics_collector_is_null={}, sfx_reporter_factory_is_null={}",
metricsCollector == null,
sfxReporterFactory == null);
return new ApiExceptionHandlerUtils();
}

// We have all the bits we need to do metrics reporting, so return a SfxAwareApiExceptionHandlerUtils
// that will do it.
return new SfxAwareApiExceptionHandlerUtils(metricRegistry, sfxMetricMetadata);
}
}

0 comments on commit 950c830

Please sign in to comment.