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