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