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}