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