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 }