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.io.Serializable; 22 import java.util.ArrayList; 23 import java.util.concurrent.TimeUnit; 24 25 import org.apache.logging.log4j.core.Layout; 26 import org.apache.logging.log4j.core.LogEvent; 27 import org.apache.logging.log4j.core.appender.AbstractManager; 28 import org.apache.logging.log4j.core.appender.ManagerFactory; 29 30 /** 31 * Manager that allows database appenders to have their configuration reloaded without losing events. 32 */ 33 public abstract class AbstractDatabaseManager extends AbstractManager implements Flushable { 34 private final ArrayList<LogEvent> buffer; 35 private final int bufferSize; 36 private final Layout<? extends Serializable> layout; 37 38 private boolean running = false; 39 40 /** 41 * Instantiates the base manager. 42 * 43 * @param name The manager name, which should include any configuration details that one might want to be able to 44 * reconfigure at runtime, such as database name, username, (hashed) password, etc. 45 * @param bufferSize The size of the log event buffer. 46 */ 47 protected AbstractDatabaseManager(final String name, final int bufferSize) { 48 this(name, bufferSize, null); 49 } 50 51 /** 52 * Instantiates the base manager. 53 * 54 * @param name The manager name, which should include any configuration details that one might want to be able to 55 * reconfigure at runtime, such as database name, username, (hashed) password, etc. 56 * @param layout the Appender-level layout. 57 * @param bufferSize The size of the log event buffer. 58 */ 59 protected AbstractDatabaseManager(final String name, final int bufferSize, final Layout<? extends Serializable> layout) { 60 super(null, name); 61 this.bufferSize = bufferSize; 62 this.buffer = new ArrayList<>(bufferSize + 1); 63 this.layout = layout; 64 } 65 66 /** 67 * Implementations should implement this method to perform any proprietary startup operations. This method will 68 * never be called twice on the same instance. It is safe to throw any exceptions from this method. This method 69 * does not necessarily connect to the database, as it is generally unreliable to connect once and use the same 70 * connection for hours. 71 */ 72 protected abstract void startupInternal() throws Exception; 73 74 /** 75 * This method is called within the appender when the appender is started. If it has not already been called, it 76 * calls {@link #startupInternal()} and catches any exceptions it might throw. 77 */ 78 public final synchronized void startup() { 79 if (!this.isRunning()) { 80 try { 81 this.startupInternal(); 82 this.running = true; 83 } catch (final Exception e) { 84 logError("Could not perform database startup operations", e); 85 } 86 } 87 } 88 89 /** 90 * Implementations should implement this method to perform any proprietary disconnection / shutdown operations. This 91 * method will never be called twice on the same instance, and it will only be called <em>after</em> 92 * {@link #startupInternal()}. It is safe to throw any exceptions from this method. This method does not 93 * necessarily disconnect from the database for the same reasons outlined in {@link #startupInternal()}. 94 * @return true if all resources were closed normally, false otherwise. 95 */ 96 protected abstract boolean shutdownInternal() throws Exception; 97 98 /** 99 * This method is called from the {@link #close()} method when the appender is stopped or the appender's manager 100 * is replaced. If it has not already been called, it calls {@link #shutdownInternal()} and catches any exceptions 101 * it might throw. 102 * @return true if all resources were closed normally, false otherwise. 103 */ 104 public final synchronized boolean shutdown() { 105 boolean closed = true; 106 this.flush(); 107 if (this.isRunning()) { 108 try { 109 closed &= this.shutdownInternal(); 110 } catch (final Exception e) { 111 logWarn("Caught exception while performing database shutdown operations", e); 112 closed = false; 113 } finally { 114 this.running = false; 115 } 116 } 117 return closed; 118 } 119 120 /** 121 * Indicates whether the manager is currently connected {@link #startup()} has been called and {@link #shutdown()} 122 * has not been called). 123 * 124 * @return {@code true} if the manager is connected. 125 */ 126 public final boolean isRunning() { 127 return this.running; 128 } 129 130 /** 131 * Connects to the database and starts a transaction (if applicable). With buffering enabled, this is called when 132 * flushing the buffer begins, before the first call to {@link #writeInternal}. With buffering disabled, this is 133 * called immediately before every invocation of {@link #writeInternal}. 134 */ 135 protected abstract void connectAndStart(); 136 137 /** 138 * Performs the actual writing of the event in an implementation-specific way. This method is called immediately 139 * from {@link #write(LogEvent, Serializable)} if buffering is off, or from {@link #flush()} if the buffer has reached its limit. 140 * 141 * @param event The event to write to the database. 142 * @deprecated Use {@link #writeInternal(LogEvent, Serializable)}. 143 */ 144 @Deprecated 145 protected abstract void writeInternal(LogEvent event); 146 147 /** 148 * Performs the actual writing of the event in an implementation-specific way. This method is called immediately 149 * from {@link #write(LogEvent, Serializable)} if buffering is off, or from {@link #flush()} if the buffer has reached its limit. 150 * 151 * @param event The event to write to the database. 152 */ 153 protected abstract void writeInternal(LogEvent event, Serializable serializable); 154 155 /** 156 * Commits any active transaction (if applicable) and disconnects from the database (returns the connection to the 157 * connection pool). With buffering enabled, this is called when flushing the buffer completes, after the last call 158 * to {@link #writeInternal}. With buffering disabled, this is called immediately after every invocation of 159 * {@link #writeInternal}. 160 * @return true if all resources were closed normally, false otherwise. 161 */ 162 protected abstract boolean commitAndClose(); 163 164 /** 165 * This method is called automatically when the buffer size reaches its maximum or at the beginning of a call to 166 * {@link #shutdown()}. It can also be called manually to flush events to the database. 167 */ 168 @Override 169 public final synchronized void flush() { 170 if (this.isRunning() && this.buffer.size() > 0) { 171 this.connectAndStart(); 172 try { 173 for (final LogEvent event : this.buffer) { 174 this.writeInternal(event, layout != null ? layout.toSerializable(event) : null); 175 } 176 } finally { 177 this.commitAndClose(); 178 // not sure if this should be done when writing the events failed 179 this.buffer.clear(); 180 } 181 } 182 } 183 184 /** 185 * This method manages buffering and writing of events. 186 * 187 * @param event The event to write to the database. 188 * @deprecated since 2.11.0 Use {@link #write(LogEvent, Serializable)}. 189 */ 190 @Deprecated 191 public final synchronized void write(final LogEvent event) { 192 write(event, null); 193 } 194 195 /** 196 * This method manages buffering and writing of events. 197 * 198 * @param event The event to write to the database. 199 * @param serializable Serializable event 200 */ 201 public final synchronized void write(final LogEvent event, final Serializable serializable) { 202 if (this.bufferSize > 0) { 203 this.buffer.add(event.toImmutable()); 204 if (this.buffer.size() >= this.bufferSize || event.isEndOfBatch()) { 205 this.flush(); 206 } 207 } else { 208 this.connectAndStart(); 209 try { 210 this.writeInternal(event, serializable); 211 } finally { 212 this.commitAndClose(); 213 } 214 } 215 } 216 217 @Override 218 public final boolean releaseSub(final long timeout, final TimeUnit timeUnit) { 219 return this.shutdown(); 220 } 221 222 @Override 223 public final String toString() { 224 return this.getName(); 225 } 226 227 /** 228 * Implementations should define their own getManager method and call this method from that to create or get 229 * existing managers. 230 * 231 * @param name The manager name, which should include any configuration details that one might want to be able to 232 * reconfigure at runtime, such as database name, username, (hashed) password, etc. 233 * @param data The concrete instance of {@link AbstractFactoryData} appropriate for the given manager. 234 * @param factory A factory instance for creating the appropriate manager. 235 * @param <M> The concrete manager type. 236 * @param <T> The concrete {@link AbstractFactoryData} type. 237 * @return a new or existing manager of the specified type and name. 238 */ 239 protected static <M extends AbstractDatabaseManager, T extends AbstractFactoryData> M getManager( 240 final String name, final T data, final ManagerFactory<M, T> factory 241 ) { 242 return AbstractManager.getManager(name, factory, data); 243 } 244 245 /** 246 * Implementations should extend this class for passing data between the getManager method and the manager factory 247 * class. 248 */ 249 protected abstract static class AbstractFactoryData { 250 private final int bufferSize; 251 private final Layout<? extends Serializable> layout; 252 253 /** 254 * Constructs the base factory data. 255 * 256 * @param bufferSize The size of the buffer. 257 * @param bufferSize The appender-level layout 258 */ 259 protected AbstractFactoryData(final int bufferSize, final Layout<? extends Serializable> layout) { 260 this.bufferSize = bufferSize; 261 this.layout = layout; 262 } 263 264 /** 265 * Gets the buffer size. 266 * 267 * @return the buffer size. 268 */ 269 public int getBufferSize() { 270 return bufferSize; 271 } 272 273 /** 274 * Gets the layout. 275 * 276 * @return the layout. 277 */ 278 public Layout<? extends Serializable> getLayout() { 279 return layout; 280 } 281 } 282 }