001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache license, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the license for the specific language governing permissions and 015 * limitations under the license. 016 */ 017 package org.apache.logging.log4j.core.appender.db; 018 019 import java.util.ArrayList; 020 021 import org.apache.logging.log4j.core.LogEvent; 022 import org.apache.logging.log4j.core.appender.AbstractManager; 023 import org.apache.logging.log4j.core.appender.ManagerFactory; 024 025 /** 026 * Manager that allows database appenders to have their configuration reloaded without losing events. 027 */ 028 public abstract class AbstractDatabaseManager extends AbstractManager { 029 private final ArrayList<LogEvent> buffer; 030 private final int bufferSize; 031 032 private boolean running = false; 033 034 /** 035 * Instantiates the base manager. 036 * 037 * @param name The manager name, which should include any configuration details that one might want to be able to 038 * reconfigure at runtime, such as database name, username, (hashed) password, etc. 039 * @param bufferSize The size of the log event buffer. 040 */ 041 protected AbstractDatabaseManager(final String name, final int bufferSize) { 042 super(name); 043 this.bufferSize = bufferSize; 044 this.buffer = new ArrayList<LogEvent>(bufferSize + 1); 045 } 046 047 /** 048 * Implementations should implement this method to perform any proprietary startup operations. This method will 049 * never be called twice on the same instance. It is safe to throw any exceptions from this method. This method 050 * does not necessarily connect to the database, as it is generally unreliable to connect once and use the same 051 * connection for hours. 052 */ 053 protected abstract void startupInternal() throws Exception; 054 055 /** 056 * This method is called within the appender when the appender is started. If it has not already been called, it 057 * calls {@link #startupInternal()} and catches any exceptions it might throw. 058 */ 059 public final synchronized void startup() { 060 if (!this.isRunning()) { 061 try { 062 this.startupInternal(); 063 this.running = true; 064 } catch (final Exception e) { 065 LOGGER.error("Could not perform database startup operations using logging manager [{}].", 066 this.getName(), e); 067 } 068 } 069 } 070 071 /** 072 * Implementations should implement this method to perform any proprietary disconnection / shutdown operations. This 073 * method will never be called twice on the same instance, and it will only be called <em>after</em> 074 * {@link #startupInternal()}. It is safe to throw any exceptions from this method. This method does not 075 * necessarily disconnect from the database for the same reasons outlined in {@link #startupInternal()}. 076 */ 077 protected abstract void shutdownInternal() throws Exception; 078 079 /** 080 * This method is called from the {@link #release()} method when the appender is stopped or the appender's manager 081 * is replaced. If it has not already been called, it calls {@link #shutdownInternal()} and catches any exceptions 082 * it might throw. 083 */ 084 public final synchronized void shutdown() { 085 this.flush(); 086 if (this.isRunning()) { 087 try { 088 this.shutdownInternal(); 089 } catch (final Exception e) { 090 LOGGER.warn("Error while performing database shutdown operations using logging manager [{}].", 091 this.getName(), e); 092 } finally { 093 this.running = false; 094 } 095 } 096 } 097 098 /** 099 * Indicates whether the manager is currently connected {@link #startup()} has been called and {@link #shutdown()} 100 * has not been called). 101 * 102 * @return {@code true} if the manager is connected. 103 */ 104 public final boolean isRunning() { 105 return this.running; 106 } 107 108 /** 109 * Connects to the database and starts a transaction (if applicable). With buffering enabled, this is called when 110 * flushing the buffer begins, before the first call to {@link #writeInternal}. With buffering disabled, this is 111 * called immediately before every invocation of {@link #writeInternal}. 112 */ 113 protected abstract void connectAndStart(); 114 115 /** 116 * Performs the actual writing of the event in an implementation-specific way. This method is called immediately 117 * from {@link #write(LogEvent)} if buffering is off, or from {@link #flush()} if the buffer has reached its limit. 118 * 119 * @param event The event to write to the database. 120 */ 121 protected abstract void writeInternal(LogEvent event); 122 123 /** 124 * Commits any active transaction (if applicable) and disconnects from the database (returns the connection to the 125 * connection pool). With buffering enabled, this is called when flushing the buffer completes, after the last call 126 * to {@link #writeInternal}. With buffering disabled, this is called immediately after every invocation of 127 * {@link #writeInternal}. 128 */ 129 protected abstract void commitAndClose(); 130 131 /** 132 * This method is called automatically when the buffer size reaches its maximum or at the beginning of a call to 133 * {@link #shutdown()}. It can also be called manually to flush events to the database. 134 */ 135 public final synchronized void flush() { 136 if (this.isRunning() && this.buffer.size() > 0) { 137 this.connectAndStart(); 138 try { 139 for (final LogEvent event : this.buffer) { 140 this.writeInternal(event); 141 } 142 } finally { 143 this.commitAndClose(); 144 // not sure if this should be done when writing the events failed 145 this.buffer.clear(); 146 } 147 } 148 } 149 150 /** 151 * This method manages buffering and writing of events. 152 * 153 * @param event The event to write to the database. 154 */ 155 public final synchronized void write(final LogEvent event) { 156 if (this.bufferSize > 0) { 157 this.buffer.add(event); 158 if (this.buffer.size() >= this.bufferSize || event.isEndOfBatch()) { 159 this.flush(); 160 } 161 } else { 162 this.connectAndStart(); 163 try { 164 this.writeInternal(event); 165 } finally { 166 this.commitAndClose(); 167 } 168 } 169 } 170 171 @Override 172 public final void releaseSub() { 173 this.shutdown(); 174 } 175 176 @Override 177 public final String toString() { 178 return this.getName(); 179 } 180 181 /** 182 * Implementations should define their own getManager method and call this method from that to create or get 183 * existing managers. 184 * 185 * @param name The manager name, which should include any configuration details that one might want to be able to 186 * reconfigure at runtime, such as database name, username, (hashed) password, etc. 187 * @param data The concrete instance of {@link AbstractFactoryData} appropriate for the given manager. 188 * @param factory A factory instance for creating the appropriate manager. 189 * @param <M> The concrete manager type. 190 * @param <T> The concrete {@link AbstractFactoryData} type. 191 * @return a new or existing manager of the specified type and name. 192 */ 193 protected static <M extends AbstractDatabaseManager, T extends AbstractFactoryData> M getManager( 194 final String name, final T data, final ManagerFactory<M, T> factory 195 ) { 196 return AbstractManager.getManager(name, factory, data); 197 } 198 199 /** 200 * Implementations should extend this class for passing data between the getManager method and the manager factory 201 * class. 202 */ 203 protected abstract static class AbstractFactoryData { 204 private final int bufferSize; 205 206 /** 207 * Constructs the base factory data. 208 * 209 * @param bufferSize The size of the buffer. 210 */ 211 protected AbstractFactoryData(final int bufferSize) { 212 this.bufferSize = bufferSize; 213 } 214 215 /** 216 * Gets the buffer size. 217 * 218 * @return the buffer size. 219 */ 220 public int getBufferSize() { 221 return bufferSize; 222 } 223 } 224 }