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