com.stackify.error.log4j12.StackifyErrorAppender Maven / Gradle / Ivy
/*
* Copyright 2013 Stackify
*
* 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.stackify.error.log4j12;
import java.net.HttpURLConnection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.spi.LoggingEvent;
import org.apache.log4j.spi.ThrowableInformation;
import com.stackify.api.ApiClient;
import com.stackify.api.EnvironmentDetail;
import com.stackify.api.StackifyError;
import com.stackify.api.common.ApiClients;
import com.stackify.api.common.EnvironmentDetails;
import com.stackify.api.common.error.ErrorGovernor;
import com.stackify.api.common.http.StackifyErrorSender;
import com.stackify.api.common.lang.Throwables;
import com.stackify.api.json.StackifyErrorConverter;
/**
* Log4j 1.2 logger appender for sending exceptions to Stackify.
*
*
* Example (*.properties file):
*
* {@code
* log4j.appender.STACKIFY_ERROR=com.stackify.error.log4j12.StackifyErrorAppender
* log4j.appender.STACKIFY_ERROR.apiKey=YOUR_API_KEY
* log4j.appender.STACKIFY_ERROR.application=YOUR_APPLICATION_NAME
* log4j.appender.STACKIFY_ERROR.environment=YOUR_ENVIRONMENT
* }
*
*
*
* Example (*.xml file):
*
* {@code
*
*
*
*
*
* }
*
*
* @author Eric Martin
*/
public class StackifyErrorAppender extends NonReentrantAppender {
/**
* Logger for the appender
*/
private static final Logger LOGGER = Logger.getLogger(StackifyErrorAppender.class);
/**
* Client side error governor to suppress duplicate errors
*/
private ErrorGovernor errorGovernor = new ErrorGovernor();
/**
* The class responsible for sending the errors to Stackify
*/
private StackifyErrorSender errorSender;
/**
* The API Client
*/
private ApiClient apiClient = null;
/**
* The environment detailassertNull
*/
private EnvironmentDetail environmentDetail = null;
/**
* API URL (Appender configuration parameter)
*/
private String apiUrl = "https://api.stackify.com/Error/V1";
/**
* API Key (Appender configuration parameter)
*/
private String apiKey = null;
/**
* Application name (Appender configuration parameter)
*/
private String application = null;
/**
* Environment (Appender configuration parameter)
*/
private String environment = null;
/**
* JSON Converter (Appender configuration parameter)
*/
private String converter = "com.stackify.api.json.jackson.StackifyErrorJacksonConverter";
/**
* @return the apiUrl
*/
public String getApiUrl() {
return apiUrl;
}
/**
* @param apiUrl the apiUrl to set
*/
public void setApiUrl(String apiUrl) {
this.apiUrl = apiUrl;
}
/**
* @return the apiKey
*/
public String getApiKey() {
return apiKey;
}
/**
* @param apiKey the apiKey to set
*/
public void setApiKey(String apiKey) {
this.apiKey = apiKey;
}
/**
* @return the application
*/
public String getApplication() {
return application;
}
/**
* @param application the application to set
*/
public void setApplication(String application) {
this.application = application;
}
/**
* @return the environment
*/
public String getEnvironment() {
return environment;
}
/**
* @param environment the environment to set
*/
public void setEnvironment(String environment) {
this.environment = environment;
}
/**
* @return the converter
*/
public String getConverter() {
return converter;
}
/**
* @param converter the converter to set
*/
public void setConverter(String converter) {
this.converter = converter;
}
/**
* @return the apiClient
*/
public ApiClient getApiClient() {
return apiClient;
}
/**
* @return the environmentDetail
*/
public EnvironmentDetail getEnvironmentDetail() {
return environmentDetail;
}
/**
* @return the errorSender
*/
public StackifyErrorSender getErrorSender() {
return errorSender;
}
/**
* @see org.apache.log4j.AppenderSkeleton#activateOptions()
*/
@Override
public void activateOptions() {
super.activateOptions();
// check the api key
if ((apiKey == null) || (apiKey.isEmpty())) {
LOGGER.error("API Key not set for the Stackify Error Appender");
return;
}
// build the static API client record
this.apiClient = ApiClients.getApiClient(StackifyErrorAppender.class, "/stackify-error-log4j12.properties");
// build the static environment details record
this.environmentDetail = EnvironmentDetails.getEnvironmentDetail(application, environment);
// load the JSON converter
try {
Class> converterClass = Class.forName(converter);
StackifyErrorConverter converterInstance = (StackifyErrorConverter) converterClass.newInstance();
errorSender = new StackifyErrorSender(converterInstance);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
/**
* @see com.stackify.error.log4j12.NonReentrantAppender#subAppend(org.apache.log4j.spi.LoggingEvent)
*/
@Override
protected void subAppend(final LoggingEvent event) {
// Make sure the API Key is set
if ((apiKey == null) || (apiKey.isEmpty())) {
return;
}
// Get the exception from the event
// If it was a FATAL or ERROR without an exception, create a placeholder for the stack trace
Throwable exception = getThrowable(event);
if (exception == null) {
if ((event.getLevel() == Level.ERROR) || (event.getLevel() == Level.FATAL)) {
exception = new Throwable();
} else {
return;
}
}
try {
// Build the tags for the error event
List tags = new ArrayList();
tags.add("log4j12");
tags.add(event.getLevel().toString().toLowerCase());
tags.add(event.getLoggerName());
// Build the StackifyError POJO from the LoggingEvent
StackifyError.Builder errorBuilder = StackifyError.newBuilder();
errorBuilder.apiClient(apiClient);
errorBuilder.environmentDetail(environmentDetail);
errorBuilder.occurredEpochMillis(new Date(event.getTimeStamp()));
errorBuilder.error(Throwables.toErrorItem(getLogMessage(event), exception));
errorBuilder.tags(tags);
Map customProperties = getCustomProperties(event);
if (!customProperties.isEmpty()) {
errorBuilder.customProperties(customProperties);
}
StackifyError error = errorBuilder.build();
// Send the error to Stackify
if (errorGovernor.errorShouldBeSent(error)) {
int rc = errorSender.send(apiUrl, apiKey, Collections.singletonList(error));
if (rc != HttpURLConnection.HTTP_OK) {
if (LOGGER.isInfoEnabled()) {
LOGGER.info("Stackify Error Service returned HTTP " + Integer.toString(rc));
}
}
}
} catch (Throwable t) {
if (LOGGER.isInfoEnabled()) {
LOGGER.info("Exception posting to Stackify Error Service", t);
}
}
}
/**
* @see org.apache.log4j.Appender#close()
*/
@Override
public void close() {
}
/**
* @see org.apache.log4j.Appender#requiresLayout()
*/
@Override
public boolean requiresLayout() {
return false;
}
/**
* Returns the exception tied to this log event
* @param event The log event
* @return The Throwable or null
*/
private Throwable getThrowable(final LoggingEvent event) {
ThrowableInformation throwableInfo = event.getThrowableInformation();
if (throwableInfo != null) {
Throwable t = throwableInfo.getThrowable();
if (t != null) {
return t;
}
}
Object message = event.getMessage();
if (message != null) {
if (message instanceof Throwable) {
return (Throwable) message;
}
}
return null;
}
/**
* Gets custom properties from the events MDC and MDC
* @param event The logging event
* @return Map assembled from the event's MDC and NDC
*/
private Map getCustomProperties(final LoggingEvent event) {
Map customProperties = new HashMap();
// unload the MDC
Map mdc = event.getProperties();
if (mdc != null) {
Iterator mdcIterator = mdc.entrySet().iterator();
while (mdcIterator.hasNext()) {
Map.Entry entryPair = (Map.Entry) mdcIterator.next();
Object key = entryPair.getKey();
Object value = entryPair.getValue();
customProperties.put(key.toString(), value != null ? value.toString() : null);
}
}
// unload the NDC
String ndc = event.getNDC();
if (ndc != null) {
if (!ndc.isEmpty()) {
customProperties.put("NDC", ndc);
}
}
// return the custom properties
return customProperties;
}
/**
* Returns the log message
* @param event The log event
* @return The log message or null
*/
private String getLogMessage(final LoggingEvent event) {
Object message = event.getMessage();
if (message != null) {
if (message instanceof String) {
return (String) message;
}
}
return null;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy