com.netflix.hystrix.HystrixRequestLog Maven / Gradle / Ivy
Show all versions of hystrix-core Show documentation
/**
* Copyright 2012 Netflix, 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.netflix.hystrix;
import com.netflix.hystrix.metric.HystrixRequestEventsStream;
import com.netflix.hystrix.strategy.HystrixPlugins;
import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy;
import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext;
import com.netflix.hystrix.strategy.concurrency.HystrixRequestVariableHolder;
import com.netflix.hystrix.strategy.concurrency.HystrixRequestVariableLifecycle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.LinkedBlockingQueue;
/**
* Log of {@link HystrixCommand} executions and events during the current request.
*/
public class HystrixRequestLog {
private static final Logger logger = LoggerFactory.getLogger(HystrixRequestLog.class);
/**
* RequestLog: Reduce Chance of Memory Leak
* https://github.com/Netflix/Hystrix/issues/53
*
* Upper limit on RequestLog before ignoring further additions and logging warnings.
*
* Intended to help prevent memory leaks when someone isn't aware of the
* HystrixRequestContext lifecycle or enabling/disabling RequestLog.
*/
/* package */static final int MAX_STORAGE = 1000;
private static final HystrixRequestVariableHolder currentRequestLog = new HystrixRequestVariableHolder(new HystrixRequestVariableLifecycle() {
@Override
public HystrixRequestLog initialValue() {
return new HystrixRequestLog();
}
public void shutdown(HystrixRequestLog value) {
//write this value to the Request stream
HystrixRequestEventsStream.getInstance().write(value.getAllExecutedCommands());
}
});
/**
* History of {@link HystrixCommand} executed in this request.
*/
private LinkedBlockingQueue> executedCommands = new LinkedBlockingQueue>(MAX_STORAGE);
/**
* History of {@link HystrixInvokableInfo} executed in this request.
*/
private LinkedBlockingQueue> allExecutedCommands = new LinkedBlockingQueue>(MAX_STORAGE);
// prevent public instantiation
private HystrixRequestLog() {
}
/**
* {@link HystrixRequestLog} for current request as defined by {@link HystrixRequestContext}.
*
* @return {@link HystrixRequestLog}
*/
public static HystrixRequestLog getCurrentRequest(HystrixConcurrencyStrategy concurrencyStrategy) {
return currentRequestLog.get(concurrencyStrategy);
}
/**
* {@link HystrixRequestLog} for current request as defined by {@link HystrixRequestContext}.
*
* NOTE: This uses the default {@link HystrixConcurrencyStrategy} or global override. If an injected strategy is being used by commands you must instead use
* {@link #getCurrentRequest(HystrixConcurrencyStrategy)}.
*
* @return {@link HystrixRequestLog}
*/
public static HystrixRequestLog getCurrentRequest() {
return currentRequestLog.get(HystrixPlugins.getInstance().getConcurrencyStrategy());
}
/**
* Retrieve {@link HystrixCommand} instances that were executed during this {@link HystrixRequestContext}.
*
* @return {@code Collection>}
*/
@Deprecated
public Collection> getExecutedCommands() {
return Collections.unmodifiableCollection(executedCommands);
}
/**
* Retrieve {@link HystrixCommand} instances that were executed during this {@link HystrixRequestContext}.
*
* @return {@code Collection>}
*/
public Collection> getAllExecutedCommands() {
return Collections.unmodifiableCollection(allExecutedCommands);
}
/**
* Add {@link HystrixCommand} instance to the request log.
*
* @param command
* {@code HystrixCommand>}
*/
/* package */void addExecutedCommand(HystrixInvokableInfo> command) {
if (!allExecutedCommands.offer(command)) {
// see RequestLog: Reduce Chance of Memory Leak https://github.com/Netflix/Hystrix/issues/53
logger.warn("RequestLog ignoring command after reaching limit of " + MAX_STORAGE + ". See https://github.com/Netflix/Hystrix/issues/53 for more information.");
}
// TODO remove this when deprecation completed
if (command instanceof HystrixCommand) {
@SuppressWarnings("rawtypes")
HystrixCommand> _c = (HystrixCommand) command;
if (!executedCommands.offer(_c)) {
// see RequestLog: Reduce Chance of Memory Leak https://github.com/Netflix/Hystrix/issues/53
logger.warn("RequestLog ignoring command after reaching limit of " + MAX_STORAGE + ". See https://github.com/Netflix/Hystrix/issues/53 for more information.");
}
}
}
/**
* Formats the log of executed commands into a string usable for logging purposes.
*
* Examples:
*
* - TestCommand[SUCCESS][1ms]
* - TestCommand[SUCCESS][1ms], TestCommand[SUCCESS, RESPONSE_FROM_CACHE][1ms]x4
* - TestCommand[TIMEOUT][1ms]
* - TestCommand[FAILURE][1ms]
* - TestCommand[THREAD_POOL_REJECTED][1ms]
* - TestCommand[THREAD_POOL_REJECTED, FALLBACK_SUCCESS][1ms]
* - TestCommand[EMIT, SUCCESS][1ms]
* - TestCommand[EMITx5, SUCCESS][1ms]
* - TestCommand[EMITx5, FAILURE, FALLBACK_EMITx6, FALLBACK_FAILURE][100ms]
* - TestCommand[FAILURE, FALLBACK_SUCCESS][1ms], TestCommand[FAILURE, FALLBACK_SUCCESS, RESPONSE_FROM_CACHE][1ms]x4
* - GetData[SUCCESS][1ms], PutData[SUCCESS][1ms], GetValues[SUCCESS][1ms], GetValues[SUCCESS, RESPONSE_FROM_CACHE][1ms], TestCommand[FAILURE, FALLBACK_FAILURE][1ms], TestCommand[FAILURE,
* FALLBACK_FAILURE, RESPONSE_FROM_CACHE][1ms]
*
*
* If a command has a multiplier such as x4
, that means this command was executed 4 times with the same events. The time in milliseconds is the sum of the 4 executions.
*
* For example, TestCommand[SUCCESS][15ms]x4
represents TestCommand being executed 4 times and the sum of those 4 executions was 15ms. These 4 each executed the run() method since
* RESPONSE_FROM_CACHE
was not present as an event.
*
* If an EMIT or FALLBACK_EMIT has a multiplier such as x5
, that means a HystrixObservableCommand
was used and it emitted that number of OnNext
s.
*
* For example, TestCommand[EMITx5, FAILURE, FALLBACK_EMITx6, FALLBACK_FAILURE][100ms]
represents TestCommand executing observably, emitted 5 OnNext
s, then an OnError
.
* This command also has an Observable fallback, and it emits 6 OnNext
s, then an OnCompleted
.
*
* @return String request log or "Unknown" if unable to instead of throwing an exception.
*/
public String getExecutedCommandsAsString() {
try {
LinkedHashMap aggregatedCommandsExecuted = new LinkedHashMap();
Map aggregatedCommandExecutionTime = new HashMap();
StringBuilder builder = new StringBuilder();
int estimatedLength = 0;
for (HystrixInvokableInfo> command : allExecutedCommands) {
builder.setLength(0);
builder.append(command.getCommandKey().name());
List events = new ArrayList(command.getExecutionEvents());
if (events.size() > 0) {
Collections.sort(events);
//replicate functionality of Arrays.toString(events.toArray()) to append directly to existing StringBuilder
builder.append("[");
for (HystrixEventType event : events) {
switch (event) {
case EMIT:
int numEmissions = command.getNumberEmissions();
if (numEmissions > 1) {
builder.append(event).append("x").append(numEmissions).append(", ");
} else {
builder.append(event).append(", ");
}
break;
case FALLBACK_EMIT:
int numFallbackEmissions = command.getNumberFallbackEmissions();
if (numFallbackEmissions > 1) {
builder.append(event).append("x").append(numFallbackEmissions).append(", ");
} else {
builder.append(event).append(", ");
}
break;
default:
builder.append(event).append(", ");
}
}
builder.setCharAt(builder.length() - 2, ']');
builder.setLength(builder.length() - 1);
} else {
builder.append("[Executed]");
}
String display = builder.toString();
estimatedLength += display.length() + 12; //add 12 chars to display length for appending totalExecutionTime and count below
Integer counter = aggregatedCommandsExecuted.get(display);
if( counter != null){
aggregatedCommandsExecuted.put(display, counter + 1);
} else {
// add it
aggregatedCommandsExecuted.put(display, 1);
}
int executionTime = command.getExecutionTimeInMilliseconds();
if (executionTime < 0) {
// do this so we don't create negative values or subtract values
executionTime = 0;
}
counter = aggregatedCommandExecutionTime.get(display);
if( counter != null && executionTime > 0){
// add to the existing executionTime (sum of executionTimes for duplicate command displayNames)
aggregatedCommandExecutionTime.put(display, aggregatedCommandExecutionTime.get(display) + executionTime);
} else {
// add it
aggregatedCommandExecutionTime.put(display, executionTime);
}
}
builder.setLength(0);
builder.ensureCapacity(estimatedLength);
for (String displayString : aggregatedCommandsExecuted.keySet()) {
if (builder.length() > 0) {
builder.append(", ");
}
builder.append(displayString);
int totalExecutionTime = aggregatedCommandExecutionTime.get(displayString);
builder.append("[").append(totalExecutionTime).append("ms]");
int count = aggregatedCommandsExecuted.get(displayString);
if (count > 1) {
builder.append("x").append(count);
}
}
return builder.toString();
} catch (Exception e) {
logger.error("Failed to create HystrixRequestLog response header string.", e);
// don't let this cause the entire app to fail so just return "Unknown"
return "Unknown";
}
}
}