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