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