View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  package org.apache.logging.log4j.core.appender;
18  
19  import java.util.HashMap;
20  import java.util.Map;
21  import java.util.concurrent.TimeUnit;
22  import java.util.concurrent.locks.Lock;
23  import java.util.concurrent.locks.ReentrantLock;
24  
25  import org.apache.logging.log4j.Level;
26  import org.apache.logging.log4j.Logger;
27  import org.apache.logging.log4j.core.AbstractLifeCycle;
28  import org.apache.logging.log4j.core.LoggerContext;
29  import org.apache.logging.log4j.core.config.ConfigurationException;
30  import org.apache.logging.log4j.message.Message;
31  import org.apache.logging.log4j.status.StatusLogger;
32  
33  /**
34   * Abstract base class used to register managers.
35   * <p>
36   * This class implements {@link AutoCloseable} mostly to allow unit tests to be written safely and succinctly. While
37   * managers do need to allocate resources (usually on construction) and then free these resources, a manager is longer
38   * lived than other auto-closeable objects like streams. None the less, making a manager AutoCloseable forces readers to
39   * be aware of the the pattern: allocate resources on construction and call {@link #close()} at some point.
40   * </p>
41   */
42  public abstract class AbstractManager implements AutoCloseable {
43  
44      /**
45       * Allow subclasses access to the status logger without creating another instance.
46       */
47      protected static final Logger LOGGER = StatusLogger.getLogger();
48  
49      // Need to lock that map instead of using a ConcurrentMap due to stop removing the
50      // manager from the map and closing the stream, requiring the whole stop method to be locked.
51      private static final Map<String, AbstractManager> MAP = new HashMap<>();
52  
53      private static final Lock LOCK = new ReentrantLock();
54  
55      /**
56       * Number of Appenders using this manager.
57       */
58      protected int count;
59  
60      private final String name;
61  
62      private final LoggerContext loggerContext;
63  
64      protected AbstractManager(final LoggerContext loggerContext, final String name) {
65          this.loggerContext = loggerContext;
66          this.name = name;
67          LOGGER.debug("Starting {} {}", this.getClass().getSimpleName(), name);
68      }
69  
70      /**
71       * Called to signify that this Manager is no longer required by an Appender.
72       */
73      @Override
74      public void close() {
75          stop(AbstractLifeCycle.DEFAULT_STOP_TIMEOUT, AbstractLifeCycle.DEFAULT_STOP_TIMEUNIT);
76      }
77  
78      public boolean stop(final long timeout, final TimeUnit timeUnit) {
79          boolean stopped = true;
80          LOCK.lock();
81          try {
82              --count;
83              if (count <= 0) {
84                  MAP.remove(name);
85                  LOGGER.debug("Shutting down {} {}", this.getClass().getSimpleName(), getName());
86                  stopped = releaseSub(timeout, timeUnit);
87                  LOGGER.debug("Shut down {} {}, all resources released: {}", this.getClass().getSimpleName(), getName(), stopped);
88              }
89          } finally {
90              LOCK.unlock();
91          }
92          return stopped;
93      }
94  
95      /**
96       * Retrieves a Manager if it has been previously created or creates a new Manager.
97       * @param name The name of the Manager to retrieve.
98       * @param factory The Factory to use to create the Manager.
99       * @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     /**
130      * Used by Log4j to update the Manager during reconfiguration. This method should be considered private.
131      * Implementations may not be thread safe. This method may be made protected in a future release.
132      * @param data The data to update.
133      */
134     public void updateData(final Object data) {
135         // This default implementation does nothing.
136     }
137 
138     /**
139      * Determines if a Manager with the specified name exists.
140      * @param name The name of the Manager.
141      * @return True if the Manager exists, false otherwise.
142      */
143     public static boolean hasManager(final String name) {
144         LOCK.lock();
145         try {
146             return MAP.containsKey(name);
147         } finally {
148             LOCK.unlock();
149         }
150     }
151 
152     /**
153      * Returns the specified manager, cast to the specified narrow type.
154      * @param narrowClass the type to cast to
155      * @param manager the manager object to return
156      * @param <M> the narrow type
157      * @return the specified manager, cast to the specified narrow type
158      * @throws ConfigurationException if the manager cannot be cast to the specified type, which only happens when
159      *          the configuration has multiple incompatible appenders pointing to the same resource
160      * @since 2.9
161      * @see <a href="https://issues.apache.org/jira/browse/LOG4J2-1908">LOG4J2-1908</a>
162      */
163     protected static <M extends AbstractManager> M narrow(final Class<M> narrowClass, final AbstractManager manager) {
164         if (narrowClass.isAssignableFrom(manager.getClass())) {
165             return (M) manager;
166         }
167         throw new ConfigurationException(
168                 "Configuration has multiple incompatible Appenders pointing to the same resource '" +
169                         manager.getName() + "'");
170     }
171 
172     protected static StatusLogger logger() {
173         return StatusLogger.getLogger();
174     }
175 
176     /**
177      * May be overridden by managers to perform processing while the manager is being released and the
178      * lock is held. A timeout is passed for implementors to use as they see fit.
179      * @param timeout timeout
180      * @param timeUnit timeout time unit
181      * @return true if all resources were closed normally, false otherwise.
182      */
183     protected boolean releaseSub(final long timeout, final TimeUnit timeUnit) {
184         // This default implementation does nothing.
185         return true;
186     }
187 
188     protected int getCount() {
189         return count;
190     }
191 
192     /**
193      * Gets the logger context used to create this instance or null. The logger context is usually set when an appender
194      * creates a manager and that appender is given a Configuration. Not all appenders are given a Configuration by
195      * their factory method or builder.
196      *
197      * @return the logger context used to create this instance or null.
198      */
199     public LoggerContext getLoggerContext() {
200         return loggerContext;
201     }
202 
203     /**
204      * Called to signify that this Manager is no longer required by an Appender.
205      * @deprecated In 2.7, use {@link #close()}.
206      */
207     @Deprecated
208     public void release() {
209         close();
210     }
211 
212     /**
213      * Returns the name of the Manager.
214      * @return The name of the Manager.
215      */
216     public String getName() {
217         return name;
218     }
219 
220     /**
221      * Provide a description of the content format supported by this Manager.  Default implementation returns an empty
222      * (unspecified) Map.
223      *
224      * @return a Map of key/value pairs describing the Manager-specific content format, or an empty Map if no content
225      * format descriptors are specified.
226      */
227     public Map<String, String> getContentFormat() {
228         return new HashMap<>();
229     }
230 
231     protected void log(final Level level, final String message, final Throwable throwable) {
232         final Message m = LOGGER.getMessageFactory().newMessage("{} {} {}: {}",
233                 getClass().getSimpleName(), getName(), message, throwable);
234         LOGGER.log(level, m, throwable);
235     }
236 
237     protected void logDebug(final String message, final Throwable throwable) {
238         log(Level.DEBUG, message, throwable);
239     }
240 
241     protected void logError(final String message, final Throwable throwable) {
242         log(Level.ERROR, message, throwable);
243     }
244 
245     protected void logWarn(final String message, final Throwable throwable) {
246         log(Level.WARN, message, throwable);
247     }
248 
249 }