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