1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.logging.log4j.core.appender.db.jdbc;
18
19 import java.io.Serializable;
20 import java.io.StringReader;
21 import java.sql.Clob;
22 import java.sql.Connection;
23 import java.sql.DatabaseMetaData;
24 import java.sql.NClob;
25 import java.sql.PreparedStatement;
26 import java.sql.SQLException;
27 import java.sql.Timestamp;
28 import java.sql.Types;
29 import java.util.ArrayList;
30 import java.util.Date;
31 import java.util.List;
32 import java.util.Objects;
33
34 import org.apache.logging.log4j.core.Layout;
35 import org.apache.logging.log4j.core.LogEvent;
36 import org.apache.logging.log4j.core.StringLayout;
37 import org.apache.logging.log4j.core.appender.AppenderLoggingException;
38 import org.apache.logging.log4j.core.appender.ManagerFactory;
39 import org.apache.logging.log4j.core.appender.db.AbstractDatabaseManager;
40 import org.apache.logging.log4j.core.appender.db.ColumnMapping;
41 import org.apache.logging.log4j.core.config.plugins.convert.DateTypeConverter;
42 import org.apache.logging.log4j.core.config.plugins.convert.TypeConverters;
43 import org.apache.logging.log4j.core.util.Closer;
44 import org.apache.logging.log4j.message.MapMessage;
45 import org.apache.logging.log4j.spi.ThreadContextMap;
46 import org.apache.logging.log4j.spi.ThreadContextStack;
47 import org.apache.logging.log4j.status.StatusLogger;
48 import org.apache.logging.log4j.util.IndexedReadOnlyStringMap;
49 import org.apache.logging.log4j.util.ReadOnlyStringMap;
50 import org.apache.logging.log4j.util.Strings;
51
52
53
54
55 public final class JdbcDatabaseManager extends AbstractDatabaseManager {
56
57 private static StatusLogger logger() {
58 return StatusLogger.getLogger();
59 }
60
61 private static final JdbcDatabaseManagerFactory INSTANCE = new JdbcDatabaseManagerFactory();
62
63
64 private final List<ColumnMapping> columnMappings;
65 private final List<ColumnConfig> columnConfigs;
66 private final ConnectionSource connectionSource;
67 private final String sqlStatement;
68
69 private Connection connection;
70 private PreparedStatement statement;
71 private boolean isBatchSupported;
72
73 private JdbcDatabaseManager(final String name, final int bufferSize, final ConnectionSource connectionSource,
74 final String sqlStatement, final List<ColumnConfig> columnConfigs,
75 final List<ColumnMapping> columnMappings) {
76 super(name, bufferSize);
77 this.connectionSource = connectionSource;
78 this.sqlStatement = sqlStatement;
79 this.columnConfigs = columnConfigs;
80 this.columnMappings = columnMappings;
81 }
82
83 @Override
84 protected void startupInternal() throws Exception {
85 this.connection = this.connectionSource.getConnection();
86 final DatabaseMetaData metaData = this.connection.getMetaData();
87 this.isBatchSupported = metaData.supportsBatchUpdates();
88 logger().debug("Closing Connection {}", this.connection);
89 Closer.closeSilently(this.connection);
90 }
91
92 @Override
93 protected boolean shutdownInternal() {
94 if (this.connection != null || this.statement != null) {
95 return this.commitAndClose();
96 }
97 if (connectionSource != null) {
98 connectionSource.stop();
99 }
100 return true;
101 }
102
103 @Override
104 protected void connectAndStart() {
105 try {
106 this.connection = this.connectionSource.getConnection();
107 this.connection.setAutoCommit(false);
108 logger().debug("Preparing SQL: {}", this.sqlStatement);
109 this.statement = this.connection.prepareStatement(this.sqlStatement);
110 } catch (final SQLException e) {
111 throw new AppenderLoggingException(
112 "Cannot write logging event or flush buffer; JDBC manager cannot connect to the database.", e);
113 }
114 }
115
116 @Deprecated
117 @Override
118 protected void writeInternal(final LogEvent event) {
119 writeInternal(event, null);
120 }
121
122 private void setFields(final MapMessage<?, ?> mapMessage) throws SQLException {
123 final IndexedReadOnlyStringMap map = mapMessage.getIndexedReadOnlyStringMap();
124 final String simpleName = statement.getClass().getName();
125 int i = 1;
126 for (final ColumnMapping mapping : this.columnMappings) {
127 final String source = mapping.getSource();
128 final String key = Strings.isEmpty(source) ? mapping.getName() : source;
129 final Object value = map.getValue(key);
130 if (logger().isTraceEnabled()) {
131 final String valueStr = value instanceof String ? "\"" + value + "\"" : Objects.toString(value, null);
132 logger().trace("{} setObject({}, {}) for key '{}' and mapping '{}'", simpleName, i, valueStr, key,
133 mapping.getName());
134 }
135 statement.setObject(i++, value);
136 }
137 }
138
139 @Override
140 protected void writeInternal(final LogEvent event, final Serializable serializable) {
141 StringReader reader = null;
142 try {
143 if (!this.isRunning() || this.connection == null || this.connection.isClosed() || this.statement == null
144 || this.statement.isClosed()) {
145 throw new AppenderLoggingException(
146 "Cannot write logging event; JDBC manager not connected to the database.");
147 }
148
149 if (serializable instanceof MapMessage) {
150 setFields((MapMessage<?, ?>) serializable);
151 }
152 int i = 1;
153 for (final ColumnMapping mapping : this.columnMappings) {
154 if (ThreadContextMap.class.isAssignableFrom(mapping.getType())
155 || ReadOnlyStringMap.class.isAssignableFrom(mapping.getType())) {
156 this.statement.setObject(i++, event.getContextData().toMap());
157 } else if (ThreadContextStack.class.isAssignableFrom(mapping.getType())) {
158 this.statement.setObject(i++, event.getContextStack().asList());
159 } else if (Date.class.isAssignableFrom(mapping.getType())) {
160 this.statement.setObject(i++, DateTypeConverter.fromMillis(event.getTimeMillis(),
161 mapping.getType().asSubclass(Date.class)));
162 } else {
163 StringLayout layout = mapping.getLayout();
164 if (layout != null) {
165 if (Clob.class.isAssignableFrom(mapping.getType())) {
166 this.statement.setClob(i++, new StringReader(layout.toSerializable(event)));
167 } else if (NClob.class.isAssignableFrom(mapping.getType())) {
168 this.statement.setNClob(i++, new StringReader(layout.toSerializable(event)));
169 } else {
170 final Object value = TypeConverters.convert(layout.toSerializable(event), mapping.getType(),
171 null);
172 if (value == null) {
173 this.statement.setNull(i++, Types.NULL);
174 } else {
175 this.statement.setObject(i++, value);
176 }
177 }
178 }
179 }
180 }
181 for (final ColumnConfig column : this.columnConfigs) {
182 if (column.isEventTimestamp()) {
183 this.statement.setTimestamp(i++, new Timestamp(event.getTimeMillis()));
184 } else if (column.isClob()) {
185 reader = new StringReader(column.getLayout().toSerializable(event));
186 if (column.isUnicode()) {
187 this.statement.setNClob(i++, reader);
188 } else {
189 this.statement.setClob(i++, reader);
190 }
191 } else if (column.isUnicode()) {
192 this.statement.setNString(i++, column.getLayout().toSerializable(event));
193 } else {
194 this.statement.setString(i++, column.getLayout().toSerializable(event));
195 }
196 }
197
198 if (this.isBatchSupported) {
199 this.statement.addBatch();
200 } else if (this.statement.executeUpdate() == 0) {
201 throw new AppenderLoggingException(
202 "No records inserted in database table for log event in JDBC manager.");
203 }
204 } catch (final SQLException e) {
205 throw new AppenderLoggingException("Failed to insert record for log event in JDBC manager: " +
206 e.getMessage(), e);
207 } finally {
208 Closer.closeSilently(reader);
209 }
210 }
211
212 @Override
213 protected boolean commitAndClose() {
214 boolean closed = true;
215 try {
216 if (this.connection != null && !this.connection.isClosed()) {
217 if (this.isBatchSupported) {
218 logger().debug("Executing batch PreparedStatement {}", this.statement);
219 this.statement.executeBatch();
220 }
221 logger().debug("Committing Connection {}", this.connection);
222 this.connection.commit();
223 }
224 } catch (final SQLException e) {
225 throw new AppenderLoggingException("Failed to commit transaction logging event or flushing buffer.", e);
226 } finally {
227 try {
228 logger().debug("Closing PreparedStatement {}", this.statement);
229 Closer.close(this.statement);
230 } catch (final Exception e) {
231 logWarn("Failed to close SQL statement logging event or flushing buffer", e);
232 closed = false;
233 } finally {
234 this.statement = null;
235 }
236
237 try {
238 logger().debug("Closing Connection {}", this.connection);
239 Closer.close(this.connection);
240 } catch (final Exception e) {
241 logWarn("Failed to close database connection logging event or flushing buffer", e);
242 closed = false;
243 } finally {
244 this.connection = null;
245 }
246 }
247 return closed;
248 }
249
250
251
252
253
254
255
256
257
258
259
260
261 @Deprecated
262 public static JdbcDatabaseManager getJDBCDatabaseManager(final String name, final int bufferSize,
263 final ConnectionSource connectionSource,
264 final String tableName,
265 final ColumnConfig[] columnConfigs) {
266
267 return getManager(name,
268 new FactoryData(bufferSize, null, connectionSource, tableName, columnConfigs, new ColumnMapping[0]),
269 getFactory());
270 }
271
272
273
274
275
276
277
278
279
280
281
282
283
284 @Deprecated
285 public static JdbcDatabaseManager getManager(final String name,
286 final int bufferSize,
287 final ConnectionSource connectionSource,
288 final String tableName,
289 final ColumnConfig[] columnConfigs,
290 final ColumnMapping[] columnMappings) {
291 return getManager(name, new FactoryData(bufferSize, null, connectionSource, tableName, columnConfigs, columnMappings),
292 getFactory());
293 }
294
295
296
297
298
299
300
301
302
303
304
305
306
307 public static JdbcDatabaseManager getManager(final String name,
308 final int bufferSize,
309 final Layout<? extends Serializable> layout,
310 final ConnectionSource connectionSource,
311 final String tableName,
312 final ColumnConfig[] columnConfigs,
313 final ColumnMapping[] columnMappings) {
314 return getManager(name, new FactoryData(bufferSize, layout, connectionSource, tableName, columnConfigs, columnMappings),
315 getFactory());
316 }
317
318 private static JdbcDatabaseManagerFactory getFactory() {
319 return INSTANCE;
320 }
321
322
323
324
325 private static final class FactoryData extends AbstractDatabaseManager.AbstractFactoryData {
326 private final ConnectionSource connectionSource;
327 private final String tableName;
328 private final ColumnConfig[] columnConfigs;
329 private final ColumnMapping[] columnMappings;
330
331 protected FactoryData(final int bufferSize, final Layout<? extends Serializable> layout,
332 final ConnectionSource connectionSource, final String tableName, final ColumnConfig[] columnConfigs,
333 final ColumnMapping[] columnMappings) {
334 super(bufferSize, layout);
335 this.connectionSource = connectionSource;
336 this.tableName = tableName;
337 this.columnConfigs = columnConfigs;
338 this.columnMappings = columnMappings;
339 }
340 }
341
342
343
344
345 private static final class JdbcDatabaseManagerFactory implements ManagerFactory<JdbcDatabaseManager, FactoryData> {
346
347 private static final char PARAMETER_MARKER = '?';
348
349 @Override
350 public JdbcDatabaseManager createManager(final String name, final FactoryData data) {
351 final StringBuilder sb = new StringBuilder("INSERT INTO ").append(data.tableName).append(" (");
352
353
354 int i = 1;
355 for (final ColumnMapping mapping : data.columnMappings) {
356 final String mappingName = mapping.getName();
357 logger().trace("Adding INSERT ColumnMapping[{}]: {}={} ", i++, mappingName, mapping);
358 sb.append(mappingName).append(',');
359 }
360 for (final ColumnConfig config : data.columnConfigs) {
361 sb.append(config.getColumnName()).append(',');
362 }
363
364 sb.setCharAt(sb.length() - 1, ')');
365 sb.append(" VALUES (");
366 i = 1;
367 final List<ColumnMapping> columnMappings = new ArrayList<>(data.columnMappings.length);
368 for (final ColumnMapping mapping : data.columnMappings) {
369 final String mappingName = mapping.getName();
370 if (Strings.isNotEmpty(mapping.getLiteralValue())) {
371 logger().trace("Adding INSERT VALUES literal for ColumnMapping[{}]: {}={} ", i, mappingName, mapping.getLiteralValue());
372 sb.append(mapping.getLiteralValue());
373 }
374 if (Strings.isNotEmpty(mapping.getParameter())) {
375 logger().trace("Adding INSERT VALUES parameter for ColumnMapping[{}]: {}={} ", i, mappingName, mapping.getParameter());
376 sb.append(mapping.getParameter());
377 columnMappings.add(mapping);
378 } else {
379 logger().trace("Adding INSERT VALUES parameter marker for ColumnMapping[{}]: {}={} ", i, mappingName, PARAMETER_MARKER);
380 sb.append(PARAMETER_MARKER);
381 columnMappings.add(mapping);
382 }
383 sb.append(',');
384 i++;
385 }
386 final List<ColumnConfig> columnConfigs = new ArrayList<>(data.columnConfigs.length);
387 for (final ColumnConfig config : data.columnConfigs) {
388 if (Strings.isNotEmpty(config.getLiteralValue())) {
389 sb.append(config.getLiteralValue());
390 } else {
391 sb.append(PARAMETER_MARKER);
392 columnConfigs.add(config);
393 }
394 sb.append(',');
395 }
396
397 sb.setCharAt(sb.length() - 1, ')');
398 final String sqlStatement = sb.toString();
399
400 return new JdbcDatabaseManager(name, data.getBufferSize(), data.connectionSource, sqlStatement,
401 columnConfigs, columnMappings);
402 }
403 }
404
405 }