
cn.taketoday.aop.interceptor.AbstractTraceInterceptor Maven / Gradle / Ivy
/*
* Original Author -> Harry Yang ([email protected]) https://taketoday.cn
* Copyright © TODAY & 2017 - 2022 All Rights Reserved.
*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see [http://www.gnu.org/licenses/]
*/
package cn.taketoday.aop.interceptor;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import java.io.Serializable;
import cn.taketoday.aop.support.AopUtils;
import cn.taketoday.lang.Assert;
import cn.taketoday.logging.Logger;
import cn.taketoday.logging.LoggerFactory;
/**
* Base {@code MethodInterceptor} implementation for tracing.
*
* By default, log messages are written to the log for the interceptor class,
* not the class which is being intercepted. Setting the {@code useDynamicLogger}
* bean property to {@code true} causes all log messages to be written to
* the {@code Logger} for the target class being intercepted.
*
*
Subclasses must implement the {@code invokeUnderTrace} method, which
* is invoked by this class ONLY when a particular invocation SHOULD be traced.
* Subclasses should write to the {@code Logger} instance provided.
*
* @author Rob Harrop
* @author Juergen Hoeller
* @author TODAY
* @see #setUseDynamicLogger
* @see #invokeUnderTrace(MethodInvocation, Logger)
* @since 3.0
*/
@SuppressWarnings("serial")
public abstract class AbstractTraceInterceptor implements MethodInterceptor, Serializable {
protected transient Logger defaultLogger = LoggerFactory.getLogger(getClass());
/**
* Indicates whether or not proxy class names should be hidden when using dynamic loggers.
*
* @see #setUseDynamicLogger
*/
private boolean hideProxyClassNames = false;
/**
* Indicates whether to pass an exception to the logger.
*
* @see #writeToLog(Logger, String, Throwable)
*/
private boolean logExceptionStackTrace = true;
/**
* Set whether to use a dynamic logger or a static logger.
* Default is a static logger for this trace interceptor.
*
Used to determine which {@code Logger} instance should be used to write
* log messages for a particular method invocation: a dynamic one for the
* {@code Class} getting called, or a static one for the {@code Class}
* of the trace interceptor.
*
NOTE: Specify either this property or "loggerName", not both.
*
* @see #getLoggerForInvocation(MethodInvocation)
*/
public void setUseDynamicLogger(boolean useDynamicLogger) {
// Release default logger if it is not being used.
this.defaultLogger = (useDynamicLogger ? null : LoggerFactory.getLogger(getClass()));
}
/**
* Set the name of the logger to use. The name will be passed to the
* underlying logger implementation through Commons Logging, getting
* interpreted as log category according to the logger's configuration.
*
This can be specified to not log into the category of a class
* (whether this interceptor's class or the class getting called)
* but rather into a specific named category.
*
NOTE: Specify either this property or "useDynamicLogger", not both.
*
* @see LoggerFactory#getLogger(String)
* @see java.util.logging.Logger#getLogger(String)
*/
public void setLoggerName(String loggerName) {
this.defaultLogger = LoggerFactory.getLogger(loggerName);
}
/**
* Set to "true" to have {@link #setUseDynamicLogger dynamic loggers} hide
* proxy class names wherever possible. Default is "false".
*/
public void setHideProxyClassNames(boolean hideProxyClassNames) {
this.hideProxyClassNames = hideProxyClassNames;
}
/**
* Set whether to pass an exception to the logger, suggesting inclusion
* of its stack trace into the log. Default is "true"; set this to "false"
* in order to reduce the log output to just the trace message (which may
* include the exception class name and exception message, if applicable).
*/
public void setLogExceptionStackTrace(boolean logExceptionStackTrace) {
this.logExceptionStackTrace = logExceptionStackTrace;
}
/**
* Determines whether or not logging is enabled for the particular {@code MethodInvocation}.
* If not, the method invocation proceeds as normal, otherwise the method invocation is passed
* to the {@code invokeUnderTrace} method for handling.
*
* @see #invokeUnderTrace(MethodInvocation, Logger)
*/
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
Logger logger = getLoggerForInvocation(invocation);
if (isInterceptorEnabled(invocation, logger)) {
return invokeUnderTrace(invocation, logger);
}
else {
return invocation.proceed();
}
}
/**
* Return the appropriate {@code Logger} instance to use for the given
* {@code MethodInvocation}. If the {@code useDynamicLogger} flag
* is set, the {@code Logger} instance will be for the target class of the
* {@code MethodInvocation}, otherwise the {@code Logger} will be the
* default static logger.
*
* @param invocation the {@code MethodInvocation} being traced
* @return the {@code Logger} instance to use
* @see #setUseDynamicLogger
*/
protected Logger getLoggerForInvocation(MethodInvocation invocation) {
if (this.defaultLogger != null) {
return this.defaultLogger;
}
else {
Object target = invocation.getThis();
Assert.state(target != null, "Target must not be null");
return LoggerFactory.getLogger(getClassForLogging(target));
}
}
/**
* Determine the class to use for logging purposes.
*
* @param target the target object to introspect
* @return the target class for the given object
* @see #setHideProxyClassNames
*/
protected Class> getClassForLogging(Object target) {
return (this.hideProxyClassNames ? AopUtils.getTargetClass(target) : target.getClass());
}
/**
* Determine whether the interceptor should kick in, that is,
* whether the {@code invokeUnderTrace} method should be called.
*
Default behavior is to check whether the given {@code Logger}
* instance is enabled. Subclasses can override this to apply the
* interceptor in other cases as well.
*
* @param invocation the {@code MethodInvocation} being traced
* @param logger the {@code Logger} instance to check
* @see #invokeUnderTrace
* @see #isLogEnabled
*/
protected boolean isInterceptorEnabled(MethodInvocation invocation, Logger logger) {
return isLogEnabled(logger);
}
/**
* Determine whether the given {@link Logger} instance is enabled.
*
Default is {@code true} when the "trace" level is enabled.
* Subclasses can override this to change the level under which 'tracing' occurs.
*
* @param logger the {@code Logger} instance to check
*/
protected boolean isLogEnabled(Logger logger) {
return logger.isTraceEnabled();
}
/**
* Write the supplied trace message to the supplied {@code Logger} instance.
*
To be called by {@link #invokeUnderTrace} for enter/exit messages.
*
Delegates to {@link #writeToLog(Logger, String, Throwable)} as the
* ultimate delegate that controls the underlying logger invocation.
*
* @see #writeToLog(Logger, String, Throwable)
*/
protected void writeToLog(Logger logger, String message) {
writeToLog(logger, message, null);
}
/**
* Write the supplied trace message and {@link Throwable} to the
* supplied {@code Logger} instance.
*
To be called by {@link #invokeUnderTrace} for enter/exit outcomes,
* potentially including an exception. Note that an exception's stack trace
* won't get logged when {@link #setLogExceptionStackTrace} is "false".
*
By default messages are written at {@code TRACE} level. Subclasses
* can override this method to control which level the message is written
* at, typically also overriding {@link #isLogEnabled} accordingly.
*
* @see #setLogExceptionStackTrace
* @see #isLogEnabled
*/
protected void writeToLog(Logger logger, String message, Throwable ex) {
if (ex != null && this.logExceptionStackTrace) {
logger.trace(message, ex);
}
else {
logger.trace(message);
}
}
/**
* Subclasses must override this method to perform any tracing around the
* supplied {@code MethodInvocation}. Subclasses are responsible for
* ensuring that the {@code MethodInvocation} actually executes by
* calling {@code MethodInvocation.proceed()}.
*
By default, the passed-in {@code Logger} instance will have log level
* "trace" enabled. Subclasses do not have to check for this again, unless
* they overwrite the {@code isInterceptorEnabled} method to modify
* the default behavior, and may delegate to {@code writeToLog} for actual
* messages to be written.
*
* @param logger the {@code Logger} to write trace messages to
* @return the result of the call to {@code MethodInvocation.proceed()}
* @throws Throwable if the call to {@code MethodInvocation.proceed()}
* encountered any errors
* @see #isLogEnabled
* @see #writeToLog(Logger, String)
* @see #writeToLog(Logger, String, Throwable)
*/
protected abstract Object invokeUnderTrace(MethodInvocation invocation, Logger logger) throws Throwable;
}