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 }