/* * Copyright 2012-2015, the original author or authors. * * 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.flipkart.phantom.event.consumer; import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.apache.commons.lang3.time.DateFormatUtils; import org.apache.commons.lang3.time.FastDateFormat; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.trpr.platform.core.impl.event.AbstractEndpointEventConsumerImpl; import org.trpr.platform.core.impl.event.PlatformApplicationEvent; import org.trpr.platform.core.spi.event.EndpointEventConsumer; import org.trpr.platform.model.event.PlatformEvent; import com.flipkart.phantom.event.ServiceProxyEvent; import com.flipkart.phantom.event.ServiceProxyEventProducer; import com.netflix.hystrix.HystrixEventType; /** * ServiceProxyEvent Consumer to log every hystrix command which did not return only SUCCESS as an event. * This can happen for a number of reasons such as: * - Main execution failed, fallback succeeded. Failure can happen because of: * - Command Timeout * - Thread Rejection * - Exception * - Open Circuit * - Main execution failed and fallback also failed. Failure reasons are similar. * This does not log if main execution succeeds. * <p/> * To enable this request logger, declare it in application context with * events to log as subscriptions. * * @author kartikbu * @author amanpreet.singh * @version 1.0 * @created 10/10/13 5:48 PM */ public class RequestLogger extends AbstractEndpointEventConsumerImpl { /** Logger for this class */ private static final Logger LOGGER = LoggerFactory.getLogger(RequestLogger.class); private static FastDateFormat dateFormatter = FastDateFormat.getInstance("dd-MMM-yyyy_HH:mm:ss.SSS"); /** Map of endpoint URIs and their registered consumers*/ private Map<String, EndpointEventConsumer> subscriptionConsumers = new HashMap<String, EndpointEventConsumer>(); /** Implementation of {@link org.trpr.platform.core.impl.event.AbstractEndpointEventConsumerImpl#handlePlatformEvent(org.trpr.platform.model.event.PlatformEvent)} */ @SuppressWarnings("unchecked") @Override protected void handlePlatformEvent(final PlatformEvent platformEvent) { if (platformEvent instanceof ServiceProxyEvent) { ServiceProxyEvent serviceProxyEvent = (ServiceProxyEvent)platformEvent; String subscriptionKey = ServiceProxyEventProducer.EVENT_PUBLISHING_URI + serviceProxyEvent.getEventType(); if (this.subscriptionConsumers.containsKey(subscriptionKey)) { // we pass on this event to the consumer registered to process the event this.subscriptionConsumers.get(subscriptionKey).onApplicationEvent(new PlatformApplicationEvent(platformEvent)); } else { log((ServiceProxyEvent) platformEvent); } } else { LOGGER.warn("Event Not Logged:Non compatible event received. Expecting ServiceProxyEvent Type but received " + platformEvent.getClass()); } } /** * Adds the specified endpoint URI to the subscriptions list of this consumer. Forwards all received matching callbacks to the specified EndpointEventConsumer * @param endpointURI the subscription to add to the list of subscriptions * @param eventConsumer the EndpointEventConsumer to invoke for all events with matching endpoint URIs. */ public void addSubscriptionAndConsumer(String endpointURI, EndpointEventConsumer eventConsumer) { this.subscriptionConsumers.put(endpointURI, eventConsumer); List<String> allSubscriptions = new LinkedList<String>(Arrays.asList(this.getSubscriptions())); allSubscriptions.add(endpointURI); super.setSubscriptions(allSubscriptions.toArray(new String[0])); } /** * Writes a line of log describing the command. Logs the following details: * - Command Name * - Events (comma separated) * - TimeStamp when event occurred * - EventType describing source of event based on origination handler @see {@link com.flipkart.phantom.event.ServiceProxyEvent} * - EventSource Actual Handler responsible for executing the request * <p/> * Also prints Exception which caused this request to fail. * * @param event The event published by {@link com.flipkart.phantom.event.ServiceProxyEventProducer} */ private void log(final ServiceProxyEvent event) { List<HystrixEventType> events = event.getHystrixEventList(); if (events.size() > 0 && !events.contains(HystrixEventType.SUCCESS)) { LOGGER.error( "ClientRequestId=" + event.getRequestId() + " " + "Command=" + event.getCommandName() + " " + (events.size() > 0 ? "Events=" + eventsToString(events) + " " : "") + "SentTime=" + getFormattedTimeStamp(event.getRequestSentTime()) + " " + "ReceivedTime=" + getFormattedTimeStamp(event.getRequestReceiveTime()) + " " + "ExecutionStartTime=" + getFormattedTimeStamp(event.getRequestExecutionStartTime()) + " " + "ExecutionEndTime=" + dateFormatter.format(event.getCreatedDate().getTime()) + " " + "EventType=" + event.getEventType() + " " + "EventSource=" + event.getEventSource() + " " + "TimeTaken=" + event.getExecutionTime() , event.getException() ); } } /** * Helper method to get formatted time stamp */ private String getFormattedTimeStamp(final long requestReceiveTime) { if (requestReceiveTime < 0) { return String.valueOf(requestReceiveTime); } else { return dateFormatter.format(new Date(requestReceiveTime)); } } /** * Helper method to comma-separate the list of events * * @param events The list of events happened during hystrix command execution */ private static String eventsToString(List<HystrixEventType> events) { String joined = ""; boolean first = true; for (HystrixEventType event : events) { if (first) { first = false; } else { joined += ","; } joined += event.name(); } return joined; } }