All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.springframework.aop.interceptor.CustomizableTraceInterceptor Maven / Gradle / Ivy

There is a newer version: 5.3.34
Show newest version
/*
 * Copyright 2002-2006 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 org.springframework.aop.interceptor;

import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.logging.Log;

import org.springframework.core.Constants;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StopWatch;
import org.springframework.util.StringUtils;

/**
 * MethodInterceptor implementation that allows for highly customizable
 * method-level tracing, using placeholders.
 *
 * 

Trace messages are written on method entry, and if the method invocation succeeds * on method exit. If an invocation results in an exception, then an exception message * is written. The contents of these trace messages is fully customizable and special * placeholders are available to allow you to include runtime information in your log * messages. The placeholders available are: * *

    *
  • $[methodName] - replaced with the name of the method being invoked
  • *
  • $[targetClassName] - replaced with the name of the class that is * the target of the invocation
  • *
  • $[targetClassShortName] - replaced with the short name of the class * that is the target of the invocation
  • *
  • $[returnValue] - replaced with the value returned by the invocation
  • *
  • $[argumentTypes] - replaced with a comma-separated list of the * short class names of the method arguments
  • *
  • $[arguments] - replaced with a comma-separated list of the * String representation of the method arguments
  • *
  • $[exception] - replaced with the String representation * of any Throwable raised during the invocation
  • *
  • $[invocationTime] - replaced with the time, in milliseconds, * taken by the method invocation
  • *
* *

There are restrictions on which placeholders can be used in which messages: * see the individual message properties for details on the valid placeholders. * *

NOTE: This class requires JDK 1.4 or later. It uses the * java.util.regex package for regular expression matching, * which is only available on JDK 1.4+. * * @author Rob Harrop * @since 1.2 * @see #setEnterMessage * @see #setExitMessage * @see #setExceptionMessage * @see SimpleTraceInterceptor */ public class CustomizableTraceInterceptor extends AbstractTraceInterceptor { /** * The $[methodName] placeholder. * Replaced with the name of the method being invoked. */ public static final String PLACEHOLDER_METHOD_NAME = "$[methodName]"; /** * The $[targetClassName] placeholder. * Replaced with the fully-qualifed name of the Class * of the method invocation target. */ public static final String PLACEHOLDER_TARGET_CLASS_NAME = "$[targetClassName]"; /** * The $[targetClassShortName] placeholder. * Replaced with the short name of the Class of the * method invocation target. */ public static final String PLACEHOLDER_TARGET_CLASS_SHORT_NAME = "$[targetClassShortName]"; /** * The $[returnValue] placeholder. * Replaced with the String representation of the value * returned by the method invocation. */ public static final String PLACEHOLDER_RETURN_VALUE = "$[returnValue]"; /** * The $[argumentTypes] placeholder. * Replaced with a comma-separated list of the argument types for the * method invocation. Argument types are written as short class names. */ public static final String PLACEHOLDER_ARGUMENT_TYPES = "$[argumentTypes]"; /** * The $[arguments] placeholder. * Replaced with a comma separated list of the argument values for the * method invocation. Relies on the toString() method of * each argument type. */ public static final String PLACEHOLDER_ARGUMENTS = "$[arguments]"; /** * The $[exception] placeholder. * Replaced with the String representation of any * Throwable raised during method invocation. */ public static final String PLACEHOLDER_EXCEPTION = "$[exception]"; /** * The $[invocationTime] placeholder. * Replaced with the time taken by the invocation (in milliseconds). */ public static final String PLACEHOLDER_INVOCATION_TIME = "$[invocationTime]"; /** * The default message used for writing method entry messages. */ private static final String DEFAULT_ENTER_MESSAGE = "Entering method '" + PLACEHOLDER_METHOD_NAME + "' of class [" + PLACEHOLDER_TARGET_CLASS_NAME + "]"; /** * The default message used for writing method exit messages. */ private static final String DEFAULT_EXIT_MESSAGE = "Exiting method '" + PLACEHOLDER_METHOD_NAME + "' of class [" + PLACEHOLDER_TARGET_CLASS_NAME + "]"; /** * The default method used for writing exception messages. */ private static final String DEFAULT_EXCEPTION_MESSAGE = "Exception thrown in method '" + PLACEHOLDER_METHOD_NAME + "' of class [" + PLACEHOLDER_TARGET_CLASS_NAME + "]"; /** * The Pattern used to match placeholders. */ private static final Pattern PATTERN = Pattern.compile("\\$\\[\\p{Alpha}+\\]"); /** * The Pattern used to escape regex values in class names - * specifically $. */ private static final Pattern ESCAPE_PATTERN = Pattern.compile("\\$"); /** * The Set of allowed placeholders. */ private static final Set ALLOWED_PLACEHOLDERS = new Constants(CustomizableTraceInterceptor.class).getValues("PLACEHOLDER_"); /** * The message for method entry. */ private String enterMessage = DEFAULT_ENTER_MESSAGE; /** * The message for method exit. */ private String exitMessage = DEFAULT_EXIT_MESSAGE; /** * The message for exceptions during method execution. */ private String exceptionMessage = DEFAULT_EXCEPTION_MESSAGE; /** * Ses the template used for method entry log messages. * This template can contain any of the following placeholders: *

    *
  • $[targetClassName]
  • *
  • $[targetClassShortName]
  • *
  • $[argumentTypes]
  • *
  • $[arguments]
  • *
* @throws IllegalArgumentException if the message template is empty * or contains any invalid placeholders */ public void setEnterMessage(String enterMessage) throws IllegalArgumentException { Assert.hasText(enterMessage, "'enterMessage' must not be empty"); checkForInvalidPlaceholders(enterMessage); Assert.doesNotContain(enterMessage, PLACEHOLDER_RETURN_VALUE, "enterMessage cannot contain placeholder [" + PLACEHOLDER_RETURN_VALUE + "]"); Assert.doesNotContain(enterMessage, PLACEHOLDER_EXCEPTION, "enterMessage cannot contain placeholder [" + PLACEHOLDER_EXCEPTION + "]"); Assert.doesNotContain(enterMessage, PLACEHOLDER_INVOCATION_TIME, "enterMessage cannot contain placeholder [" + PLACEHOLDER_INVOCATION_TIME + "]"); this.enterMessage = enterMessage; } /** * Set the template used for method exit log messages. * This template can contain any of the following placeholders: *
    *
  • $[targetClassName]
  • *
  • $[targetClassShortName]
  • *
  • $[argumentTypes]
  • *
  • $[arguments]
  • *
  • $[returnValue]
  • *
  • $[invocationTime]
  • *
* @throws IllegalArgumentException if the message template is empty * or contains any invalid placeholders */ public void setExitMessage(String exitMessage) { Assert.hasText(exitMessage, "'exitMessage' must not be empty"); checkForInvalidPlaceholders(exitMessage); Assert.doesNotContain(exitMessage, PLACEHOLDER_EXCEPTION, "exitMessage cannot contain placeholder [" + PLACEHOLDER_EXCEPTION + "]"); this.exitMessage = exitMessage; } /** * Set the template used for method exception log messages. * This template can contain any of the following placeholders: *
    *
  • $[targetClassName]
  • *
  • $[targetClassShortName]
  • *
  • $[argumentTypes]
  • *
  • $[arguments]
  • *
  • $[exception]
  • *
* @throws IllegalArgumentException if the message template is empty * or contains any invalid placeholders */ public void setExceptionMessage(String exceptionMessage) { Assert.hasText(exceptionMessage, "'exceptionMessage' must not be empty"); checkForInvalidPlaceholders(exceptionMessage); Assert.doesNotContain(exceptionMessage, PLACEHOLDER_RETURN_VALUE, "exceptionMessage cannot contain placeholder [" + PLACEHOLDER_RETURN_VALUE + "]"); Assert.doesNotContain(exceptionMessage, PLACEHOLDER_INVOCATION_TIME, "exceptionMessage cannot contain placeholder [" + PLACEHOLDER_INVOCATION_TIME + "]"); this.exceptionMessage = exceptionMessage; } /** * Writes a log message before the invocation based on the value of enterMessage. * If the invocation succeeds, then a log message is written on exit based on the value * exitMessage. If an exception occurs during invocation, then a message is * written based on the value of exceptionMessage. * @see #setEnterMessage * @see #setExitMessage * @see #setExceptionMessage */ protected Object invokeUnderTrace(MethodInvocation invocation, Log logger) throws Throwable { String name = invocation.getMethod().getDeclaringClass().getName() + "." + invocation.getMethod().getName(); StopWatch stopWatch = new StopWatch(name); Object returnValue = null; boolean exitThroughException = false; try { stopWatch.start(name); writeToLog(logger, replacePlaceholders(this.enterMessage, invocation, null, null, -1)); returnValue = invocation.proceed(); return returnValue; } catch (Throwable ex) { if(stopWatch.isRunning()) { stopWatch.stop(); } exitThroughException = true; writeToLog(logger, replacePlaceholders(this.exceptionMessage, invocation, null, ex, stopWatch.getTotalTimeMillis()), ex); throw ex; } finally { if (!exitThroughException) { if(stopWatch.isRunning()) { stopWatch.stop(); } writeToLog(logger, replacePlaceholders(this.exitMessage, invocation, returnValue, null, stopWatch.getTotalTimeMillis())); } } } /** * Writes the supplied message to the supplied Log instance. * @see #writeToLog(org.apache.commons.logging.Log, String, Throwable) */ protected void writeToLog(Log logger, String message) { writeToLog(logger, message, null); } /** * Writes the supplied message and {@link Throwable} to the * supplied Log instance. By default messages are written * at TRACE level. Sub-classes can override this method * to control which level the message is written at. */ protected void writeToLog(Log logger, String message, Throwable ex) { if (ex != null) { logger.trace(message, ex); } else { logger.trace(message); } } /** * Replace the placeholders in the given message with the supplied values, * or values derived from those supplied. * @param message the message template containing the placeholders to be replaced * @param methodInvocation the MethodInvocation being logged. * Used to derive values for all placeholders except $[exception] * and $[returnValue]. * @param returnValue any value returned by the invocation. * Used to replace the $[returnValue] placeholder. May be null. * @param throwable any Throwable raised during the invocation. * The value of Throwable.toString() is replaced for the * $[exception] placeholder. May be null. * @param invocationTime the value to write in place of the * $[invocationTime] placeholder * @return the formatted output to write to the log */ protected String replacePlaceholders(String message, MethodInvocation methodInvocation, Object returnValue, Throwable throwable, long invocationTime) { Matcher matcher = PATTERN.matcher(message); StringBuffer output = new StringBuffer(); while (matcher.find()) { String match = matcher.group(); if (PLACEHOLDER_METHOD_NAME.equals(match)) { matcher.appendReplacement(output, methodInvocation.getMethod().getName()); } else if (PLACEHOLDER_TARGET_CLASS_NAME.equals(match)) { String targetClassName = escape(methodInvocation.getThis().getClass().getName()); matcher.appendReplacement(output, targetClassName); } else if (PLACEHOLDER_TARGET_CLASS_SHORT_NAME.equals(match)) { matcher.appendReplacement(output, escape(ClassUtils.getShortName(methodInvocation.getThis().getClass()))); } else if (PLACEHOLDER_ARGUMENTS.equals(match)) { matcher.appendReplacement(output, escape(StringUtils.arrayToCommaDelimitedString(methodInvocation.getArguments()))); } else if (PLACEHOLDER_ARGUMENT_TYPES.equals(match)) { appendArgumentTypes(methodInvocation, matcher, output); } else if (PLACEHOLDER_RETURN_VALUE.equals(match)) { appendReturnValue(methodInvocation, matcher, output, returnValue); } else if (throwable != null && PLACEHOLDER_EXCEPTION.equals(match)) { matcher.appendReplacement(output, throwable.toString()); } else if (PLACEHOLDER_INVOCATION_TIME.equals(match)) { matcher.appendReplacement(output, Long.toString(invocationTime)); } else { // Should not happen since placeholders are checked earlier. throw new IllegalArgumentException("Unknown placeholder [" + match + "]"); } } matcher.appendTail(output); return output.toString(); } /** * Adds the String representation of the method return value * to the supplied StringBuffer. Correctly handles * null and void results. * @param methodInvocation the MethodInvocation that returned the value * @param matcher the Matcher containing the matched placeholder * @param output the StringBuffer to write output to * @param returnValue the value returned by the method invocatio. */ private void appendReturnValue( MethodInvocation methodInvocation, Matcher matcher, StringBuffer output, Object returnValue) { if (methodInvocation.getMethod().getReturnType() == void.class) { matcher.appendReplacement(output, "void"); } else if (returnValue == null) { matcher.appendReplacement(output, "null"); } else { matcher.appendReplacement(output, escape(returnValue.toString())); } } /** * Adds a comma-separated list of the short Class names of the * method argument types to the output. For example, if a method has signature * put(java.lang.String, java.lang.Object) then the value returned * will be String, Object. * @param methodInvocation the MethodInvocation being logged. * Arguments will be retreived from the corresponding Method. * @param matcher the Matcher containing the state of the output * @param output the StringBuffer containing the output */ private void appendArgumentTypes(MethodInvocation methodInvocation, Matcher matcher, StringBuffer output) { Class[] argumentTypes = methodInvocation.getMethod().getParameterTypes(); String[] argumentTypeShortNames = new String[argumentTypes.length]; for (int i = 0; i < argumentTypeShortNames.length; i++) { argumentTypeShortNames[i] = ClassUtils.getShortName(argumentTypes[i]); } matcher.appendReplacement(output, escape(StringUtils.arrayToCommaDelimitedString(argumentTypeShortNames))); } /** * Checks to see if the supplied String has any placeholders * that are not specified as constants on this class and throws an * IllegalArgumentException if so. */ private void checkForInvalidPlaceholders(String message) throws IllegalArgumentException { Matcher matcher = PATTERN.matcher(message); while (matcher.find()) { String match = matcher.group(); if (!ALLOWED_PLACEHOLDERS.contains(match)) { throw new IllegalArgumentException("Placeholder [" + match + "] is not valid"); } } } /** * Replaces $ in inner class names with \$. */ private String escape(String input) { Matcher matcher = ESCAPE_PATTERN.matcher(input); StringBuffer output = new StringBuffer(input.length()); while (matcher.find()) { matcher.appendReplacement(output, ""); output.append("\\").append(matcher.group()); } matcher.appendTail(output); return output.toString(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy