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-2008 Sun Microsystems, Inc.
015 * Portions Copyright 2013-2016 ForgeRock AS.
016 */
017package org.opends.server.core;
018
019import static org.opends.messages.ConfigMessages.*;
020
021import java.io.ByteArrayInputStream;
022import java.io.IOException;
023import java.util.ArrayList;
024import java.util.List;
025import java.util.logging.Level;
026import java.util.logging.LogManager;
027
028import org.forgerock.i18n.LocalizableMessage;
029import org.forgerock.i18n.slf4j.LocalizedLogger;
030import org.forgerock.opendj.config.server.ConfigException;
031import org.forgerock.opendj.ldap.ResultCode;
032import org.forgerock.opendj.config.server.ConfigurationAddListener;
033import org.forgerock.opendj.config.server.ConfigurationDeleteListener;
034import org.forgerock.opendj.server.config.server.AccessLogPublisherCfg;
035import org.forgerock.opendj.server.config.server.DebugLogPublisherCfg;
036import org.forgerock.opendj.server.config.server.ErrorLogPublisherCfg;
037import org.forgerock.opendj.server.config.server.HTTPAccessLogPublisherCfg;
038import org.forgerock.opendj.server.config.server.LogPublisherCfg;
039import org.forgerock.opendj.server.config.server.RootCfg;
040import org.opends.messages.Severity;
041import org.opends.server.loggers.AbstractLogger;
042import org.opends.server.loggers.AccessLogger;
043import org.opends.server.loggers.DebugLogger;
044import org.opends.server.loggers.ErrorLogger;
045import org.opends.server.loggers.HTTPAccessLogger;
046import org.forgerock.opendj.config.server.ConfigChangeResult;
047import org.opends.server.types.InitializationException;
048import org.slf4j.bridge.SLF4JBridgeHandler;
049
050/**
051 * This class defines a utility that will be used to manage the set of loggers
052 * used in the Directory Server.  It will perform the logger initialization when
053 * the server is starting, and then will manage any additions, removals, or
054 * modifications of any loggers while the server is running.
055 */
056public class LoggerConfigManager implements ConfigurationAddListener<LogPublisherCfg>,
057                                            ConfigurationDeleteListener<LogPublisherCfg>
058{
059
060  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
061
062  /**
063   * Class to manage java.util.logging to slf4j bridge.
064   * Main purpose of this class is to adapt the j.u.l log level when a debug/error log publisher change is detected.
065   * <p>
066   * @ThreadSafe
067   */
068  private static class JulToSlf4jLogManager
069  {
070    private Level currentJulLogLevel = Level.OFF;
071    private final Object lock = new Object();
072
073    private Level computeJulLogLevel()
074    {
075      if (DebugLogger.getInstance().isEnabled())
076      {
077        return Level.FINEST;
078      }
079
080      for (final Severity severity : Severity.values())
081      {
082        if (ErrorLogger.isEnabledFor("", severity))
083        {
084          return errorLoggerSeverityToJulLevel(severity);
085        }
086      }
087      return Level.OFF;
088    }
089
090    private void adjustJulLevel()
091    {
092      final Level newLevel1 = computeJulLogLevel();
093      if (isMoreDetailedThanCurrentLevel(newLevel1))
094      {
095        synchronized (lock)
096        {
097          final Level newLevel2 = computeJulLogLevel();
098          if (isMoreDetailedThanCurrentLevel(newLevel2))
099          {
100            changeJulLogLevel(newLevel2);
101          }
102        }
103      }
104    }
105
106    private void changeJulLogLevel(final Level newLevel)
107    {
108      try
109      {
110        SLF4JBridgeHandler.removeHandlersForRootLogger();
111        // This is needed to avoid major performance issue. See: http://www.slf4j.org/legacy.html#jul-to-slf4j
112        LogManager.getLogManager().readConfiguration(
113                new ByteArrayInputStream((".level=" + newLevel).getBytes()));
114        SLF4JBridgeHandler.install();
115        currentJulLogLevel = newLevel;
116      }
117      catch (IOException | SecurityException e)
118      {
119        logger.error(ERR_CONFIG_CANNOT_CONFIGURE_JUL_LOGGER.get(e.getMessage()), e);
120        SLF4JBridgeHandler.removeHandlersForRootLogger();
121      }
122    }
123
124    private boolean isMoreDetailedThanCurrentLevel(final Level challenger)
125    {
126      return challenger.intValue() < currentJulLogLevel.intValue();
127    }
128
129    /** Convert OpenDJ error log severity to JUL Severity. */
130    private Level errorLoggerSeverityToJulLevel(Severity severity)
131    {
132      switch (severity)
133      {
134      case DEBUG:
135      case INFORMATION:
136      case NOTICE:
137        return Level.INFO;
138      case WARNING:
139        return Level.WARNING;
140      case ERROR:
141        return Level.SEVERE;
142      default:
143        return Level.OFF;
144      }
145    }
146  }
147
148  private final ServerContext serverContext;
149
150  private final JulToSlf4jLogManager julToSlf4jManager = new JulToSlf4jLogManager();
151
152  /**
153   * Create the logger config manager with the provided
154   * server context.
155   *
156   * @param context
157   *            The server context.
158   */
159  public LoggerConfigManager(final ServerContext context)
160  {
161    this.serverContext = context;
162  }
163
164  /**
165   * Initializes all the log publishers.
166   *
167   * @throws ConfigException
168   *           If an unrecoverable problem arises in the process of
169   *           performing the initialization as a result of the server
170   *           configuration.
171   * @throws InitializationException
172   *           If a problem occurs during initialization that is not
173   *           related to the server configuration.
174   */
175  public void initializeLoggerConfig() throws ConfigException, InitializationException
176  {
177    RootCfg root = serverContext.getRootConfig();
178    root.addLogPublisherAddListener(this);
179    root.addLogPublisherDeleteListener(this);
180
181    List<DebugLogPublisherCfg> debugPublisherCfgs = new ArrayList<>();
182    List<AccessLogPublisherCfg> accessPublisherCfgs = new ArrayList<>();
183    List<HTTPAccessLogPublisherCfg> httpAccessPublisherCfgs = new ArrayList<>();
184    List<ErrorLogPublisherCfg> errorPublisherCfgs = new ArrayList<>();
185
186    for (String name : root.listLogPublishers())
187    {
188      LogPublisherCfg config = root.getLogPublisher(name);
189
190      if(config instanceof DebugLogPublisherCfg)
191      {
192        debugPublisherCfgs.add((DebugLogPublisherCfg)config);
193      }
194      else if(config instanceof AccessLogPublisherCfg)
195      {
196        accessPublisherCfgs.add((AccessLogPublisherCfg)config);
197      }
198      else if (config instanceof HTTPAccessLogPublisherCfg)
199      {
200        httpAccessPublisherCfgs.add((HTTPAccessLogPublisherCfg) config);
201      }
202      else if(config instanceof ErrorLogPublisherCfg)
203      {
204        errorPublisherCfgs.add((ErrorLogPublisherCfg)config);
205      }
206      else
207      {
208        throw new ConfigException(ERR_CONFIG_LOGGER_INVALID_OBJECTCLASS.get(config.dn()));
209      }
210    }
211
212    // See if there are active loggers in all categories.  If not, then log a
213    // message.
214    // Do not output warn message for debug loggers because it is valid to fully
215    // disable all debug loggers.
216    if (accessPublisherCfgs.isEmpty())
217    {
218      logger.warn(WARN_CONFIG_LOGGER_NO_ACTIVE_ACCESS_LOGGERS);
219    }
220    if (errorPublisherCfgs.isEmpty())
221    {
222      logger.warn(WARN_CONFIG_LOGGER_NO_ACTIVE_ERROR_LOGGERS);
223    }
224
225    DebugLogger.getInstance().initializeLogger(debugPublisherCfgs, serverContext);
226    AccessLogger.getInstance().initializeLogger(accessPublisherCfgs, serverContext);
227    HTTPAccessLogger.getInstance().initializeLogger(httpAccessPublisherCfgs, serverContext);
228    ErrorLogger.getInstance().initializeLogger(errorPublisherCfgs, serverContext);
229    julToSlf4jManager.adjustJulLevel();
230  }
231
232  /**
233   * Returns the logger instance corresponding to the provided config. If no
234   * logger corresponds to it, null will be returned and a message will be added
235   * to the provided messages list.
236   *
237   * @param config
238   *          the config for which to return the logger instance
239   * @param messages
240   *          where the error message will be output if no logger correspond to
241   *          the provided config.
242   * @return the logger corresponding to the provided config, null if no logger
243   *         corresponds.
244   */
245  private AbstractLogger getLoggerInstance(LogPublisherCfg config, List<LocalizableMessage> messages)
246  {
247    if (config instanceof DebugLogPublisherCfg)
248    {
249      return DebugLogger.getInstance();
250    }
251    else if (config instanceof AccessLogPublisherCfg)
252    {
253      return AccessLogger.getInstance();
254    }
255    else if (config instanceof HTTPAccessLogPublisherCfg)
256    {
257      return HTTPAccessLogger.getInstance();
258    }
259    else if (config instanceof ErrorLogPublisherCfg)
260    {
261      return ErrorLogger.getInstance();
262    }
263
264    messages.add(ERR_CONFIG_LOGGER_INVALID_OBJECTCLASS.get(config.dn()));
265    return null;
266  }
267
268  @Override
269  public boolean isConfigurationAddAcceptable(LogPublisherCfg config, List<LocalizableMessage> unacceptableReasons)
270  {
271    AbstractLogger instance = getLoggerInstance(config, unacceptableReasons);
272    return instance != null
273        && instance.isConfigurationAddAcceptable(config, unacceptableReasons);
274  }
275
276  @Override
277  public ConfigChangeResult applyConfigurationAdd(LogPublisherCfg config)
278  {
279    final ConfigChangeResult ccr = new ConfigChangeResult();
280    final AbstractLogger instance = getLoggerInstance(config, ccr.getMessages());
281    if (instance != null)
282    {
283      return instance.applyConfigurationAdd(config);
284    }
285
286    ccr.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
287    return ccr;
288  }
289
290  @Override
291  public boolean isConfigurationDeleteAcceptable(LogPublisherCfg config, List<LocalizableMessage> unacceptableReasons)
292  {
293    final AbstractLogger instance = getLoggerInstance(config, unacceptableReasons);
294    return instance != null
295        && instance.isConfigurationDeleteAcceptable(config, unacceptableReasons);
296  }
297
298  @Override
299  public ConfigChangeResult applyConfigurationDelete(LogPublisherCfg config)
300  {
301    final ConfigChangeResult ccr = new ConfigChangeResult();
302    final AbstractLogger instance = getLoggerInstance(config, ccr.getMessages());
303    if (instance != null)
304    {
305      return instance.applyConfigurationDelete(config);
306    }
307    ccr.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
308    return ccr;
309  }
310
311  /**
312   * Update the current java.util.logging.Level.
313   * This level is used to filter logs from third party libraries which use j.u.l to our slf4j logger.
314   */
315  public void adjustJulLevel()
316  {
317    julToSlf4jManager.adjustJulLevel();
318  }
319}