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