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