blob: 870f614d1c32e74ede9c6987fba2c78541ce264b [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache license, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the license for the specific language governing permissions and
* limitations under the license.
*/
package org.apache.logging.log4j.core.appender.db;
import java.io.Flushable;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.appender.AbstractManager;
import org.apache.logging.log4j.core.appender.ManagerFactory;
/**
* Manager that allows database appenders to have their configuration reloaded without losing events.
*/
public abstract class AbstractDatabaseManager extends AbstractManager implements Flushable {
/**
* Implementations should extend this class for passing data between the getManager method and the manager factory
* class.
*/
protected abstract static class AbstractFactoryData {
private final int bufferSize;
private final Layout<? extends Serializable> layout;
/**
* Constructs the base factory data.
*
* @param bufferSize The size of the buffer.
* @param layout The appender-level layout
*/
protected AbstractFactoryData(final int bufferSize, final Layout<? extends Serializable> layout) {
this.bufferSize = bufferSize;
this.layout = layout;
}
/**
* Gets the buffer size.
*
* @return the buffer size.
*/
public int getBufferSize() {
return bufferSize;
}
/**
* Gets the layout.
*
* @return the layout.
*/
public Layout<? extends Serializable> getLayout() {
return layout;
}
}
/**
* Implementations should define their own getManager method and call this method from that to create or get
* existing managers.
*
* @param name The manager name, which should include any configuration details that one might want to be able to
* reconfigure at runtime, such as database name, username, (hashed) password, etc.
* @param data The concrete instance of {@link AbstractFactoryData} appropriate for the given manager.
* @param factory A factory instance for creating the appropriate manager.
* @param <M> The concrete manager type.
* @param <T> The concrete {@link AbstractFactoryData} type.
* @return a new or existing manager of the specified type and name.
*/
protected static <M extends AbstractDatabaseManager, T extends AbstractFactoryData> M getManager(
final String name, final T data, final ManagerFactory<M, T> factory
) {
return AbstractManager.getManager(name, factory, data);
}
private final ArrayList<LogEvent> buffer;
private final int bufferSize;
private final Layout<? extends Serializable> layout;
private boolean running;
/**
* Instantiates the base manager.
*
* @param name The manager name, which should include any configuration details that one might want to be able to
* reconfigure at runtime, such as database name, username, (hashed) password, etc.
* @param bufferSize The size of the log event buffer.
*/
protected AbstractDatabaseManager(final String name, final int bufferSize) {
this(name, bufferSize, null);
}
/**
* Instantiates the base manager.
*
* @param name The manager name, which should include any configuration details that one might want to be able to
* reconfigure at runtime, such as database name, username, (hashed) password, etc.
* @param layout the Appender-level layout.
* @param bufferSize The size of the log event buffer.
*/
protected AbstractDatabaseManager(final String name, final int bufferSize, final Layout<? extends Serializable> layout) {
super(null, name);
this.bufferSize = bufferSize;
this.buffer = new ArrayList<>(bufferSize + 1);
this.layout = layout;
}
protected void buffer(final LogEvent event) {
this.buffer.add(event.toImmutable());
if (this.buffer.size() >= this.bufferSize || event.isEndOfBatch()) {
this.flush();
}
}
/**
* Commits any active transaction (if applicable) and disconnects from the database (returns the connection to the
* connection pool). With buffering enabled, this is called when flushing the buffer completes, after the last call
* to {@link #writeInternal}. With buffering disabled, this is called immediately after every invocation of
* {@link #writeInternal}.
* @return true if all resources were closed normally, false otherwise.
*/
protected abstract boolean commitAndClose();
/**
* Connects to the database and starts a transaction (if applicable). With buffering enabled, this is called when
* flushing the buffer begins, before the first call to {@link #writeInternal}. With buffering disabled, this is
* called immediately before every invocation of {@link #writeInternal}.
*/
protected abstract void connectAndStart();
/**
* This method is called automatically when the buffer size reaches its maximum or at the beginning of a call to
* {@link #shutdown()}. It can also be called manually to flush events to the database.
*/
@Override
public final synchronized void flush() {
if (this.isRunning() && isBuffered()) {
this.connectAndStart();
try {
for (final LogEvent event : this.buffer) {
this.writeInternal(event, layout != null ? layout.toSerializable(event) : null);
}
} finally {
this.commitAndClose();
// not sure if this should be done when writing the events failed
this.buffer.clear();
}
}
}
protected boolean isBuffered() {
return this.bufferSize > 0;
}
/**
* Indicates whether the manager is currently connected {@link #startup()} has been called and {@link #shutdown()}
* has not been called).
*
* @return {@code true} if the manager is connected.
*/
public final boolean isRunning() {
return this.running;
}
@Override
public final boolean releaseSub(final long timeout, final TimeUnit timeUnit) {
return this.shutdown();
}
/**
* This method is called from the {@link #close()} method when the appender is stopped or the appender's manager
* is replaced. If it has not already been called, it calls {@link #shutdownInternal()} and catches any exceptions
* it might throw.
* @return true if all resources were closed normally, false otherwise.
*/
public final synchronized boolean shutdown() {
boolean closed = true;
this.flush();
if (this.isRunning()) {
try {
closed &= this.shutdownInternal();
} catch (final Exception e) {
logWarn("Caught exception while performing database shutdown operations", e);
closed = false;
} finally {
this.running = false;
}
}
return closed;
}
/**
* Implementations should implement this method to perform any proprietary disconnection / shutdown operations. This
* method will never be called twice on the same instance, and it will only be called <em>after</em>
* {@link #startupInternal()}. It is safe to throw any exceptions from this method. This method does not
* necessarily disconnect from the database for the same reasons outlined in {@link #startupInternal()}.
* @return true if all resources were closed normally, false otherwise.
*/
protected abstract boolean shutdownInternal() throws Exception;
/**
* This method is called within the appender when the appender is started. If it has not already been called, it
* calls {@link #startupInternal()} and catches any exceptions it might throw.
*/
public final synchronized void startup() {
if (!this.isRunning()) {
try {
this.startupInternal();
this.running = true;
} catch (final Exception e) {
logError("Could not perform database startup operations", e);
}
}
}
/**
* Implementations should implement this method to perform any proprietary startup operations. This method will
* never be called twice on the same instance. It is safe to throw any exceptions from this method. This method
* does not necessarily connect to the database, as it is generally unreliable to connect once and use the same
* connection for hours.
*/
protected abstract void startupInternal() throws Exception;
@Override
public final String toString() {
return this.getName();
}
/**
* This method manages buffering and writing of events.
*
* @param event The event to write to the database.
* @param serializable Serializable event
*/
public final synchronized void write(final LogEvent event, final Serializable serializable) {
if (isBuffered()) {
buffer(event);
} else {
writeThrough(event, serializable);
}
}
/**
* Performs the actual writing of the event in an implementation-specific way. This method is called immediately
* from {@link #write(LogEvent, Serializable)} if buffering is off, or from {@link #flush()} if the buffer has reached its limit.
*
* @param event The event to write to the database.
*/
protected abstract void writeInternal(LogEvent event, Serializable serializable);
protected void writeThrough(final LogEvent event, final Serializable serializable) {
this.connectAndStart();
try {
this.writeInternal(event, serializable);
} finally {
this.commitAndClose();
}
}
}