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}