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