/*******************************************************************************
* Copyright (c) 2006-2010 eBay Inc. All Rights Reserved.
* 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
*******************************************************************************/
package org.ebayopensource.turmeric.runtime.common.impl.pipeline;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.ebayopensource.turmeric.runtime.common.errors.ErrorSubcategory;
import org.ebayopensource.turmeric.runtime.common.exceptions.ErrorDataFactory;
import org.ebayopensource.turmeric.runtime.common.exceptions.ServiceException;
import org.ebayopensource.turmeric.runtime.common.impl.internal.monitoring.SystemMetricDefs;
import org.ebayopensource.turmeric.runtime.common.impl.internal.pipeline.BaseMessageContextImpl;
import org.ebayopensource.turmeric.runtime.common.impl.utils.ErrorDataLoggingRegistry;
import org.ebayopensource.turmeric.runtime.common.impl.utils.LogManager;
import org.ebayopensource.turmeric.runtime.common.pipeline.LoggingHandler;
import org.ebayopensource.turmeric.runtime.common.pipeline.LoggingHandlerStage;
import org.ebayopensource.turmeric.runtime.common.pipeline.MessageContext;
import org.ebayopensource.turmeric.runtime.common.service.ServiceId;
import org.ebayopensource.turmeric.runtime.common.types.SOAHeaders;
import org.ebayopensource.turmeric.runtime.errorlibrary.ErrorConstants;
import org.ebayopensource.turmeric.common.v1.types.CommonErrorData;
import org.ebayopensource.turmeric.common.v1.types.ErrorCategory;
import org.ebayopensource.turmeric.common.v1.types.ErrorData;
import org.ebayopensource.turmeric.common.v1.types.ErrorParameter;
/**
* @author ichernyshev
* @author rmurphy
*/
public abstract class BaseLoggingHandler implements LoggingHandler {
private boolean m_isClientSide;
private Logger m_logger;
private PayloadLoggingHelper m_payloadHelper;
private Level m_callEventLogLevel = Level.OFF;
private Level m_inputErrorLogLevel = Level.INFO;
private Level m_appErrorLogLevel = Level.SEVERE;
private Level m_errorLogLevel = Level.SEVERE;
private Level m_warningLogLevel = Level.WARNING;
private boolean m_shouldLogCalls;
private boolean m_logRequestUrl;
private boolean m_logHeaders;
public static final String PAYLOADLOG_ON ="on";
public static final String PAYLOADLOG_OFF ="off";
public static final String PAYLOADLOG_FULL ="full";
public static final String PAYLOADLOG_ERRORONLY ="errorOnly";
public static final String REQUEST_PAYLOAD_LOG_LEVEL="request-payload-log-level";
public static final String RESPONSE_PAYLOAD_LOG_LEVEL="response-payload-log-level";
/**
* Used in client requests to log the metrics for the configured logging handlers
*/
private static final String ENABLE_LOGGING_METRICS_HEADER = SOAHeaders.SYS_PREFIX + "LOGGING-METRICS";
private static ThreadLocal<Long> s_totalTime = new ThreadLocal<Long>() {
@Override
protected Long initialValue() {
return 0L;
}
};
public void init(InitContext ctx)
throws ServiceException
{
ServiceId svcId = ctx.getServiceId();
Map<String,String> options = ctx.getOptions();
m_isClientSide = svcId.isClientSide();
m_logger = LogManager.getInstance(this.getClass());
m_payloadHelper = new PayloadLoggingHelper(svcId, options, m_logger);
String value = options.get("log-level-call-event");
if (value != null) {
try {
m_callEventLogLevel = Level.parse(value);
} catch (Exception e) {
throw new ServiceException(ErrorDataFactory.createErrorData(ErrorConstants.CFG_GENERIC_ERROR,
ErrorConstants.ERRORDOMAIN, new Object[] {"Invalid log-level-call-event: " + value}), e);
}
}
m_shouldLogCalls = (m_callEventLogLevel != Level.OFF);
value = options.get("log-level-errors");
if (value != null) {
try {
m_errorLogLevel = Level.parse(value);
} catch (Exception e) {
throw new ServiceException(ErrorDataFactory.createErrorData(ErrorConstants.CFG_GENERIC_ERROR,
ErrorConstants.ERRORDOMAIN, new Object[] {"Invalid log-level-errors: " + value}), e);
}
}
value = options.get("log-level-input-errors");
if (value != null) {
try {
m_inputErrorLogLevel = Level.parse(value);
} catch (Exception e) {
throw new ServiceException(ErrorDataFactory.createErrorData(ErrorConstants.CFG_GENERIC_ERROR,
ErrorConstants.ERRORDOMAIN, new Object[] {"Invalid log-level-input-errors: " + value}), e);
}
}
value = options.get("log-level-app-errors");
if (value != null) {
try {
m_appErrorLogLevel = Level.parse(value);
/* // Make sure the app error log level is not higher then WARNING
if (m_appErrorLogLevel.equals(Level.SEVERE)) {
m_appErrorLogLevel = Level.WARNING;
}
*/} catch (Exception e) {
throw new ServiceException(
ErrorDataFactory.createErrorData(ErrorConstants.CFG_GENERIC_ERROR,
ErrorConstants.ERRORDOMAIN, new Object[] { "Invalid log-level-app-errors: " + value }), e);
}
}
value = options.get("log-level-warnings");
if (value != null) {
try {
m_warningLogLevel = Level.parse(value);
} catch (Exception e) {
throw new ServiceException(ErrorDataFactory.createErrorData(ErrorConstants.CFG_GENERIC_ERROR,
ErrorConstants.ERRORDOMAIN, new Object[] {"Invalid log-level-warnings: " + value}), e);
}
}
value = options.get("log-request-url");
if (value != null) {
try {
m_logRequestUrl = Boolean.parseBoolean(value);
} catch (Exception e) {
throw new ServiceException(ErrorDataFactory.createErrorData(ErrorConstants.CFG_GENERIC_ERROR,
ErrorConstants.ERRORDOMAIN, new Object[] {"Invalid log-request-url: " + value}), e);
}
}
value = options.get("log-headers");
if (value != null) {
try {
m_logHeaders = Boolean.parseBoolean(value);
} catch (Exception e) {
throw new ServiceException(ErrorDataFactory.createErrorData(ErrorConstants.CFG_GENERIC_ERROR,
ErrorConstants.ERRORDOMAIN, new Object[] {"Invalid log-headers: " + value}), e);
}
}
ctx.setSupportsErrorLogging();
}
protected final Level getAppErrorLogLevel() {
return m_appErrorLogLevel;
}
protected final Logger getLogger() {
return m_logger;
}
public void logProcessingStage(MessageContext ctx, LoggingHandlerStage stage)
throws ServiceException
{
String requestPayloadBeanValue = getRequestPayloadLog(ctx);
String responsePayloadBeanValue = getResponsePayloadLog(ctx);
if (m_shouldLogCalls) {
StringBuilder sb = new StringBuilder();
if (stage == LoggingHandlerStage.REQUEST_STARTED) {
addFullCallInfo(ctx, LoggingHandlerStage.REQUEST_STARTED, ", ", sb);
} else {
addCallInfo(ctx, stage, ", ", sb);
}
logEventInternal(ctx, stage, sb.toString());
}
if (stage == LoggingHandlerStage.REQUEST_STARTED) {
if (!m_isClientSide) {
if (m_logRequestUrl) {
logRequestUrl(ctx, stage);
}
if (m_logHeaders) {
logReqestHeaders(ctx, stage);
}
}
if (!requestPayloadBeanValue.equalsIgnoreCase(PAYLOADLOG_OFF))
{
m_payloadHelper.startRequestRecording(ctx);
}
} else if (stage == LoggingHandlerStage.BEFORE_REQUEST_DISPATCH) {
if (!m_isClientSide) {
if (requestPayloadBeanValue.equalsIgnoreCase(PAYLOADLOG_ON) ||
(requestPayloadBeanValue.equalsIgnoreCase(PAYLOADLOG_ERRORONLY)
&& (ctx.hasErrors() || ctx.hasResponseResidentErrors())))
m_payloadHelper.logRequestPayload(ctx);
}
} else if (stage == LoggingHandlerStage.RESPONSE_STARTED) {
if (!responsePayloadBeanValue.equalsIgnoreCase(PAYLOADLOG_OFF))
{
m_payloadHelper.startResponseRecording(ctx);
}
} else if (stage == LoggingHandlerStage.RESPONSE_COMPLETE) {
if (m_isClientSide) {
if (requestPayloadBeanValue.equalsIgnoreCase(PAYLOADLOG_ON) ||
(requestPayloadBeanValue.equalsIgnoreCase(PAYLOADLOG_ERRORONLY)
&& (ctx.hasErrors() || ctx.hasResponseResidentErrors())))
m_payloadHelper.logRequestPayload(ctx);
if (m_logHeaders) {
logResponseHeaders(ctx, stage);
}
}
if (responsePayloadBeanValue.equalsIgnoreCase(PAYLOADLOG_ON) ||
(responsePayloadBeanValue.equalsIgnoreCase(PAYLOADLOG_ERRORONLY)
&& (ctx.hasErrors() || ctx.hasResponseResidentErrors())))
{
m_payloadHelper.logResponsePayload(ctx);
}
}
}
private void logReqestHeaders(MessageContext ctx, LoggingHandlerStage stage) throws ServiceException {
Map<String, String> requestHeaders = ctx.getRequestMessage().getTransportHeaders();
logHeaders(ctx, stage, requestHeaders, "Request headers: ");
}
private void logResponseHeaders(MessageContext ctx, LoggingHandlerStage stage) throws ServiceException {
Map<String, String> responseHeaders = ctx.getResponseMessage().getTransportHeaders();
logHeaders(ctx, stage, responseHeaders, "Response headers: ");
}
private void logHeaders(MessageContext ctx, LoggingHandlerStage stage,
Map<String, String> inputHeaders, String title) throws ServiceException {
if (null == inputHeaders || inputHeaders.isEmpty()) {
return;
}
StringBuilder sb = new StringBuilder(title);
Iterator<String> iter = inputHeaders.keySet().iterator();
while (iter.hasNext()) {
String name = iter.next();
String value = inputHeaders.get(name);
sb.append(name).append(": ").append(value);
if (iter.hasNext()) {
sb.append(", ");
}
}
logEventInternal(ctx, stage, sb.toString());
}
private static final int URI_LEN_LIMIT = 2048;
private void logRequestUrl(MessageContext ctx, LoggingHandlerStage stage) throws ServiceException {
String uriStr = ctx.getRequestUri() == null ? "none" : ctx.getRequestUri();
String uri = uriStr.substring(0, (uriStr.length() > URI_LEN_LIMIT ? URI_LEN_LIMIT : uriStr.length()));
logEventInternal(ctx, stage, "Request Uri: " + uri);
}
protected void addCallInfo(MessageContext ctx, LoggingHandlerStage stage, String separator, StringBuilder sb) throws ServiceException {
if (sb.length() != 0) {
sb.append(separator);
}
sb.append(m_isClientSide ? "Client" : "Server");
sb.append(separator);
sb.append(stage);
String requestGuid = ctx.getRequestGuid();
if (requestGuid != null) {
sb.append(separator);
sb.append("GUID=");
sb.append(requestGuid);
}
}
protected void addFullCallInfo(MessageContext ctx, LoggingHandlerStage stage, String separator, StringBuilder sb) throws ServiceException {
addCallInfo(ctx, stage, separator, sb);
if (sb.length() != 0) {
sb.append(separator);
}
sb.append("Svc=");
sb.append(ctx.getAdminName());
String version = ctx.getInvokerVersion();
sb.append(separator);
sb.append("Ver=");
sb.append(version != null? version : "");
sb.append(separator);
sb.append("Op=");
sb.append(ctx.getOperationName());
String useCase = ""; // TODO: get the real use case
if (useCase != null && useCase.length() != 0 && !useCase.equals("default")) {
sb.append(separator);
sb.append("Use Case=");
sb.append(useCase);
}
}
public void logResponseResidentError(MessageContext ctx, ErrorData errorData) throws ServiceException {
long startTime = System.nanoTime();
try {
Level logLevel = getErrorLogLevel( errorData );
logErrorInternal(ctx, errorData, logLevel);
} finally {
logMetrics(System.nanoTime() - startTime);
}
}
public void logError(MessageContext ctx, Throwable e) throws ServiceException {
long startTime = System.nanoTime();
try {
Level logLevel = getErrorLogLevel(ctx, e);
logErrorInternal(ctx, e, logLevel);
} finally {
logMetrics(System.nanoTime() - startTime);
}
}
public void logWarning(MessageContext ctx, Throwable e) throws ServiceException {
long startTime = System.nanoTime();
try {
Level logLevel = getWarningLogLevel(ctx, e);
logWarningInternal(ctx, e, logLevel);
} finally {
logMetrics(System.nanoTime() - startTime);
}
}
protected Level getErrorLogLevel( ErrorData errorData ) {
// ErrorSeverity errorSeverity = errorData.getSeverity();
// if ( errorSeverity == null ) throw new NullPointerException();
// return errorSeverity.equals( ErrorSeverity.WARNING ) ? Level.WARNING : Level.SEVERE;
// RRE logging should use at most WARNING level.
if (m_appErrorLogLevel.equals(Level.SEVERE)) {
return Level.WARNING;
}
return m_appErrorLogLevel;
}
protected Level getErrorLogLevel(MessageContext ctx, Throwable e)
throws ServiceException
{
LoggingHandlerUtils handlerUtils = getHandlerUtils();
ErrorSubcategory subcategory = handlerUtils.getErrorSubcategory(ctx, e);
if (subcategory == ErrorSubcategory.INBOUND_DATA ||
subcategory == ErrorSubcategory.INBOUND_META_DATA ||
subcategory == ErrorSubcategory.MARKDOWN)
{
return m_inputErrorLogLevel;
}
ErrorCategory category = handlerUtils.getErrorCategory(ctx, e);
if (category == ErrorCategory.APPLICATION) {
return m_appErrorLogLevel;
}
return m_errorLogLevel;
}
protected Level getWarningLogLevel(MessageContext ctx, Throwable e)
throws ServiceException
{
return m_warningLogLevel;
}
protected void logEventInternal(MessageContext ctx, LoggingHandlerStage stage, String text)
throws ServiceException
{
m_logger.log(m_callEventLogLevel, text);
}
protected void logErrorInternal(MessageContext ctx, ErrorData errorData, Level logLevel)
{
logErrorInternal( ctx, errorData, logLevel, getErrorPrefixText(ctx) );
}
protected void logErrorInternal(MessageContext ctx, ErrorData errorData, Level logLevel, String prefix)
{
if ( errorData instanceof CommonErrorData ) {
logCommonErrorInternal( ctx, (CommonErrorData) errorData, logLevel, prefix );
return;
}
assert ctx != null;
assert errorData != null;
assert logLevel != null;
assert prefix != null;
StringBuffer buf = new StringBuffer();
// Don't add {} to log message, the logger treats {n} as arguments
buf.append( "ErrorData=(" );
buf.append( "id=" ).append( errorData.getErrorId() ).append( "," );
buf.append( "domain=" ).append( errorData.getDomain() ).append( "," );
buf.append( "subdomain=" ).append( errorData.getSubdomain() ).append( "," );
buf.append( "severity=" ).append( errorData.getSeverity() ).append( "," );
buf.append( "category=" ).append( errorData.getCategory() ).append( "," );
String message = getMessageText( errorData );
buf.append( "message=" ).append( message ).append( "," );
List<ErrorParameter> errorParameterList = errorData.getParameter();
for ( ErrorParameter errorParameter : errorParameterList ) {
buf.append( "param=" ).append( errorParameter.getName() ).append( "," );
buf.append( "value=" ).append( errorParameter.getValue() ).append( "," );
}
// Don't add {} to log message, the logger treats {n} as arguments
buf.append( ")" );
m_logger.log(logLevel, prefix + buf );
}
protected void logCommonErrorInternal(MessageContext ctx, CommonErrorData errorData, Level logLevel, String prefix)
{
assert ctx != null;
assert errorData != null;
assert logLevel != null;
assert prefix != null;
StringBuffer buf = new StringBuffer();
// Don't add {} to log message, the logger treats {n} as arguments
buf.append( "ErrorData=(" );
buf.append( "id=" ).append( errorData.getErrorId() ).append( "," );
buf.append( "name=" ).append( errorData.getErrorName() ).append( "," );
buf.append( "organization=" ).append( errorData.getOrganization() ).append( "," );
buf.append( "domain=" ).append( errorData.getDomain() ).append( "," );
buf.append( "subdomain=" ).append( errorData.getSubdomain() ).append( "," );
buf.append( "severity=" ).append( errorData.getSeverity() ).append( "," );
buf.append( "category=" ).append( errorData.getCategory() ).append( "," );
String message = getMessageText( errorData );
buf.append( "message=" ).append( message ).append( "," );
buf.append( "cause=" ).append( errorData.getCause() ).append( "," );
String resolution = getResolutionText( errorData );
buf.append( "resolution=" ).append( resolution ).append( "," );
List<ErrorParameter> errorParameterList = errorData.getParameter();
for ( ErrorParameter errorParameter : errorParameterList ) {
buf.append( "param=" ).append( errorParameter.getName() ).append( "," );
buf.append( "value=" ).append( errorParameter.getValue() ).append( "," );
}
// Don't add {} to log message, the logger treats {n} as arguments
buf.append( ")" );
m_logger.log(logLevel, prefix + buf );
}
private String getMessageText( ErrorData errorData ) {
/**
* Assuming that it exists, log the English-version of the message, not the localized one
*/
String message = ErrorDataLoggingRegistry.getInstance().getEnglishMessage( errorData );
return message == null ? errorData.getMessage() : message;
}
private String getResolutionText( CommonErrorData errorData ) {
/**
* Assuming that it exists, log the English-version of the message, not the localized one
*/
String resolution = ErrorDataLoggingRegistry.getInstance().getEnglishResolution( errorData );
return resolution == null ? errorData.getResolution() : resolution;
}
protected void logErrorInternal(MessageContext ctx, Throwable e, Level logLevel)
throws ServiceException
{
e = unwrapException(e);
String prefix = getErrorPrefixText(ctx);
m_logger.log(logLevel, prefix + e.toString(), e);
}
protected void logWarningInternal(MessageContext ctx, Throwable e, Level logLevel)
throws ServiceException
{
e = unwrapException(e);
String prefix = getErrorPrefixText(ctx);
m_logger.log(logLevel, prefix + e.toString(), e);
}
protected Throwable unwrapException(Throwable e) {
return e;
}
protected String getErrorPrefixText(MessageContext ctx) {
StringBuilder sb = new StringBuilder();
String guid = ctx.getRequestGuid();
if (guid != null) {
sb.append('{');
sb.append(guid);
sb.append("} ");
}
return sb.toString();
}
protected final void logMetrics(MessageContext ctx, LoggingHandlerStage stage,
long duration) {
logMetrics(duration);
if (stage == LoggingHandlerStage.RESPONSE_COMPLETE && isMetricEnabled(ctx)) {
((BaseMessageContextImpl)ctx).updateSvcAndOpMetric(SystemMetricDefs.OP_TIME_LOGGING, s_totalTime.get());
// reset the accumulated total time
s_totalTime.remove();
}
}
private boolean isMetricEnabled(MessageContext ctx) {
String enableStr = null;
try {
enableStr = ctx.getRequestMessage().getTransportHeader(ENABLE_LOGGING_METRICS_HEADER);
} catch (ServiceException e) {
// FIXIT: for now ignoring the exception and returning immediately
}
return enableStr != null;
}
protected final void logMetrics(long duration) {
Long curTotal = s_totalTime.get();
curTotal += duration;
s_totalTime.set(curTotal);
}
protected abstract LoggingHandlerUtils getHandlerUtils();
protected abstract String getRequestPayloadLog(MessageContext ctx);
protected abstract String getResponsePayloadLog(MessageContext ctx);
}