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.Objects;
22  import java.util.concurrent.TimeUnit;
23  import java.util.concurrent.locks.Lock;
24  import java.util.concurrent.locks.ReentrantLock;
25  
26  import org.apache.logging.log4j.Level;
27  import org.apache.logging.log4j.Logger;
28  import org.apache.logging.log4j.core.AbstractLifeCycle;
29  import org.apache.logging.log4j.core.LoggerContext;
30  import org.apache.logging.log4j.core.config.ConfigurationException;
31  import org.apache.logging.log4j.message.Message;
32  import org.apache.logging.log4j.status.StatusLogger;
33  
34  /**
35   * Abstract base class used to register managers.
36   * <p>
37   * This class implements {@link AutoCloseable} mostly to allow unit tests to be written safely and succinctly. While
38   * managers do need to allocate resources (usually on construction) and then free these resources, a manager is longer
39   * lived than other auto-closeable objects like streams. None the less, making a manager AutoCloseable forces readers to
40   * be aware of the the pattern: allocate resources on construction and call {@link #close()} at some point.
41   * </p>
42   */
43  public abstract class AbstractManager implements AutoCloseable {
44  
45      /**
46       * Allow subclasses access to the status logger without creating another instance.
47       */
48      protected static final Logger LOGGER = StatusLogger.getLogger();
49  
50      // Need to lock that map instead of using a ConcurrentMap due to stop removing the
51      // manager from the map and closing the stream, requiring the whole stop method to be locked.
52      private static final Map<String, AbstractManager> MAP = new HashMap<>();
53  
54      private static final Lock LOCK = new ReentrantLock();
55  
56      /**
57       * Number of Appenders using this manager.
58       */
59      protected int count;
60  
61      private final String name;
62  
63      private final LoggerContext loggerContext;
64  
65      protected AbstractManager(final LoggerContext loggerContext, final String name) {
66          this.loggerContext = loggerContext;
67          this.name = name;
68          LOGGER.debug("Starting {} {}", this.getClass().getSimpleName(), name);
69      }
70  
71      /**
72       * Called to signify that this Manager is no longer required by an Appender.
73       */
74      @Override
75      public void close() {
76          stop(AbstractLifeCycle.DEFAULT_STOP_TIMEOUT, AbstractLifeCycle.DEFAULT_STOP_TIMEUNIT);
77      }
78  
79      public boolean stop(final long timeout, final TimeUnit timeUnit) {
80          boolean stopped = true;
81          LOCK.lock();
82          try {
83              --count;
84              if (count <= 0) {
85                  MAP.remove(name);
86                  LOGGER.debug("Shutting down {} {}", this.getClass().getSimpleName(), getName());
87                  stopped = releaseSub(timeout, timeUnit);
88                  LOGGER.debug("Shut down {} {}, all resources released: {}", this.getClass().getSimpleName(), getName(), stopped);
89              }
90          } finally {
91              LOCK.unlock();
92          }
93          return stopped;
94      }
95  
96      /**
97       * Retrieves a Manager if it has been previously created or creates a new Manager.
98       * @param name The name of the Manager to retrieve.
99       * @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 }