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.db;
18  
19  import java.util.ArrayList;
20  
21  import org.apache.logging.log4j.core.LogEvent;
22  import org.apache.logging.log4j.core.appender.AbstractManager;
23  import org.apache.logging.log4j.core.appender.ManagerFactory;
24  
25  /**
26   * Manager that allows database appenders to have their configuration reloaded without losing events.
27   */
28  public abstract class AbstractDatabaseManager extends AbstractManager {
29      private final ArrayList<LogEvent> buffer;
30      private final int bufferSize;
31  
32      private boolean running = false;
33  
34      /**
35       * Instantiates the base manager.
36       *
37       * @param name The manager name, which should include any configuration details that one might want to be able to
38       *             reconfigure at runtime, such as database name, username, (hashed) password, etc.
39       * @param bufferSize The size of the log event buffer.
40       */
41      protected AbstractDatabaseManager(final String name, final int bufferSize) {
42          super(name);
43          this.bufferSize = bufferSize;
44          this.buffer = new ArrayList<LogEvent>(bufferSize + 1);
45      }
46  
47      /**
48       * Implementations should implement this method to perform any proprietary startup operations. This method will
49       * never be called twice on the same instance. It is safe to throw any exceptions from this method. This method
50       * does not necessarily connect to the database, as it is generally unreliable to connect once and use the same
51       * connection for hours.
52       */
53      protected abstract void startupInternal() throws Exception;
54  
55      /**
56       * This method is called within the appender when the appender is started. If it has not already been called, it
57       * calls {@link #startupInternal()} and catches any exceptions it might throw.
58       */
59      public final synchronized void startup() {
60          if (!this.isRunning()) {
61              try {
62                  this.startupInternal();
63                  this.running = true;
64              } catch (final Exception e) {
65                  LOGGER.error("Could not perform database startup operations using logging manager [{}].",
66                          this.getName(), e);
67              }
68          }
69      }
70  
71      /**
72       * Implementations should implement this method to perform any proprietary disconnection / shutdown operations. This
73       * method will never be called twice on the same instance, and it will only be called <em>after</em>
74       * {@link #startupInternal()}. It is safe to throw any exceptions from this method. This method does not
75       * necessarily disconnect from the database for the same reasons outlined in {@link #startupInternal()}.
76       */
77      protected abstract void shutdownInternal() throws Exception;
78  
79      /**
80       * This method is called from the {@link #release()} method when the appender is stopped or the appender's manager
81       * is replaced. If it has not already been called, it calls {@link #shutdownInternal()} and catches any exceptions
82       * it might throw.
83       */
84      public final synchronized void shutdown() {
85          this.flush();
86          if (this.isRunning()) {
87              try {
88                  this.shutdownInternal();
89              } catch (final Exception e) {
90                  LOGGER.warn("Error while performing database shutdown operations using logging manager [{}].",
91                          this.getName(), e);
92              } finally {
93                  this.running = false;
94              }
95          }
96      }
97  
98      /**
99       * Indicates whether the manager is currently connected {@link #startup()} has been called and {@link #shutdown()}
100      * has not been called).
101      *
102      * @return {@code true} if the manager is connected.
103      */
104     public final boolean isRunning() {
105         return this.running;
106     }
107 
108     /**
109      * Connects to the database and starts a transaction (if applicable). With buffering enabled, this is called when
110      * flushing the buffer begins, before the first call to {@link #writeInternal}. With buffering disabled, this is
111      * called immediately before every invocation of {@link #writeInternal}.
112      */
113     protected abstract void connectAndStart();
114 
115     /**
116      * Performs the actual writing of the event in an implementation-specific way. This method is called immediately
117      * from {@link #write(LogEvent)} if buffering is off, or from {@link #flush()} if the buffer has reached its limit.
118      *
119      * @param event The event to write to the database.
120      */
121     protected abstract void writeInternal(LogEvent event);
122 
123     /**
124      * Commits any active transaction (if applicable) and disconnects from the database (returns the connection to the
125      * connection pool). With buffering enabled, this is called when flushing the buffer completes, after the last call
126      * to {@link #writeInternal}. With buffering disabled, this is called immediately after every invocation of
127      * {@link #writeInternal}.
128      */
129     protected abstract void commitAndClose();
130 
131     /**
132      * This method is called automatically when the buffer size reaches its maximum or at the beginning of a call to
133      * {@link #shutdown()}. It can also be called manually to flush events to the database.
134      */
135     public final synchronized void flush() {
136         if (this.isRunning() && this.buffer.size() > 0) {
137             this.connectAndStart();
138             try {
139                 for (final LogEvent event : this.buffer) {
140                     this.writeInternal(event);
141                 }
142             } finally {
143                 this.commitAndClose();
144                 // not sure if this should be done when writing the events failed
145                 this.buffer.clear();
146             }
147         }
148     }
149 
150     /**
151      * This method manages buffering and writing of events.
152      *
153      * @param event The event to write to the database.
154      */
155     public final synchronized void write(final LogEvent event) {
156         if (this.bufferSize > 0) {
157             this.buffer.add(event);
158             if (this.buffer.size() >= this.bufferSize || event.isEndOfBatch()) {
159                 this.flush();
160             }
161         } else {
162             this.connectAndStart();
163             try {
164                 this.writeInternal(event);
165             } finally {
166                 this.commitAndClose();
167             }
168         }
169     }
170 
171     @Override
172     public final void releaseSub() {
173         this.shutdown();
174     }
175 
176     @Override
177     public final String toString() {
178         return this.getName();
179     }
180 
181     /**
182      * Implementations should define their own getManager method and call this method from that to create or get
183      * existing managers.
184      *
185      * @param name The manager name, which should include any configuration details that one might want to be able to
186      *             reconfigure at runtime, such as database name, username, (hashed) password, etc.
187      * @param data The concrete instance of {@link AbstractFactoryData} appropriate for the given manager.
188      * @param factory A factory instance for creating the appropriate manager.
189      * @param <M> The concrete manager type.
190      * @param <T> The concrete {@link AbstractFactoryData} type.
191      * @return a new or existing manager of the specified type and name.
192      */
193     protected static <M extends AbstractDatabaseManager, T extends AbstractFactoryData> M getManager(
194             final String name, final T data, final ManagerFactory<M, T> factory
195     ) {
196         return AbstractManager.getManager(name, factory, data);
197     }
198 
199     /**
200      * Implementations should extend this class for passing data between the getManager method and the manager factory
201      * class.
202      */
203     protected abstract static class AbstractFactoryData {
204         private final int bufferSize;
205 
206         /**
207          * Constructs the base factory data.
208          *
209          * @param bufferSize The size of the buffer.
210          */
211         protected AbstractFactoryData(final int bufferSize) {
212             this.bufferSize = bufferSize;
213         }
214 
215         /**
216          * Gets the buffer size.
217          *
218          * @return the buffer size.
219          */
220         public int getBufferSize() {
221             return bufferSize;
222         }
223     }
224 }