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
35 /**
36 * Implementations should extend this class for passing data between the getManager method and the manager factory
37 * class.
38 */
39 protected abstract static class AbstractFactoryData {
40 private final int bufferSize;
41 private final Layout<? extends Serializable> layout;
42
43 /**
44 * Constructs the base factory data.
45 *
46 * @param bufferSize The size of the buffer.
47 * @param bufferSize The appender-level layout
48 */
49 protected AbstractFactoryData(final int bufferSize, final Layout<? extends Serializable> layout) {
50 this.bufferSize = bufferSize;
51 this.layout = layout;
52 }
53
54 /**
55 * Gets the buffer size.
56 *
57 * @return the buffer size.
58 */
59 public int getBufferSize() {
60 return bufferSize;
61 }
62
63 /**
64 * Gets the layout.
65 *
66 * @return the layout.
67 */
68 public Layout<? extends Serializable> getLayout() {
69 return layout;
70 }
71 }
72 /**
73 * Implementations should define their own getManager method and call this method from that to create or get
74 * existing managers.
75 *
76 * @param name The manager name, which should include any configuration details that one might want to be able to
77 * reconfigure at runtime, such as database name, username, (hashed) password, etc.
78 * @param data The concrete instance of {@link AbstractFactoryData} appropriate for the given manager.
79 * @param factory A factory instance for creating the appropriate manager.
80 * @param <M> The concrete manager type.
81 * @param <T> The concrete {@link AbstractFactoryData} type.
82 * @return a new or existing manager of the specified type and name.
83 */
84 protected static <M extends AbstractDatabaseManager, T extends AbstractFactoryData> M getManager(
85 final String name, final T data, final ManagerFactory<M, T> factory
86 ) {
87 return AbstractManager.getManager(name, factory, data);
88 }
89 private final ArrayList<LogEvent> buffer;
90 private final int bufferSize;
91
92 private final Layout<? extends Serializable> layout;
93
94 private boolean running;
95
96 /**
97 * Instantiates the base manager.
98 *
99 * @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 }