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 java.util.List;
030
031import org.forgerock.i18n.LocalizableMessage;
032import org.forgerock.opendj.config.server.ConfigChangeResult;
033import org.opends.server.admin.server.ConfigurationChangeListener;
034import org.opends.server.admin.std.server.DebugTargetCfg;
035
036/** This class encapsulates the trace settings in effect at a given tracing scope. */
037public class TraceSettings implements
038    ConfigurationChangeListener<DebugTargetCfg>
039{
040  /** A TraceSettings object representing a fully disabled trace state. */
041  public static final TraceSettings DISABLED = new TraceSettings(Level.DISABLED);
042
043  private static final String STACK_DUMP_KEYWORD = "stack";
044  private static final String INCLUDE_CAUSE_KEYWORD = "cause";
045  private static final String SUPPRESS_ARG_KEYWORD = "noargs";
046  private static final String SUPPRESS_RETVAL_KEYWORD = "noretval";
047  private static final String ENABLED_KEYWORD = "enabled";
048  private static final String EXCEPTIONS_ONLY_KEYWORD = "exceptionsonly";
049
050  /** Represents the level of trace. */
051  enum Level
052  {
053    /** Log nothing. */
054    DISABLED,
055    /** Log only exceptions. */
056    EXCEPTIONS_ONLY,
057    /** Log everything. */
058    ALL;
059
060    /**
061     * Returns the level corresponding to provided options.
062     *
063     * @param isEnabled
064     *          Indicates if tracer is enabled.
065     * @param isDebugExceptionsOnly
066     *          Indicates if tracer should log only exceptions.
067     * @return the level corresponding to options
068     */
069    static Level getLevel(boolean isEnabled, boolean isDebugExceptionsOnly)
070    {
071      if (isEnabled)
072      {
073        if (isDebugExceptionsOnly)
074        {
075          return Level.EXCEPTIONS_ONLY;
076        }
077        return Level.ALL;
078      }
079      return Level.DISABLED;
080    }
081  }
082
083  /** The level of this setting. */
084  private Level level;
085  /** Indicates if method arguments should be logged. */
086  private boolean noArgs;
087  /** Indicates if method return values should be logged. */
088  private boolean noRetVal;
089  /** The level of stack frames to include. */
090  private int stackDepth;
091  /** Indicates if the cause exception is included in exception messages. */
092  private boolean includeCause;
093
094  /** Construct new trace settings with default values. */
095  public TraceSettings()
096  {
097    this(Level.ALL, false, false, 0, false);
098  }
099
100  /**
101   * Construct new trace settings at provided level.
102   *
103   * @param level
104   *          Level for this settings.
105   */
106  private TraceSettings(Level level)
107  {
108    this(level, false, false, 0, false);
109  }
110
111  /**
112   * Construct new trace settings at the specified level. Optionally turn off
113   * arguments, return value in entry and exit messages, and specifying the
114   * depth of stack traces and whether to include the cause of exceptions.
115   *
116   * @param level
117   *          the level for this setting.
118   * @param noArgs
119   *          whether to include arguments in the log messages.
120   * @param noRetVal
121   *          whether to include return values in the log messages.
122   * @param stackDepth
123   *          the stack depth to display in log messages.
124   * @param includeCause
125   *          whether to include the cause of exceptions.
126   */
127  TraceSettings(Level level, boolean noArgs,
128      boolean noRetVal, int stackDepth, boolean includeCause)
129  {
130    this.level = level;
131    this.noArgs = noArgs;
132    this.noRetVal = noRetVal;
133    this.stackDepth = stackDepth;
134    this.includeCause = includeCause;
135  }
136
137  /**
138   * Construct a new trace settings from the provided configuration.
139   *
140   * @param config
141   *          The debug target configuration that contains the information to
142   *          use to initialize this trace setting.
143   */
144  TraceSettings(DebugTargetCfg config)
145  {
146    this.level = Level.getLevel(config.isEnabled(), config.isDebugExceptionsOnly());
147    this.noArgs = config.isOmitMethodEntryArguments();
148    this.noRetVal = config.isOmitMethodReturnValue();
149    this.stackDepth = config.getThrowableStackFrames();
150    this.includeCause = config.isIncludeThrowableCause();
151
152    config.addChangeListener(this);
153  }
154
155  @Override
156  public boolean isConfigurationChangeAcceptable(DebugTargetCfg config,
157      List<LocalizableMessage> unacceptableReasons)
158  {
159    // This should always be acceptable. We are assuming that the scope for this
160    // trace setting is the same since it is part of the DN.
161    return true;
162  }
163
164  @Override
165  public ConfigChangeResult applyConfigurationChange(DebugTargetCfg config)
166  {
167    final ConfigChangeResult ccr = new ConfigChangeResult();
168    // We can assume that the target scope did not change since it is the
169    // naming attribute. Changing it would result in a modify DN.
170
171    this.level = Level.getLevel(config.isEnabled(), config.isDebugExceptionsOnly());
172    this.noArgs = config.isOmitMethodEntryArguments();
173    this.noRetVal = config.isOmitMethodReturnValue();
174    this.stackDepth = config.getThrowableStackFrames();
175    this.includeCause = config.isIncludeThrowableCause();
176
177    return ccr;
178  }
179
180  /**
181   * Parse trace settings from the string representation.
182   *
183   * @param value
184   *          the trace settings string to be parsed.
185   * @return the trace settings parsed from the string.
186   */
187  protected static TraceSettings parseTraceSettings(String value)
188  {
189    TraceSettings settings = null;
190    if (value != null)
191    {
192      boolean enabled = false;
193      boolean exceptionsOnly = false;
194      boolean noArgs = false;
195      boolean noRetVal = false;
196      int stackDepth = 0;
197      boolean includeCause = false;
198
199      String[] keywords = value.split(",");
200
201      for (String keyword : keywords)
202      {
203        //See if stack dump keyword is included
204        if (keyword.startsWith(STACK_DUMP_KEYWORD))
205        {
206          //See if a stack depth is included
207          if (keyword.length() == STACK_DUMP_KEYWORD.length())
208          {
209            stackDepth = DebugStackTraceFormatter.COMPLETE_STACK;
210          }
211          else
212          {
213            int depthStart = keyword.indexOf("=", STACK_DUMP_KEYWORD.length());
214            if (depthStart == STACK_DUMP_KEYWORD.length())
215            {
216              try
217              {
218                stackDepth = Integer.valueOf(keyword.substring(depthStart + 1));
219              }
220              catch (NumberFormatException nfe)
221              { // TODO: i18n
222                System.err.println("The keyword " + STACK_DUMP_KEYWORD
223                    + " contains an invalid depth value. The complete stack "
224                    + "will be included.");
225              }
226            }
227          }
228        }
229        //See if to include cause in exception messages.
230        else if (INCLUDE_CAUSE_KEYWORD.equals(keyword))
231        {
232          includeCause = true;
233        }
234        // See if to suppress method arguments.
235        else if (SUPPRESS_ARG_KEYWORD.equals(keyword))
236        {
237          noArgs = true;
238        }
239        // See if to suppress return values.
240        else if (SUPPRESS_RETVAL_KEYWORD.equals(keyword))
241        {
242          noRetVal = true;
243        }
244        else if (ENABLED_KEYWORD.equals(keyword))
245        {
246          enabled = true;
247        }
248        else if (EXCEPTIONS_ONLY_KEYWORD.equals(keyword))
249        {
250          exceptionsOnly = true;
251        }
252      }
253      settings =
254          new TraceSettings(Level.getLevel(enabled, exceptionsOnly),
255              noArgs, noRetVal, stackDepth, includeCause);
256    }
257
258    return settings;
259  }
260
261  /**
262   * Get the level of this setting.
263   *
264   * @return the level of this setting.
265   */
266  public Level getLevel()
267  {
268    return level;
269  }
270
271  /**
272   * Get whether method arguments should be logged.
273   *
274   * @return if method arguments should be logged.
275   */
276  public boolean isNoArgs()
277  {
278    return noArgs;
279  }
280
281  /**
282   * Get whether method return values should be logged.
283   *
284   * @return if method return values should be logged.
285   */
286  public boolean isNoRetVal()
287  {
288    return noRetVal;
289  }
290
291  /**
292   * Get the level of stack frames to include.
293   *
294   * @return the level of stack frames to include.
295   */
296  public int getStackDepth()
297  {
298    return stackDepth;
299  }
300
301  /**
302   * Get whether the cause exception is included in exception messages.
303   *
304   * @return if the cause exception is included in exception messages.
305   */
306  public boolean isIncludeCause()
307  {
308    return includeCause;
309  }
310
311  @Override
312  public String toString()
313  {
314    return getClass().getSimpleName() + "("
315        + "level=" + level
316        + ", logMethodArguments=" + !noArgs
317        + ", logMethodReturnValue=" + !noRetVal
318        + ", logCauseException=" + includeCause
319        + ", stackDepth=" + stackDepth
320        + ")";
321  }
322}