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.StringReader;
20 import java.sql.Clob;
21 import java.sql.Connection;
22 import java.sql.DatabaseMetaData;
23 import java.sql.NClob;
24 import java.sql.PreparedStatement;
25 import java.sql.SQLException;
26 import java.sql.Timestamp;
27 import java.sql.Types;
28 import java.util.ArrayList;
29 import java.util.Date;
30 import java.util.List;
31
32 import org.apache.logging.log4j.core.LogEvent;
33 import org.apache.logging.log4j.core.appender.AppenderLoggingException;
34 import org.apache.logging.log4j.core.appender.ManagerFactory;
35 import org.apache.logging.log4j.core.appender.db.AbstractDatabaseManager;
36 import org.apache.logging.log4j.core.appender.db.ColumnMapping;
37 import org.apache.logging.log4j.core.config.plugins.convert.DateTypeConverter;
38 import org.apache.logging.log4j.core.config.plugins.convert.TypeConverters;
39 import org.apache.logging.log4j.core.util.Closer;
40 import org.apache.logging.log4j.spi.ThreadContextMap;
41 import org.apache.logging.log4j.spi.ThreadContextStack;
42 import org.apache.logging.log4j.util.ReadOnlyStringMap;
43 import org.apache.logging.log4j.util.Strings;
44
45
46
47
48 public final class JdbcDatabaseManager extends AbstractDatabaseManager {
49
50 private static final JdbcDatabaseManagerFactory INSTANCE = new JdbcDatabaseManagerFactory();
51
52
53 private final List<ColumnMapping> columnMappings;
54 private final List<ColumnConfig> columnConfigs;
55 private final ConnectionSource connectionSource;
56 private final String sqlStatement;
57
58 private Connection connection;
59 private PreparedStatement statement;
60 private boolean isBatchSupported;
61
62 private JdbcDatabaseManager(final String name, final int bufferSize, final ConnectionSource connectionSource,
63 final String sqlStatement, final List<ColumnConfig> columnConfigs,
64 final List<ColumnMapping> columnMappings) {
65 super(name, bufferSize);
66 this.connectionSource = connectionSource;
67 this.sqlStatement = sqlStatement;
68 this.columnConfigs = columnConfigs;
69 this.columnMappings = columnMappings;
70 }
71
72 @Override
73 protected void startupInternal() throws Exception {
74 this.connection = this.connectionSource.getConnection();
75 final DatabaseMetaData metaData = this.connection.getMetaData();
76 this.isBatchSupported = metaData.supportsBatchUpdates();
77 Closer.closeSilently(this.connection);
78 }
79
80 @Override
81 protected boolean shutdownInternal() {
82 if (this.connection != null || this.statement != null) {
83 return this.commitAndClose();
84 }
85 return true;
86 }
87
88 @Override
89 protected void connectAndStart() {
90 try {
91 this.connection = this.connectionSource.getConnection();
92 this.connection.setAutoCommit(false);
93 this.statement = this.connection.prepareStatement(this.sqlStatement);
94 } catch (final SQLException e) {
95 throw new AppenderLoggingException(
96 "Cannot write logging event or flush buffer; JDBC manager cannot connect to the database.", e
97 );
98 }
99 }
100
101 @Override
102 protected void writeInternal(final LogEvent event) {
103 StringReader reader = null;
104 try {
105 if (!this.isRunning() || this.connection == null || this.connection.isClosed() || this.statement == null
106 || this.statement.isClosed()) {
107 throw new AppenderLoggingException(
108 "Cannot write logging event; JDBC manager not connected to the database.");
109 }
110
111 int i = 1;
112 for (final ColumnMapping mapping : this.columnMappings) {
113 if (ThreadContextMap.class.isAssignableFrom(mapping.getType())
114 || ReadOnlyStringMap.class.isAssignableFrom(mapping.getType())) {
115 this.statement.setObject(i++, event.getContextData().toMap());
116 } else if (ThreadContextStack.class.isAssignableFrom(mapping.getType())) {
117 this.statement.setObject(i++, event.getContextStack().asList());
118 } else if (Date.class.isAssignableFrom(mapping.getType())) {
119 this.statement.setObject(i++,
120 DateTypeConverter.fromMillis(event.getTimeMillis(), mapping.getType().asSubclass(Date.class)));
121 } else if (Clob.class.isAssignableFrom(mapping.getType())) {
122 this.statement.setClob(i++, new StringReader(mapping.getLayout().toSerializable(event)));
123 } else if (NClob.class.isAssignableFrom(mapping.getType())) {
124 this.statement.setNClob(i++, new StringReader(mapping.getLayout().toSerializable(event)));
125 } else {
126 final Object value = TypeConverters.convert(mapping.getLayout().toSerializable(event),
127 mapping.getType(), null);
128 if (value == null) {
129 this.statement.setNull(i++, Types.NULL);
130 } else {
131 this.statement.setObject(i++, value);
132 }
133 }
134 }
135 for (final ColumnConfig column : this.columnConfigs) {
136 if (column.isEventTimestamp()) {
137 this.statement.setTimestamp(i++, new Timestamp(event.getTimeMillis()));
138 } else if (column.isClob()) {
139 reader = new StringReader(column.getLayout().toSerializable(event));
140 if (column.isUnicode()) {
141 this.statement.setNClob(i++, reader);
142 } else {
143 this.statement.setClob(i++, reader);
144 }
145 } else if (column.isUnicode()) {
146 this.statement.setNString(i++, column.getLayout().toSerializable(event));
147 } else {
148 this.statement.setString(i++, column.getLayout().toSerializable(event));
149 }
150 }
151
152 if (this.isBatchSupported) {
153 this.statement.addBatch();
154 } else if (this.statement.executeUpdate() == 0) {
155 throw new AppenderLoggingException(
156 "No records inserted in database table for log event in JDBC manager.");
157 }
158 } catch (final SQLException e) {
159 throw new AppenderLoggingException("Failed to insert record for log event in JDBC manager: " +
160 e.getMessage(), e);
161 } finally {
162 Closer.closeSilently(reader);
163 }
164 }
165
166 @Override
167 protected boolean commitAndClose() {
168 boolean closed = true;
169 try {
170 if (this.connection != null && !this.connection.isClosed()) {
171 if (this.isBatchSupported) {
172 this.statement.executeBatch();
173 }
174 this.connection.commit();
175 }
176 } catch (final SQLException e) {
177 throw new AppenderLoggingException("Failed to commit transaction logging event or flushing buffer.", e);
178 } finally {
179 try {
180 Closer.close(this.statement);
181 } catch (final Exception e) {
182 logWarn("Failed to close SQL statement logging event or flushing buffer", e);
183 closed = false;
184 } finally {
185 this.statement = null;
186 }
187
188 try {
189 Closer.close(this.connection);
190 } catch (final Exception e) {
191 logWarn("Failed to close database connection logging event or flushing buffer", e);
192 closed = false;
193 } finally {
194 this.connection = null;
195 }
196 }
197 return closed;
198 }
199
200
201
202
203
204
205
206
207
208
209
210
211 @Deprecated
212 public static JdbcDatabaseManager getJDBCDatabaseManager(final String name, final int bufferSize,
213 final ConnectionSource connectionSource,
214 final String tableName,
215 final ColumnConfig[] columnConfigs) {
216
217 return getManager(name,
218 new FactoryData(bufferSize, connectionSource, tableName, columnConfigs, new ColumnMapping[0]),
219 getFactory());
220 }
221
222
223
224
225
226
227
228
229
230
231
232
233 public static JdbcDatabaseManager getManager(final String name,
234 final int bufferSize,
235 final ConnectionSource connectionSource,
236 final String tableName,
237 final ColumnConfig[] columnConfigs,
238 final ColumnMapping[] columnMappings) {
239 return getManager(name, new FactoryData(bufferSize, connectionSource, tableName, columnConfigs, columnMappings),
240 getFactory());
241 }
242
243 private static JdbcDatabaseManagerFactory getFactory() {
244 return INSTANCE;
245 }
246
247
248
249
250 private static final class FactoryData extends AbstractDatabaseManager.AbstractFactoryData {
251 private final ConnectionSource connectionSource;
252 private final String tableName;
253 private final ColumnConfig[] columnConfigs;
254 private final ColumnMapping[] columnMappings;
255
256 protected FactoryData(final int bufferSize, final ConnectionSource connectionSource, final String tableName,
257 final ColumnConfig[] columnConfigs, final ColumnMapping[] columnMappings) {
258 super(bufferSize);
259 this.connectionSource = connectionSource;
260 this.tableName = tableName;
261 this.columnConfigs = columnConfigs;
262 this.columnMappings = columnMappings;
263 }
264 }
265
266
267
268
269 private static final class JdbcDatabaseManagerFactory implements ManagerFactory<JdbcDatabaseManager, FactoryData> {
270 @Override
271 public JdbcDatabaseManager createManager(final String name, final FactoryData data) {
272 final StringBuilder sb = new StringBuilder("INSERT INTO ").append(data.tableName).append(" (");
273
274
275 for (final ColumnMapping mapping : data.columnMappings) {
276 sb.append(mapping.getName()).append(',');
277 }
278 for (final ColumnConfig config : data.columnConfigs) {
279 sb.append(config.getColumnName()).append(',');
280 }
281
282 sb.setCharAt(sb.length() - 1, ')');
283 sb.append(" VALUES (");
284 final List<ColumnMapping> columnMappings = new ArrayList<>(data.columnMappings.length);
285 for (final ColumnMapping mapping : data.columnMappings) {
286 if (Strings.isNotEmpty(mapping.getLiteralValue())) {
287 sb.append(mapping.getLiteralValue());
288 } else {
289 sb.append('?');
290 columnMappings.add(mapping);
291 }
292 sb.append(',');
293 }
294 final List<ColumnConfig> columnConfigs = new ArrayList<>(data.columnConfigs.length);
295 for (final ColumnConfig config : data.columnConfigs) {
296 if (Strings.isNotEmpty(config.getLiteralValue())) {
297 sb.append(config.getLiteralValue());
298 } else {
299 sb.append('?');
300 columnConfigs.add(config);
301 }
302 sb.append(',');
303 }
304
305 sb.setCharAt(sb.length() - 1, ')');
306 final String sqlStatement = sb.toString();
307
308 return new JdbcDatabaseManager(name, data.getBufferSize(), data.connectionSource, sqlStatement,
309 columnConfigs, columnMappings);
310 }
311 }
312
313 }