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 /** 168 * May be overridden by managers to perform processing while the manager is being released and the 169 * lock is held. A timeout is passed for implementors to use as they see fit. 170 * @param timeout timeout 171 * @param timeUnit timeout time unit 172 * @return true if all resources were closed normally, false otherwise. 173 */ 174 protected boolean releaseSub(final long timeout, final TimeUnit timeUnit) { 175 // This default implementation does nothing. 176 return true; 177 } 178 179 protected int getCount() { 180 return count; 181 } 182 183 /** 184 * Gets the logger context used to create this instance or null. The logger context is usually set when an appender 185 * creates a manager and that appender is given a Configuration. Not all appenders are given a Configuration by 186 * their factory method or builder. 187 * 188 * @return the logger context used to create this instance or null. 189 */ 190 public LoggerContext getLoggerContext() { 191 return loggerContext; 192 } 193 194 /** 195 * Called to signify that this Manager is no longer required by an Appender. 196 * @deprecated In 2.7, use {@link #close()}. 197 */ 198 @Deprecated 199 public void release() { 200 close(); 201 } 202 203 /** 204 * Returns the name of the Manager. 205 * @return The name of the Manager. 206 */ 207 public String getName() { 208 return name; 209 } 210 211 /** 212 * Provide a description of the content format supported by this Manager. Default implementation returns an empty 213 * (unspecified) Map. 214 * 215 * @return a Map of key/value pairs describing the Manager-specific content format, or an empty Map if no content 216 * format descriptors are specified. 217 */ 218 public Map<String, String> getContentFormat() { 219 return new HashMap<>(); 220 } 221 222 protected void log(final Level level, final String message, final Throwable throwable) { 223 final Message m = LOGGER.getMessageFactory().newMessage("{} {} {}: {}", 224 getClass().getSimpleName(), getName(), message, throwable); 225 LOGGER.log(level, m, throwable); 226 } 227 228 protected void logDebug(final String message, final Throwable throwable) { 229 log(Level.DEBUG, message, throwable); 230 } 231 232 protected void logError(final String message, final Throwable throwable) { 233 log(Level.ERROR, message, throwable); 234 } 235 236 protected void logWarn(final String message, final Throwable throwable) { 237 log(Level.WARN, message, throwable); 238 } 239 240}