001/*
002 * Copyright 2002-2017 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.aop.interceptor;
018
019import java.io.Serializable;
020
021import org.aopalliance.intercept.MethodInterceptor;
022import org.aopalliance.intercept.MethodInvocation;
023import org.apache.commons.logging.Log;
024import org.apache.commons.logging.LogFactory;
025
026import org.springframework.aop.support.AopUtils;
027
028/**
029 * Base {@code MethodInterceptor} implementation for tracing.
030 *
031 * <p>By default, log messages are written to the log for the interceptor class,
032 * not the class which is being intercepted. Setting the {@code useDynamicLogger}
033 * bean property to {@code true} causes all log messages to be written to
034 * the {@code Log} for the target class being intercepted.
035 *
036 * <p>Subclasses must implement the {@code invokeUnderTrace} method, which
037 * is invoked by this class ONLY when a particular invocation SHOULD be traced.
038 * Subclasses should write to the {@code Log} instance provided.
039 *
040 * @author Rob Harrop
041 * @author Juergen Hoeller
042 * @since 1.2
043 * @see #setUseDynamicLogger
044 * @see #invokeUnderTrace(org.aopalliance.intercept.MethodInvocation, org.apache.commons.logging.Log)
045 */
046@SuppressWarnings("serial")
047public abstract class AbstractTraceInterceptor implements MethodInterceptor, Serializable {
048
049        /**
050         * The default {@code Log} instance used to write trace messages.
051         * This instance is mapped to the implementing {@code Class}.
052         */
053        protected transient Log defaultLogger = LogFactory.getLog(getClass());
054
055        /**
056         * Indicates whether or not proxy class names should be hidden when using dynamic loggers.
057         * @see #setUseDynamicLogger
058         */
059        private boolean hideProxyClassNames = false;
060
061        /**
062         * Indicates whether to pass an exception to the logger.
063         * @see #writeToLog(Log, String, Throwable)
064         */
065        private boolean logExceptionStackTrace = true;
066
067
068        /**
069         * Set whether to use a dynamic logger or a static logger.
070         * Default is a static logger for this trace interceptor.
071         * <p>Used to determine which {@code Log} instance should be used to write
072         * log messages for a particular method invocation: a dynamic one for the
073         * {@code Class} getting called, or a static one for the {@code Class}
074         * of the trace interceptor.
075         * <p><b>NOTE:</b> Specify either this property or "loggerName", not both.
076         * @see #getLoggerForInvocation(org.aopalliance.intercept.MethodInvocation)
077         */
078        public void setUseDynamicLogger(boolean useDynamicLogger) {
079                // Release default logger if it is not being used.
080                this.defaultLogger = (useDynamicLogger ? null : LogFactory.getLog(getClass()));
081        }
082
083        /**
084         * Set the name of the logger to use. The name will be passed to the
085         * underlying logger implementation through Commons Logging, getting
086         * interpreted as log category according to the logger's configuration.
087         * <p>This can be specified to not log into the category of a class
088         * (whether this interceptor's class or the class getting called)
089         * but rather into a specific named category.
090         * <p><b>NOTE:</b> Specify either this property or "useDynamicLogger", not both.
091         * @see org.apache.commons.logging.LogFactory#getLog(String)
092         * @see org.apache.log4j.Logger#getLogger(String)
093         * @see java.util.logging.Logger#getLogger(String)
094         */
095        public void setLoggerName(String loggerName) {
096                this.defaultLogger = LogFactory.getLog(loggerName);
097        }
098
099        /**
100         * Set to "true" to have {@link #setUseDynamicLogger dynamic loggers} hide
101         * proxy class names wherever possible. Default is "false".
102         */
103        public void setHideProxyClassNames(boolean hideProxyClassNames) {
104                this.hideProxyClassNames = hideProxyClassNames;
105        }
106
107        /**
108         * Set whether to pass an exception to the logger, suggesting inclusion
109         * of its stack trace into the log. Default is "true"; set this to "false"
110         * in order to reduce the log output to just the trace message (which may
111         * include the exception class name and exception message, if applicable).
112         * @since 4.3.10
113         */
114        public void setLogExceptionStackTrace(boolean logExceptionStackTrace) {
115                this.logExceptionStackTrace = logExceptionStackTrace;
116        }
117
118
119        /**
120         * Determines whether or not logging is enabled for the particular {@code MethodInvocation}.
121         * If not, the method invocation proceeds as normal, otherwise the method invocation is passed
122         * to the {@code invokeUnderTrace} method for handling.
123         * @see #invokeUnderTrace(org.aopalliance.intercept.MethodInvocation, org.apache.commons.logging.Log)
124         */
125        @Override
126        public Object invoke(MethodInvocation invocation) throws Throwable {
127                Log logger = getLoggerForInvocation(invocation);
128                if (isInterceptorEnabled(invocation, logger)) {
129                        return invokeUnderTrace(invocation, logger);
130                }
131                else {
132                        return invocation.proceed();
133                }
134        }
135
136        /**
137         * Return the appropriate {@code Log} instance to use for the given
138         * {@code MethodInvocation}. If the {@code useDynamicLogger} flag
139         * is set, the {@code Log} instance will be for the target class of the
140         * {@code MethodInvocation}, otherwise the {@code Log} will be the
141         * default static logger.
142         * @param invocation the {@code MethodInvocation} being traced
143         * @return the {@code Log} instance to use
144         * @see #setUseDynamicLogger
145         */
146        protected Log getLoggerForInvocation(MethodInvocation invocation) {
147                if (this.defaultLogger != null) {
148                        return this.defaultLogger;
149                }
150                else {
151                        Object target = invocation.getThis();
152                        return LogFactory.getLog(getClassForLogging(target));
153                }
154        }
155
156        /**
157         * Determine the class to use for logging purposes.
158         * @param target the target object to introspect
159         * @return the target class for the given object
160         * @see #setHideProxyClassNames
161         */
162        protected Class<?> getClassForLogging(Object target) {
163                return (this.hideProxyClassNames ? AopUtils.getTargetClass(target) : target.getClass());
164        }
165
166        /**
167         * Determine whether the interceptor should kick in, that is,
168         * whether the {@code invokeUnderTrace} method should be called.
169         * <p>Default behavior is to check whether the given {@code Log}
170         * instance is enabled. Subclasses can override this to apply the
171         * interceptor in other cases as well.
172         * @param invocation the {@code MethodInvocation} being traced
173         * @param logger the {@code Log} instance to check
174         * @see #invokeUnderTrace
175         * @see #isLogEnabled
176         */
177        protected boolean isInterceptorEnabled(MethodInvocation invocation, Log logger) {
178                return isLogEnabled(logger);
179        }
180
181        /**
182         * Determine whether the given {@link Log} instance is enabled.
183         * <p>Default is {@code true} when the "trace" level is enabled.
184         * Subclasses can override this to change the level under which 'tracing' occurs.
185         * @param logger the {@code Log} instance to check
186         */
187        protected boolean isLogEnabled(Log logger) {
188                return logger.isTraceEnabled();
189        }
190
191        /**
192         * Write the supplied trace message to the supplied {@code Log} instance.
193         * <p>To be called by {@link #invokeUnderTrace} for enter/exit messages.
194         * <p>Delegates to {@link #writeToLog(Log, String, Throwable)} as the
195         * ultimate delegate that controls the underlying logger invocation.
196         * @since 4.3.10
197         * @see #writeToLog(Log, String, Throwable)
198         */
199        protected void writeToLog(Log logger, String message) {
200                writeToLog(logger, message, null);
201        }
202
203        /**
204         * Write the supplied trace message and {@link Throwable} to the
205         * supplied {@code Log} instance.
206         * <p>To be called by {@link #invokeUnderTrace} for enter/exit outcomes,
207         * potentially including an exception. Note that an exception's stack trace
208         * won't get logged when {@link #setLogExceptionStackTrace} is "false".
209         * <p>By default messages are written at {@code TRACE} level. Subclasses
210         * can override this method to control which level the message is written
211         * at, typically also overriding {@link #isLogEnabled} accordingly.
212         * @since 4.3.10
213         * @see #setLogExceptionStackTrace
214         * @see #isLogEnabled
215         */
216        protected void writeToLog(Log logger, String message, Throwable ex) {
217                if (ex != null && this.logExceptionStackTrace) {
218                        logger.trace(message, ex);
219                }
220                else {
221                        logger.trace(message);
222                }
223        }
224
225
226        /**
227         * Subclasses must override this method to perform any tracing around the
228         * supplied {@code MethodInvocation}. Subclasses are responsible for
229         * ensuring that the {@code MethodInvocation} actually executes by
230         * calling {@code MethodInvocation.proceed()}.
231         * <p>By default, the passed-in {@code Log} instance will have log level
232         * "trace" enabled. Subclasses do not have to check for this again, unless
233         * they overwrite the {@code isInterceptorEnabled} method to modify
234         * the default behavior, and may delegate to {@code writeToLog} for actual
235         * messages to be written.
236         * @param logger the {@code Log} to write trace messages to
237         * @return the result of the call to {@code MethodInvocation.proceed()}
238         * @throws Throwable if the call to {@code MethodInvocation.proceed()}
239         * encountered any errors
240         * @see #isLogEnabled
241         * @see #writeToLog(Log, String)
242         * @see #writeToLog(Log, String, Throwable)
243         */
244        protected abstract Object invokeUnderTrace(MethodInvocation invocation, Log logger) throws Throwable;
245
246}