001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache license, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the license for the specific language governing permissions and 015 * limitations under the license. 016 */ 017package org.apache.logging.log4j.core.appender; 018 019import java.util.HashMap; 020import java.util.Map; 021import java.util.Objects; 022import java.util.concurrent.TimeUnit; 023import java.util.concurrent.locks.Lock; 024import java.util.concurrent.locks.ReentrantLock; 025 026import org.apache.logging.log4j.Level; 027import org.apache.logging.log4j.Logger; 028import org.apache.logging.log4j.core.AbstractLifeCycle; 029import org.apache.logging.log4j.core.LoggerContext; 030import org.apache.logging.log4j.core.config.ConfigurationException; 031import org.apache.logging.log4j.message.Message; 032import org.apache.logging.log4j.status.StatusLogger; 033 034/** 035 * Abstract base class used to register managers. 036 * <p> 037 * This class implements {@link AutoCloseable} mostly to allow unit tests to be written safely and succinctly. While 038 * managers do need to allocate resources (usually on construction) and then free these resources, a manager is longer 039 * lived than other auto-closeable objects like streams. None the less, making a manager AutoCloseable forces readers to 040 * be aware of the pattern: allocate resources on construction and call {@link #close()} at some point. 041 * </p> 042 */ 043public abstract class AbstractManager implements AutoCloseable { 044 045 /** 046 * Allow subclasses access to the status logger without creating another instance. 047 */ 048 protected static final Logger LOGGER = StatusLogger.getLogger(); 049 050 // Need to lock that map instead of using a ConcurrentMap due to stop removing the 051 // manager from the map and closing the stream, requiring the whole stop method to be locked. 052 private static final Map<String, AbstractManager> MAP = new HashMap<>(); 053 054 private static final Lock LOCK = new ReentrantLock(); 055 056 /** 057 * Number of Appenders using this manager. 058 */ 059 protected int count; 060 061 private final String name; 062 063 private final LoggerContext loggerContext; 064 065 protected AbstractManager(final LoggerContext loggerContext, final String name) { 066 this.loggerContext = loggerContext; 067 this.name = name; 068 LOGGER.debug("Starting {} {}", this.getClass().getSimpleName(), name); 069 } 070 071 /** 072 * Called to signify that this Manager is no longer required by an Appender. 073 */ 074 @Override 075 public void close() { 076 stop(AbstractLifeCycle.DEFAULT_STOP_TIMEOUT, AbstractLifeCycle.DEFAULT_STOP_TIMEUNIT); 077 } 078 079 public boolean stop(final long timeout, final TimeUnit timeUnit) { 080 boolean stopped = true; 081 LOCK.lock(); 082 try { 083 --count; 084 if (count <= 0) { 085 MAP.remove(name); 086 LOGGER.debug("Shutting down {} {}", this.getClass().getSimpleName(), getName()); 087 stopped = releaseSub(timeout, timeUnit); 088 LOGGER.debug("Shut down {} {}, all resources released: {}", this.getClass().getSimpleName(), getName(), stopped); 089 } 090 } finally { 091 LOCK.unlock(); 092 } 093 return stopped; 094 } 095 096 /** 097 * Retrieves a Manager if it has been previously created or creates a new Manager. 098 * @param name The name of the Manager to retrieve. 099 * @param factory The Factory to use to create the Manager. 100 * @param data An Object that should be passed to the factory when creating the Manager. 101 * @param <M> The Type of the Manager to be created. 102 * @param <T> The type of the Factory data. 103 * @return A Manager with the specified name and type. 104 */ 105 // @SuppressWarnings("resource"): this is a factory method, the resource is allocated and released elsewhere. 106 @SuppressWarnings("resource") 107 public static <M extends AbstractManager, T> M getManager(final String name, final ManagerFactory<M, T> factory, 108 final T data) { 109 LOCK.lock(); 110 try { 111 @SuppressWarnings("unchecked") 112 M manager = (M) MAP.get(name); 113 if (manager == null) { 114 manager = Objects.requireNonNull(factory, "factory").createManager(name, data); 115 if (manager == null) { 116 throw new IllegalStateException("ManagerFactory [" + factory + "] unable to create manager for [" 117 + name + "] with data [" + data + "]"); 118 } 119 MAP.put(name, manager); 120 } else { 121 manager.updateData(data); 122 } 123 manager.count++; 124 return manager; 125 } finally { 126 LOCK.unlock(); 127 } 128 } 129 130 /** 131 * Used by Log4j to update the Manager during reconfiguration. This method should be considered private. 132 * Implementations may not be thread safe. This method may be made protected in a future release. 133 * @param data The data to update. 134 */ 135 public void updateData(final Object data) { 136 // This default implementation does nothing. 137 } 138 139 /** 140 * Determines if a Manager with the specified name exists. 141 * @param name The name of the Manager. 142 * @return True if the Manager exists, false otherwise. 143 */ 144 public static boolean hasManager(final String name) { 145 LOCK.lock(); 146 try { 147 return MAP.containsKey(name); 148 } finally { 149 LOCK.unlock(); 150 } 151 } 152 153 /** 154 * Returns the specified manager, cast to the specified narrow type. 155 * @param narrowClass the type to cast to 156 * @param manager the manager object to return 157 * @param <M> the narrow type 158 * @return the specified manager, cast to the specified narrow type 159 * @throws ConfigurationException if the manager cannot be cast to the specified type, which only happens when 160 * the configuration has multiple incompatible appenders pointing to the same resource 161 * @since 2.9 162 * @see <a href="https://issues.apache.org/jira/browse/LOG4J2-1908">LOG4J2-1908</a> 163 */ 164 protected static <M extends AbstractManager> M narrow(final Class<M> narrowClass, final AbstractManager manager) { 165 if (narrowClass.isAssignableFrom(manager.getClass())) { 166 return (M) manager; 167 } 168 throw new ConfigurationException( 169 "Configuration has multiple incompatible Appenders pointing to the same resource '" + 170 manager.getName() + "'"); 171 } 172 173 protected static StatusLogger logger() { 174 return StatusLogger.getLogger(); 175 } 176 177 /** 178 * May be overridden by managers to perform processing while the manager is being released and the 179 * lock is held. A timeout is passed for implementors to use as they see fit. 180 * @param timeout timeout 181 * @param timeUnit timeout time unit 182 * @return true if all resources were closed normally, false otherwise. 183 */ 184 protected boolean releaseSub(final long timeout, final TimeUnit timeUnit) { 185 // This default implementation does nothing. 186 return true; 187 } 188 189 protected int getCount() { 190 return count; 191 } 192 193 /** 194 * Gets the logger context used to create this instance or null. The logger context is usually set when an appender 195 * creates a manager and that appender is given a Configuration. Not all appenders are given a Configuration by 196 * their factory method or builder. 197 * 198 * @return the logger context used to create this instance or null. 199 */ 200 public LoggerContext getLoggerContext() { 201 return loggerContext; 202 } 203 204 /** 205 * Called to signify that this Manager is no longer required by an Appender. 206 * @deprecated In 2.7, use {@link #close()}. 207 */ 208 @Deprecated 209 public void release() { 210 close(); 211 } 212 213 /** 214 * Returns the name of the Manager. 215 * @return The name of the Manager. 216 */ 217 public String getName() { 218 return name; 219 } 220 221 /** 222 * Provide a description of the content format supported by this Manager. Default implementation returns an empty 223 * (unspecified) Map. 224 * 225 * @return a Map of key/value pairs describing the Manager-specific content format, or an empty Map if no content 226 * format descriptors are specified. 227 */ 228 public Map<String, String> getContentFormat() { 229 return new HashMap<>(); 230 } 231 232 protected void log(final Level level, final String message, final Throwable throwable) { 233 final Message m = LOGGER.getMessageFactory().newMessage("{} {} {}: {}", 234 getClass().getSimpleName(), getName(), message, throwable); 235 LOGGER.log(level, m, throwable); 236 } 237 238 protected void logDebug(final String message, final Throwable throwable) { 239 log(Level.DEBUG, message, throwable); 240 } 241 242 protected void logError(final String message, final Throwable throwable) { 243 log(Level.ERROR, message, throwable); 244 } 245 246 protected void logWarn(final String message, final Throwable throwable) { 247 log(Level.WARN, message, throwable); 248 } 249 250}