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