001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions Copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2006-2009 Sun Microsystems, Inc.
015 * Portions Copyright 2014-2015 ForgeRock AS.
016 */
017package org.opends.server.loggers;
018
019import static org.opends.server.loggers.TraceSettings.Level.*;
020
021import java.util.Map;
022
023import org.forgerock.i18n.slf4j.LocalizedLogger;
024
025/**
026 * Class for source-code tracing at the method level.
027 *
028 * One DebugTracer instance exists for each Java class using tracing.
029 * Tracer must be registered with the DebugLogger.
030 *
031 * Logging is always done at a level basis, with debug log messages
032 * exceeding the trace threshold being traced, others being discarded.
033 */
034public class DebugTracer
035{
036  /**
037   *  We have to hardcode this value because we cannot import
038   *  {@code org.opends.server.loggers.slf4j.OpenDJLoggerAdapter.class.getName()}
039   *  to avoid OSGI split package issues.
040   *  @see OPENDJ-2226
041   */
042  private static final String OPENDJ_LOGGER_ADAPTER_CLASS_NAME = "org.opends.server.loggers.slf4j.OpenDJLoggerAdapter";
043
044  /** The class this aspect traces. */
045  private String className;
046
047  /** A class that represents a settings cache entry. */
048  private class PublisherSettings
049  {
050    private final DebugLogPublisher<?> debugPublisher;
051    private final TraceSettings classSettings;
052    private final Map<String, TraceSettings> methodSettings;
053
054    private PublisherSettings(String className, DebugLogPublisher<?> publisher)
055    {
056      debugPublisher = publisher;
057      classSettings = publisher.getClassSettings(className);
058      methodSettings = publisher.getMethodSettings(className);
059    }
060
061    @Override
062    public String toString()
063    {
064      return getClass().getSimpleName() + "("
065          + "className=" + className
066          + ", classSettings=" + classSettings
067          + ", methodSettings=" + methodSettings
068          + ")";
069    }
070  }
071
072  private PublisherSettings[] publisherSettings;
073
074  /**
075   * Construct a new DebugTracer object with cached settings obtained from
076   * the provided array of publishers.
077   *
078   * @param className The class name to use as category for logging.
079   * @param publishers The array of publishers to obtain the settings from.
080   */
081  DebugTracer(String className, DebugLogPublisher<?>[] publishers)
082  {
083    this.className = className;
084    publisherSettings = toPublisherSettings(publishers);
085  }
086
087  /**
088   * Log the provided message.
089   *
090   * @param msg
091   *          message to log.
092   */
093  public void trace(String msg)
094  {
095    traceException(msg, null);
096  }
097
098  /**
099   * Log the provided message and exception.
100   *
101   * @param msg
102   *          the message
103   * @param exception
104   *          the exception caught. May be {@code null}.
105   */
106  public void traceException(String msg, Throwable exception)
107  {
108    StackTraceElement[] stackTrace = null;
109    StackTraceElement[] filteredStackTrace = null;
110    StackTraceElement callerFrame = null;
111    final boolean hasException = exception != null;
112    for (PublisherSettings settings : publisherSettings)
113    {
114      TraceSettings activeSettings = settings.classSettings;
115      Map<String, TraceSettings> methodSettings = settings.methodSettings;
116
117      if (shouldLog(activeSettings, hasException) || methodSettings != null)
118      {
119        if (stackTrace == null)
120        {
121          stackTrace = Thread.currentThread().getStackTrace();
122        }
123        if (callerFrame == null)
124        {
125          callerFrame = getCallerFrame(stackTrace);
126        }
127
128        String signature = callerFrame.getMethodName();
129
130        // Specific method settings still could exist. Try getting
131        // the settings for this method.
132        if (methodSettings != null)
133        {
134          TraceSettings mSettings = methodSettings.get(signature);
135          if (mSettings == null)
136          {
137            // Try looking for an undecorated method name
138            int idx = signature.indexOf('(');
139            if (idx != -1)
140            {
141              mSettings = methodSettings.get(signature.substring(0, idx));
142            }
143          }
144
145          // If this method does have a specific setting
146          // and it is not supposed to be logged, continue.
147          if (!shouldLog(mSettings, hasException))
148          {
149            continue;
150          }
151          activeSettings = mSettings;
152        }
153
154        String sourceLocation = callerFrame.getFileName() + ":" + callerFrame.getLineNumber();
155
156        if (filteredStackTrace == null && activeSettings.getStackDepth() > 0)
157        {
158          StackTraceElement[] trace = hasException ? exception.getStackTrace() : stackTrace;
159          filteredStackTrace = DebugStackTraceFormatter.SMART_FRAME_FILTER.getFilteredStackTrace(trace);
160        }
161
162        if (hasException)
163        {
164          settings.debugPublisher.traceException(activeSettings, signature,
165              sourceLocation, msg, exception, filteredStackTrace);
166        }
167        else
168        {
169          settings.debugPublisher.trace(activeSettings, signature,
170              sourceLocation, msg, filteredStackTrace);
171        }
172      }
173    }
174  }
175
176  /**
177   * Gets the name of the class this tracer traces.
178   *
179   * @return The name of the class this tracer traces.
180   */
181  String getTracedClassName()
182  {
183    return className;
184  }
185
186  /**
187   * Indicates if logging is enabled for at least one category
188   * in a publisher.
189   *
190   * @return {@code true} if logging is enabled, false otherwise.
191   */
192  public boolean enabled()
193  {
194    for (PublisherSettings settings : publisherSettings)
195    {
196      if (shouldLog(settings.classSettings) || settings.methodSettings != null)
197      {
198        return true;
199      }
200    }
201    return false;
202  }
203
204  /**
205   * Update the cached settings of the tracer with the settings from the
206   * provided publishers.
207   *
208   * @param publishers The array of publishers to obtain the settings from.
209   */
210  void updateSettings(DebugLogPublisher<?>[] publishers)
211  {
212    publisherSettings = toPublisherSettings(publishers);
213  }
214
215  private PublisherSettings[] toPublisherSettings(DebugLogPublisher<?>[] publishers)
216  {
217    // Get the settings from all publishers.
218    PublisherSettings[] newSettings = new PublisherSettings[publishers.length];
219    for(int i = 0; i < publishers.length; i++)
220    {
221      newSettings[i] = new PublisherSettings(className, publishers[i]);
222    }
223    return newSettings;
224  }
225
226  /**
227   * Return the caller stack frame.
228   *
229   * @param stackTrace
230   *          The stack trace frames of the caller.
231   * @return the caller stack frame or null if none is found on the stack trace.
232   */
233  private StackTraceElement getCallerFrame(StackTraceElement[] stackTrace)
234  {
235    if (stackTrace != null && stackTrace.length > 0)
236    {
237      // Skip all logging related classes
238      for (StackTraceElement aStackTrace : stackTrace)
239      {
240        if(!isLoggingStackTraceElement(aStackTrace))
241        {
242          return aStackTrace;
243        }
244      }
245    }
246
247    return null;
248  }
249
250  /**
251   * Checks if element belongs to a class responsible for logging
252   * (includes the Thread class that may be used to get the stack trace).
253   *
254   * @param trace
255   *            the trace element to check.
256   * @return {@code true} if element corresponds to logging
257   */
258  static boolean isLoggingStackTraceElement(StackTraceElement trace)
259  {
260    String name = trace.getClassName();
261    return name.startsWith(Thread.class.getName())
262        || name.startsWith(DebugTracer.class.getName())
263        || name.startsWith(OPENDJ_LOGGER_ADAPTER_CLASS_NAME)
264        || name.startsWith(LocalizedLogger.class.getName());
265  }
266
267  /** Indicates if there is something to log. */
268  private boolean shouldLog(TraceSettings settings, boolean hasException)
269  {
270    return settings != null
271        && (settings.getLevel() == ALL
272          || (hasException && settings.getLevel() == EXCEPTIONS_ONLY));
273  }
274
275  /** Indicates if there is something to log. */
276  private boolean shouldLog(TraceSettings settings)
277  {
278    return settings.getLevel() != DISABLED;
279  }
280}