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 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 public void updateData(final Object data) { 131 // This default implementation does nothing. 132 } 133 134 /** 135 * Determines if a Manager with the specified name exists. 136 * @param name The name of the Manager. 137 * @return True if the Manager exists, false otherwise. 138 */ 139 public static boolean hasManager(final String name) { 140 LOCK.lock(); 141 try { 142 return MAP.containsKey(name); 143 } finally { 144 LOCK.unlock(); 145 } 146 } 147 148 /** 149 * Returns the specified manager, cast to the specified narrow type. 150 * @param narrowClass the type to cast to 151 * @param manager the manager object to return 152 * @param <M> the narrow type 153 * @return the specified manager, cast to the specified narrow type 154 * @throws ConfigurationException if the manager cannot be cast to the specified type, which only happens when 155 * the configuration has multiple incompatible appenders pointing to the same resource 156 * @since 2.9 157 * @see <a href="https://issues.apache.org/jira/browse/LOG4J2-1908">LOG4J2-1908</a> 158 */ 159 protected static <M extends AbstractManager> M narrow(final Class<M> narrowClass, final AbstractManager manager) { 160 if (narrowClass.isAssignableFrom(manager.getClass())) { 161 return (M) manager; 162 } 163 throw new ConfigurationException( 164 "Configuration has multiple incompatible Appenders pointing to the same resource '" + 165 manager.getName() + "'"); 166 } 167 168 protected static StatusLogger logger() { 169 return StatusLogger.getLogger(); 170 } 171 172 /** 173 * May be overridden by managers to perform processing while the manager is being released and the 174 * lock is held. A timeout is passed for implementors to use as they see fit. 175 * @param timeout timeout 176 * @param timeUnit timeout time unit 177 * @return true if all resources were closed normally, false otherwise. 178 */ 179 protected boolean releaseSub(final long timeout, final TimeUnit timeUnit) { 180 // This default implementation does nothing. 181 return true; 182 } 183 184 protected int getCount() { 185 return count; 186 } 187 188 /** 189 * Gets the logger context used to create this instance or null. The logger context is usually set when an appender 190 * creates a manager and that appender is given a Configuration. Not all appenders are given a Configuration by 191 * their factory method or builder. 192 * 193 * @return the logger context used to create this instance or null. 194 */ 195 public LoggerContext getLoggerContext() { 196 return loggerContext; 197 } 198 199 /** 200 * Called to signify that this Manager is no longer required by an Appender. 201 * @deprecated In 2.7, use {@link #close()}. 202 */ 203 @Deprecated 204 public void release() { 205 close(); 206 } 207 208 /** 209 * Returns the name of the Manager. 210 * @return The name of the Manager. 211 */ 212 public String getName() { 213 return name; 214 } 215 216 /** 217 * Provide a description of the content format supported by this Manager. Default implementation returns an empty 218 * (unspecified) Map. 219 * 220 * @return a Map of key/value pairs describing the Manager-specific content format, or an empty Map if no content 221 * format descriptors are specified. 222 */ 223 public Map<String, String> getContentFormat() { 224 return new HashMap<>(); 225 } 226 227 protected void log(final Level level, final String message, final Throwable throwable) { 228 final Message m = LOGGER.getMessageFactory().newMessage("{} {} {}: {}", 229 getClass().getSimpleName(), getName(), message, throwable); 230 LOGGER.log(level, m, throwable); 231 } 232 233 protected void logDebug(final String message, final Throwable throwable) { 234 log(Level.DEBUG, message, throwable); 235 } 236 237 protected void logError(final String message, final Throwable throwable) { 238 log(Level.ERROR, message, throwable); 239 } 240 241 protected void logWarn(final String message, final Throwable throwable) { 242 log(Level.WARN, message, throwable); 243 } 244 245}