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 package org.apache.logging.log4j.core.appender.db; 18 19 import java.util.ArrayList; 20 21 import org.apache.logging.log4j.core.LogEvent; 22 import org.apache.logging.log4j.core.appender.AbstractManager; 23 import org.apache.logging.log4j.core.appender.ManagerFactory; 24 25 /** 26 * Manager that allows database appenders to have their configuration reloaded without losing events. 27 */ 28 public abstract class AbstractDatabaseManager extends AbstractManager { 29 private final ArrayList<LogEvent> buffer; 30 private final int bufferSize; 31 32 private boolean connected = false; 33 34 /** 35 * Instantiates the base manager. 36 * 37 * @param name The manager name, which should include any configuration details that one might want to be able to 38 * reconfigure at runtime, such as database name, username, (hashed) password, etc. 39 * @param bufferSize The size of the log event buffer. 40 */ 41 protected AbstractDatabaseManager(final String name, final int bufferSize) { 42 super(name); 43 this.bufferSize = bufferSize; 44 this.buffer = new ArrayList<LogEvent>(bufferSize + 1); 45 } 46 47 /** 48 * Implementations should implement this method to perform any proprietary connection operations. This method will 49 * never be called twice on the same instance. It is safe to throw any exceptions from this method. 50 */ 51 protected abstract void connectInternal(); 52 53 /** 54 * This method is called within the appender when the appender is started. If it has not already been called, it 55 * calls {@link #connectInternal()} and catches any exceptions it might throw. 56 */ 57 public final synchronized void connect() { 58 if (!this.isConnected()) { 59 try { 60 this.connectInternal(); 61 this.connected = true; 62 } catch (final Exception e) { 63 LOGGER.error("Could not connect database logging manager.", e); 64 } 65 } 66 } 67 68 /** 69 * Implementations should implement this method to perform any proprietary disconnection / shutdown operations. This 70 * method will never be called twice on the same instance, and it will only be called <em>after</em> 71 * {@link #connectInternal()}. It is safe to throw any exceptions from this method. 72 */ 73 protected abstract void disconnectInternal(); 74 75 /** 76 * This method is called from the {@link #release()} method when the appender is stopped or the appender's manager 77 * is replaced. If it has not already been called, it calls {@link #disconnectInternal()} and catches any exceptions 78 * it might throw. 79 */ 80 public final synchronized void disconnect() { 81 this.flush(); 82 if (this.isConnected()) { 83 try { 84 this.disconnectInternal(); 85 } catch (final Exception e) { 86 LOGGER.warn("Error while disconnecting database logging manager.", e); 87 } finally { 88 this.connected = false; 89 } 90 } 91 } 92 93 /** 94 * Indicates whether the manager is currently connected {@link #connect()} has been called and {@link #disconnect()} 95 * has not been called). 96 * 97 * @return {@code true} if the manager is connected. 98 */ 99 public final boolean isConnected() { 100 return this.connected; 101 } 102 103 /** 104 * Performs the actual writing of the event in an implementation-specific way. This method is called immediately 105 * from {@link #write(LogEvent)} if buffering is off, or from {@link #flush()} if the buffer has reached its limit. 106 * 107 * @param event The event to write to the database. 108 */ 109 protected abstract void writeInternal(LogEvent event); 110 111 /** 112 * This method is called automatically when the buffer size reaches its maximum or at the beginning of a call to 113 * {@link #disconnect()}. It can also be called manually to flush events to the database. 114 */ 115 public final synchronized void flush() { 116 if (this.isConnected() && this.buffer.size() > 0) { 117 for (final LogEvent event : this.buffer) { 118 this.writeInternal(event); 119 } 120 this.buffer.clear(); 121 } 122 } 123 124 /** 125 * This method manages buffering and writing of events. 126 * 127 * @param event The event to write to the database. 128 */ 129 public final synchronized void write(final LogEvent event) { 130 if (this.bufferSize > 0) { 131 this.buffer.add(event); 132 if (this.buffer.size() >= this.bufferSize || event.isEndOfBatch()) { 133 this.flush(); 134 } 135 } else { 136 this.writeInternal(event); 137 } 138 } 139 140 @Override 141 public final void releaseSub() { 142 this.disconnect(); 143 } 144 145 @Override 146 public final String toString() { 147 return this.getName(); 148 } 149 150 /** 151 * Implementations should define their own getManager method and call this method from that to create or get 152 * existing managers. 153 * 154 * @param name The manager name, which should include any configuration details that one might want to be able to 155 * reconfigure at runtime, such as database name, username, (hashed) password, etc. 156 * @param data The concrete instance of {@link AbstractFactoryData} appropriate for the given manager. 157 * @param factory A factory instance for creating the appropriate manager. 158 * @param <M> The concrete manager type. 159 * @param <T> The concrete {@link AbstractFactoryData} type. 160 * @return a new or existing manager of the specified type and name. 161 */ 162 protected static <M extends AbstractDatabaseManager, T extends AbstractFactoryData> M getManager( 163 final String name, final T data, final ManagerFactory<M, T> factory 164 ) { 165 return AbstractManager.getManager(name, factory, data); 166 } 167 168 /** 169 * Implementations should extend this class for passing data between the getManager method and the manager factory 170 * class. 171 */ 172 protected abstract static class AbstractFactoryData { 173 private final int bufferSize; 174 175 /** 176 * Constructs the base factory data. 177 * 178 * @param bufferSize The size of the buffer. 179 */ 180 protected AbstractFactoryData(final int bufferSize) { 181 this.bufferSize = bufferSize; 182 } 183 184 /** 185 * Gets the buffer size. 186 * 187 * @return the buffer size. 188 */ 189 public int getBufferSize() { 190 return bufferSize; 191 } 192 } 193 }