/* * Copyright 2015 Red Hat, Inc. and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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 org.jbpm.bpmn2.handler; import java.text.MessageFormat; import java.util.*; import org.kie.api.runtime.process.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This class is a {@link WorkItemHandler} implementation that is meant to wrap * <i>other</i> {@link WorkItemHandler} implementations. * * </p>When an exception is thrown by the wrapped {@link WorkItemHandler} * instance, it's added to a list of {@link WorkItemExceptionInfo} instances * that contain as much information as possible about the exception, the * {@link WorkItem} that caused the exception and the {@link ProcessInstance} id * of the process in which the exception was thrown. * <ul> * <li>See the {@link WorkItemExceptionInfo} class for more information.</li> * <li>The list of {@link WorkItemExceptionInfo} classes is available via the * {@link LoggingTaskHandlerDecorator#getWorkItemExceptionInfoList()} method.</li> * </ul> * * </p>After the exception info has been saved, this class then logs a message * the appropriate information via {@link Logger#warn(String)}. The message * logged is configurable: see * {@link LoggingTaskHandlerDecorator#setLoggedMessageFormat(String)} for more * information. * * </p>This class is thread-safe, although it does not take any responsibility * for the {@link WorkItemHandler} that it wraps. If you are using this with * multiple threads, please make sure the the {@link WorkItemHandler} instance * wrapped is also thread-safe. */ public class LoggingTaskHandlerDecorator extends AbstractExceptionHandlingTaskHandler { private static final Logger logger = LoggerFactory.getLogger(LoggingTaskHandlerDecorator.class); private int loggedExceptionsLimit = 100; private Queue<WorkItemExceptionInfo> exceptionInfoList = new ArrayDeque<WorkItemExceptionInfo>(loggedExceptionsLimit); private String configuredMessage = "{0} thrown when work item {1} ({2}) was {3}ed in process instance {4}."; private List<InputParameter> configuredInputList = new ArrayList<InputParameter>(); private boolean printStackTrace = true; /** * Constructs an {@link LoggingTaskHandlerDecorator} instance that wraps a * created instance of the {@link WorkItemHandler} class given. This * instance will only keep the given number of {@link WorkItemExceptionInfo} * instances instead of the default 100. * * @param originalTaskHandlerClass * @param logLimit */ public LoggingTaskHandlerDecorator(Class<? extends WorkItemHandler> originalTaskHandlerClass, int logLimit) { super(originalTaskHandlerClass); initializeExceptionInfoList(logLimit); } /** * Constructs an {@link LoggingTaskHandlerDecorator} instance that wraps a * created instance of the {@link WorkItemHandler} class given. Only * information about the last 100 exceptions will be held in the list * available from * {@link LoggingTaskHandlerDecorator#getWorkItemExceptionInfoList()}; * * @param originalTaskHandlerClass */ public LoggingTaskHandlerDecorator(Class<? extends WorkItemHandler> originalTaskHandlerClass) { super(originalTaskHandlerClass); } /** * Constructs a {@link LoggingTaskHandlerDecorator} instance that wraps the * given {@link WorkItemHandler} instance. This instance will only keep a * refere * * @param originalTaskHandler */ public LoggingTaskHandlerDecorator(WorkItemHandler originalTaskHandler) { super(originalTaskHandler); } /** * Sets the {@link MessageFormat} string to be used to format the log * messages. If this method is used, it's a good idea to also use the * {@link LoggingTaskHandlerDecorator#setLoggedMessageInput(List)} method. * * </p>The default {@link MessageFormat} string used is one of the * following: * * </p>If the {@link WorkItemHandler} is a {@link ServiceTaskHandler} (that * is used with <code><serviceTask></code> nodes), then the format is: * <ul> * <code>{0}.{1} threw {2} when {3}ing work item {4} in process instance {5}.</code> * </ul> * <ol start="0"> * <li>The name of the interface used for the <serviceTask></li> * <li>The name of the operation used for the <serviceTask></li> * <li>The simple name of the class of the exception thrown</li> * <li>"excut" or "abort" depending on the WorkItemHandler method called</li> * <li>The work item id</li> * <li>The process instance id</li> * </ol> * * </p>For all other {@link WorkItemHandler} implementations, the format is: * <ul> * <code>{0} thrown when work item {1} ({2}) was {3}ed in process instance {4}.</code> * </ul> * where the parameters are the following: * <ol start="0"> * <li>The (simple) class name of the exception</li> * <li>The work item id</li> * <li>The name of the work item</li> * <li>"excut" or "abort" depending on the WorkItemHandler method called</li> * <li>The process instance id</li> * </ol> * * @param logMessageFormat * The format to use for logged messages. */ public synchronized void setLoggedMessageFormat(String logMessageFormat) { this.configuredMessage = logMessageFormat; } /** * Sets the list of parameter types used for the log message format that is set in * {@link LoggingTaskHandlerDecorator#setLoggedMessageFormat(String)}. * * </p>The order of the {@link InputParameter} value in the list corresponds to the {@link MessageFormat} number * used in the String given to {@link LoggingTaskHandlerDecorator#setLoggedMessageFormat(String)}. * * </p>See {@link InputParameter} for more information. * * @param inputParameterList */ public synchronized void setLoggedMessageInput(List<InputParameter> inputParameterList) { this.configuredInputList = inputParameterList; } public synchronized void setLoggedExceptionInfoListSize(int loggedExceptionInfoListSize) { initializeExceptionInfoList(loggedExceptionInfoListSize); } public synchronized void setPrintStackTrace(boolean printStackTrace) { this.printStackTrace = printStackTrace; } private void initializeExceptionInfoList(int listSize) { this.loggedExceptionsLimit = listSize; Queue<WorkItemExceptionInfo> newExceptionInfoList = new ArrayDeque<WorkItemExceptionInfo>(loggedExceptionsLimit + 1); newExceptionInfoList.addAll(exceptionInfoList); this.exceptionInfoList = newExceptionInfoList; } public synchronized List<WorkItemExceptionInfo> getWorkItemExceptionInfoList() { return new ArrayList<WorkItemExceptionInfo>(exceptionInfoList); } @Override public synchronized void handleExecuteException(Throwable cause, WorkItem workItem, WorkItemManager manager) { if (exceptionInfoList.size() == this.loggedExceptionsLimit) { exceptionInfoList.poll(); } exceptionInfoList.add(new WorkItemExceptionInfo(workItem, cause, true)); logMessage(true, workItem, cause); } @Override public synchronized void handleAbortException(Throwable cause, WorkItem workItem, WorkItemManager manager) { if (exceptionInfoList.size() == this.loggedExceptionsLimit) { exceptionInfoList.poll(); } exceptionInfoList.add(new WorkItemExceptionInfo(workItem, cause, false)); logMessage(false, workItem, cause); } private void logMessage(boolean onExecute, WorkItem workItem, Throwable cause) { String handlerMethodStem = "execut"; if (!onExecute) { handlerMethodStem = "abort"; } if( cause instanceof WorkItemHandlerRuntimeException ) { cause = cause.getCause(); } List<String> inputList = new ArrayList<String>(); if (configuredInputList.isEmpty()) { if (workItem.getParameter("Interface") != null) { configuredMessage = "{0}.{1} threw {2} when {3}ing work item {4} in process instance {5}."; inputList.add((String) workItem.getParameter("Interface")); inputList.add((String) workItem.getParameter("Operation")); inputList.add(cause.getClass().getSimpleName()); inputList.add(handlerMethodStem); inputList.add(String.valueOf(workItem.getId())); inputList.add(String.valueOf(workItem.getProcessInstanceId())); } else { // {0} thrown when work item {1} ({2}) was {3}ed in process instance {4}. inputList.add(cause.getClass().getSimpleName()); inputList.add(String.valueOf(workItem.getId())); inputList.add(workItem.getName()); inputList.add(handlerMethodStem); inputList.add(String.valueOf(workItem.getProcessInstanceId())); } } else { for (InputParameter inputType : configuredInputList) { switch (inputType) { case EXCEPTION_CLASS: inputList.add(cause.getClass().getSimpleName()); break; case WORK_ITEM_HANDLER_TYPE: inputList.add(getOriginalTaskHandler().getClass().getSimpleName()); break; case WORK_ITEM_METHOD: inputList.add(onExecute ? "execut" : "abort"); break; case WORK_ITEM_ID: inputList.add(String.valueOf(workItem.getId())); break; case WORK_ITEM_NAME: inputList.add(workItem.getName()); break; case WORK_ITEM_PARAMETERS: StringBuilder parameters = new StringBuilder(); for (String param : workItem.getParameters().keySet()) { parameters.append(param + " : " + workItem.getParameters().get(param) + ", "); } inputList.add(parameters.substring(0, parameters.length() - 2)); break; case PROCESS_INSTANCE_ID: inputList.add(String.valueOf(workItem.getProcessInstanceId())); break; case SERVICE: inputList.add((String) workItem.getParameter("Interface")); break; case OPERATION: inputList.add((String) workItem.getParameter("Operation")); break; } } } String message = MessageFormat.format(configuredMessage, inputList.toArray()); if (printStackTrace) { logger.warn(message, cause); } else { logger.warn(message); } } public class WorkItemExceptionInfo { private final Throwable cause; private final Date timeThrown; private final boolean onExecute; private final long processInstanceId; private final long workItemId; private final String workItemName; private final Map<String, Object> workItemParameters; public WorkItemExceptionInfo(WorkItem workItem, Throwable cause, boolean onExecute) { this.timeThrown = new Date(); this.cause = cause; this.onExecute = onExecute; this.processInstanceId = workItem.getProcessInstanceId(); this.workItemId = workItem.getId(); this.workItemName = workItem.getName(); this.workItemParameters = Collections.unmodifiableMap(workItem.getParameters()); } public Throwable getException() { return cause; } public Date getTimeThrown() { return timeThrown; } public boolean onExecute() { return onExecute; } public long getProcessInstanceId() { return processInstanceId; } public long getWorkItemId() { return workItemId; } public String getWorkItemName() { return workItemName; } public Map<String, Object> getWorkItemParameters() { return workItemParameters; } } /** * Type of input parameter that will be used in the {@link MessageFormat} string set in * {@link LoggingTaskHandlerDecorator#setLoggedMessageFormat(String)}. * * <p>Work items are referred to in the following table, are {@link WorkItem} instances * that were being processed when the exception was thrown. * </p>The following values can be used:<table valign='top'> * <tr> * <td><code>WORK_ITEM_ID</code></td> * <td>The work item id</td> * </tr><tr> * <td><code>WORK_ITEM_NAME</code></td> * <td>The work item name</td> * </tr><tr> * <td><code>WORK_ITEM_METHOD</code></td> * <td>Either "execut" (without an 'e') or "abort" depending what was being done with the work item.</td> * </tr><tr> * <td><code>WORK_ITEM_HANDLER_TYPE</code></td> * <td>The class name of the {@link WorkItemHandler} implementation.</td> * </tr><tr> * <td><code>WORK_ITEM_PARAMETERS</code></td> * <td>A list of the parameters present in the {@link WorkItem}</td> * </tr><tr> * <td><code>SERVICE</code></td> * <td>If the work item was being processed as part of a <serviceTask>, then this is the name of the class or service being called. Null otherwise.</td> * </tr><tr> * <td><code>OPERATION</code></td> * <td>If the work item was being processed as part of a <serviceTask>, then this is the name of the method or service operation being called. Null otherwise.</td> * </tr><tr> * <td><code>PROCESS_INSTANCE_ID</code></td> * <td>The process instance id in which the exception occurred.</td> * </tr><tr> * <td><code>EXCEPTION_CLASS</code></td> * <td>The class of the exception thrown.</td> * </tr> * </table> */ public enum InputParameter { WORK_ITEM_ID, WORK_ITEM_NAME, WORK_ITEM_METHOD, WORK_ITEM_HANDLER_TYPE, WORK_ITEM_PARAMETERS, SERVICE, OPERATION, PROCESS_INSTANCE_ID, EXCEPTION_CLASS; } }