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 018package org.apache.logging.log4j.core.appender.db; 019 020import java.io.Flushable; 021import java.io.Serializable; 022import java.util.ArrayList; 023import java.util.concurrent.TimeUnit; 024 025import org.apache.logging.log4j.core.Layout; 026import org.apache.logging.log4j.core.LogEvent; 027import org.apache.logging.log4j.core.appender.AbstractManager; 028import org.apache.logging.log4j.core.appender.ManagerFactory; 029 030/** 031 * Manager that allows database appenders to have their configuration reloaded without losing events. 032 */ 033public abstract class AbstractDatabaseManager extends AbstractManager implements Flushable { 034 035 /** 036 * Implementations should extend this class for passing data between the getManager method and the manager factory 037 * class. 038 */ 039 protected abstract static class AbstractFactoryData { 040 private final int bufferSize; 041 private final Layout<? extends Serializable> layout; 042 043 /** 044 * Constructs the base factory data. 045 * 046 * @param bufferSize The size of the buffer. 047 * @param layout The appender-level layout 048 */ 049 protected AbstractFactoryData(final int bufferSize, final Layout<? extends Serializable> layout) { 050 this.bufferSize = bufferSize; 051 this.layout = layout; 052 } 053 054 /** 055 * Gets the buffer size. 056 * 057 * @return the buffer size. 058 */ 059 public int getBufferSize() { 060 return bufferSize; 061 } 062 063 /** 064 * Gets the layout. 065 * 066 * @return the layout. 067 */ 068 public Layout<? extends Serializable> getLayout() { 069 return layout; 070 } 071 } 072 /** 073 * Implementations should define their own getManager method and call this method from that to create or get 074 * existing managers. 075 * 076 * @param name The manager name, which should include any configuration details that one might want to be able to 077 * reconfigure at runtime, such as database name, username, (hashed) password, etc. 078 * @param data The concrete instance of {@link AbstractFactoryData} appropriate for the given manager. 079 * @param factory A factory instance for creating the appropriate manager. 080 * @param <M> The concrete manager type. 081 * @param <T> The concrete {@link AbstractFactoryData} type. 082 * @return a new or existing manager of the specified type and name. 083 */ 084 protected static <M extends AbstractDatabaseManager, T extends AbstractFactoryData> M getManager( 085 final String name, final T data, final ManagerFactory<M, T> factory 086 ) { 087 return AbstractManager.getManager(name, factory, data); 088 } 089 private final ArrayList<LogEvent> buffer; 090 private final int bufferSize; 091 092 private final Layout<? extends Serializable> layout; 093 094 private boolean running; 095 096 /** 097 * Instantiates the base manager. 098 * 099 * @param name The manager name, which should include any configuration details that one might want to be able to 100 * reconfigure at runtime, such as database name, username, (hashed) password, etc. 101 * @param bufferSize The size of the log event buffer. 102 */ 103 protected AbstractDatabaseManager(final String name, final int bufferSize) { 104 this(name, bufferSize, null); 105 } 106 107 /** 108 * Instantiates the base manager. 109 * 110 * @param name The manager name, which should include any configuration details that one might want to be able to 111 * reconfigure at runtime, such as database name, username, (hashed) password, etc. 112 * @param layout the Appender-level layout. 113 * @param bufferSize The size of the log event buffer. 114 */ 115 protected AbstractDatabaseManager(final String name, final int bufferSize, final Layout<? extends Serializable> layout) { 116 super(null, name); 117 this.bufferSize = bufferSize; 118 this.buffer = new ArrayList<>(bufferSize + 1); 119 this.layout = layout; 120 } 121 122 protected void buffer(final LogEvent event) { 123 this.buffer.add(event.toImmutable()); 124 if (this.buffer.size() >= this.bufferSize || event.isEndOfBatch()) { 125 this.flush(); 126 } 127 } 128 129 /** 130 * Commits any active transaction (if applicable) and disconnects from the database (returns the connection to the 131 * connection pool). With buffering enabled, this is called when flushing the buffer completes, after the last call 132 * to {@link #writeInternal}. With buffering disabled, this is called immediately after every invocation of 133 * {@link #writeInternal}. 134 * @return true if all resources were closed normally, false otherwise. 135 */ 136 protected abstract boolean commitAndClose(); 137 138 /** 139 * Connects to the database and starts a transaction (if applicable). With buffering enabled, this is called when 140 * flushing the buffer begins, before the first call to {@link #writeInternal}. With buffering disabled, this is 141 * called immediately before every invocation of {@link #writeInternal}. 142 */ 143 protected abstract void connectAndStart(); 144 145 /** 146 * This method is called automatically when the buffer size reaches its maximum or at the beginning of a call to 147 * {@link #shutdown()}. It can also be called manually to flush events to the database. 148 */ 149 @Override 150 public final synchronized void flush() { 151 if (this.isRunning() && isBuffered()) { 152 this.connectAndStart(); 153 try { 154 for (final LogEvent event : this.buffer) { 155 this.writeInternal(event, layout != null ? layout.toSerializable(event) : null); 156 } 157 } finally { 158 this.commitAndClose(); 159 // not sure if this should be done when writing the events failed 160 this.buffer.clear(); 161 } 162 } 163 } 164 165 protected boolean isBuffered() { 166 return this.bufferSize > 0; 167 } 168 169 /** 170 * Indicates whether the manager is currently connected {@link #startup()} has been called and {@link #shutdown()} 171 * has not been called). 172 * 173 * @return {@code true} if the manager is connected. 174 */ 175 public final boolean isRunning() { 176 return this.running; 177 } 178 179 @Override 180 public final boolean releaseSub(final long timeout, final TimeUnit timeUnit) { 181 return this.shutdown(); 182 } 183 184 /** 185 * This method is called from the {@link #close()} method when the appender is stopped or the appender's manager 186 * is replaced. If it has not already been called, it calls {@link #shutdownInternal()} and catches any exceptions 187 * it might throw. 188 * @return true if all resources were closed normally, false otherwise. 189 */ 190 public final synchronized boolean shutdown() { 191 boolean closed = true; 192 this.flush(); 193 if (this.isRunning()) { 194 try { 195 closed &= this.shutdownInternal(); 196 } catch (final Exception e) { 197 logWarn("Caught exception while performing database shutdown operations", e); 198 closed = false; 199 } finally { 200 this.running = false; 201 } 202 } 203 return closed; 204 } 205 206 /** 207 * Implementations should implement this method to perform any proprietary disconnection / shutdown operations. This 208 * method will never be called twice on the same instance, and it will only be called <em>after</em> 209 * {@link #startupInternal()}. It is safe to throw any exceptions from this method. This method does not 210 * necessarily disconnect from the database for the same reasons outlined in {@link #startupInternal()}. 211 * @return true if all resources were closed normally, false otherwise. 212 */ 213 protected abstract boolean shutdownInternal() throws Exception; 214 215 /** 216 * This method is called within the appender when the appender is started. If it has not already been called, it 217 * calls {@link #startupInternal()} and catches any exceptions it might throw. 218 */ 219 public final synchronized void startup() { 220 if (!this.isRunning()) { 221 try { 222 this.startupInternal(); 223 this.running = true; 224 } catch (final Exception e) { 225 logError("Could not perform database startup operations", e); 226 } 227 } 228 } 229 230 /** 231 * Implementations should implement this method to perform any proprietary startup operations. This method will 232 * never be called twice on the same instance. It is safe to throw any exceptions from this method. This method 233 * does not necessarily connect to the database, as it is generally unreliable to connect once and use the same 234 * connection for hours. 235 */ 236 protected abstract void startupInternal() throws Exception; 237 238 @Override 239 public final String toString() { 240 return this.getName(); 241 } 242 243 /** 244 * This method manages buffering and writing of events. 245 * 246 * @param event The event to write to the database. 247 * @deprecated since 2.11.0 Use {@link #write(LogEvent, Serializable)}. 248 */ 249 @Deprecated 250 public final synchronized void write(final LogEvent event) { 251 write(event, null); 252 } 253 254 /** 255 * This method manages buffering and writing of events. 256 * 257 * @param event The event to write to the database. 258 * @param serializable Serializable event 259 */ 260 public final synchronized void write(final LogEvent event, final Serializable serializable) { 261 if (isBuffered()) { 262 buffer(event); 263 } else { 264 writeThrough(event, serializable); 265 } 266 } 267 268 /** 269 * Performs the actual writing of the event in an implementation-specific way. This method is called immediately 270 * from {@link #write(LogEvent, Serializable)} if buffering is off, or from {@link #flush()} if the buffer has reached its limit. 271 * 272 * @param event The event to write to the database. 273 * @deprecated Use {@link #writeInternal(LogEvent, Serializable)}. 274 */ 275 @Deprecated 276 protected void writeInternal(final LogEvent event) { 277 writeInternal(event, null); 278 } 279 280 /** 281 * Performs the actual writing of the event in an implementation-specific way. This method is called immediately 282 * from {@link #write(LogEvent, Serializable)} if buffering is off, or from {@link #flush()} if the buffer has reached its limit. 283 * 284 * @param event The event to write to the database. 285 */ 286 protected abstract void writeInternal(LogEvent event, Serializable serializable); 287 288 protected void writeThrough(final LogEvent event, final Serializable serializable) { 289 this.connectAndStart(); 290 try { 291 this.writeInternal(event, serializable); 292 } finally { 293 this.commitAndClose(); 294 } 295 } 296}