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     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 }