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