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 }