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.Connection;
21 import java.sql.DatabaseMetaData;
22 import java.sql.PreparedStatement;
23 import java.sql.SQLException;
24 import java.sql.Timestamp;
25 import java.util.ArrayList;
26 import java.util.List;
27
28 import org.apache.logging.log4j.core.LogEvent;
29 import org.apache.logging.log4j.core.appender.AppenderLoggingException;
30 import org.apache.logging.log4j.core.appender.ManagerFactory;
31 import org.apache.logging.log4j.core.appender.db.AbstractDatabaseManager;
32 import org.apache.logging.log4j.core.layout.PatternLayout;
33 import org.apache.logging.log4j.core.util.Closer;
34
35
36
37
38 public final class JdbcDatabaseManager extends AbstractDatabaseManager {
39
40 private static final JdbcDatabaseManagerFactory INSTANCE = new JdbcDatabaseManagerFactory();
41
42 private final List<Column> columns;
43 private final ConnectionSource connectionSource;
44 private final String sqlStatement;
45
46 private Connection connection;
47 private PreparedStatement statement;
48 private boolean isBatchSupported;
49
50 private JdbcDatabaseManager(final String name, final int bufferSize, final ConnectionSource connectionSource,
51 final String sqlStatement, final List<Column> columns) {
52 super(name, bufferSize);
53 this.connectionSource = connectionSource;
54 this.sqlStatement = sqlStatement;
55 this.columns = columns;
56 }
57
58 @Override
59 protected void startupInternal() throws Exception {
60 this.connection = this.connectionSource.getConnection();
61 final DatabaseMetaData metaData = this.connection.getMetaData();
62 this.isBatchSupported = metaData.supportsBatchUpdates();
63 Closer.closeSilently(this.connection);
64 }
65
66 @Override
67 protected void shutdownInternal() {
68 if (this.connection != null || this.statement != null) {
69 this.commitAndClose();
70 }
71 }
72
73 @Override
74 protected void connectAndStart() {
75 try {
76 this.connection = this.connectionSource.getConnection();
77 this.connection.setAutoCommit(false);
78 this.statement = this.connection.prepareStatement(this.sqlStatement);
79 } catch (final SQLException e) {
80 throw new AppenderLoggingException(
81 "Cannot write logging event or flush buffer; JDBC manager cannot connect to the database.", e
82 );
83 }
84 }
85
86 @Override
87 protected void writeInternal(final LogEvent event) {
88 StringReader reader = null;
89 try {
90 if (!this.isRunning() || this.connection == null || this.connection.isClosed() || this.statement == null
91 || this.statement.isClosed()) {
92 throw new AppenderLoggingException(
93 "Cannot write logging event; JDBC manager not connected to the database.");
94 }
95
96 int i = 1;
97 for (final Column column : this.columns) {
98 if (column.isEventTimestamp) {
99 this.statement.setTimestamp(i++, new Timestamp(event.getTimeMillis()));
100 } else {
101 if (column.isClob) {
102 reader = new StringReader(column.layout.toSerializable(event));
103 if (column.isUnicode) {
104 this.statement.setNClob(i++, reader);
105 } else {
106 this.statement.setClob(i++, reader);
107 }
108 } else {
109 if (column.isUnicode) {
110 this.statement.setNString(i++, column.layout.toSerializable(event));
111 } else {
112 this.statement.setString(i++, column.layout.toSerializable(event));
113 }
114 }
115 }
116 }
117
118 if (this.isBatchSupported) {
119 this.statement.addBatch();
120 } else if (this.statement.executeUpdate() == 0) {
121 throw new AppenderLoggingException(
122 "No records inserted in database table for log event in JDBC manager.");
123 }
124 } catch (final SQLException e) {
125 throw new AppenderLoggingException("Failed to insert record for log event in JDBC manager: " +
126 e.getMessage(), e);
127 } finally {
128 Closer.closeSilently(reader);
129 }
130 }
131
132 @Override
133 protected void commitAndClose() {
134 try {
135 if (this.connection != null && !this.connection.isClosed()) {
136 if (this.isBatchSupported) {
137 this.statement.executeBatch();
138 }
139 this.connection.commit();
140 }
141 } catch (final SQLException e) {
142 throw new AppenderLoggingException("Failed to commit transaction logging event or flushing buffer.", e);
143 } finally {
144 try {
145 Closer.close(this.statement);
146 } catch (final Exception e) {
147 LOGGER.warn("Failed to close SQL statement logging event or flushing buffer.", e);
148 } finally {
149 this.statement = null;
150 }
151
152 try {
153 Closer.close(this.connection);
154 } catch (final Exception e) {
155 LOGGER.warn("Failed to close database connection logging event or flushing buffer.", e);
156 } finally {
157 this.connection = null;
158 }
159 }
160 }
161
162
163
164
165
166
167
168
169
170
171
172 public static JdbcDatabaseManager getJDBCDatabaseManager(final String name, final int bufferSize,
173 final ConnectionSource connectionSource,
174 final String tableName,
175 final ColumnConfig[] columnConfigs) {
176
177 return AbstractDatabaseManager.getManager(
178 name, new FactoryData(bufferSize, connectionSource, tableName, columnConfigs), getFactory()
179 );
180 }
181
182 private static JdbcDatabaseManagerFactory getFactory() {
183 return INSTANCE;
184 }
185
186
187
188
189 private static final class FactoryData extends AbstractDatabaseManager.AbstractFactoryData {
190 private final ColumnConfig[] columnConfigs;
191 private final ConnectionSource connectionSource;
192 private final String tableName;
193
194 protected FactoryData(final int bufferSize, final ConnectionSource connectionSource, final String tableName,
195 final ColumnConfig[] columnConfigs) {
196 super(bufferSize);
197 this.connectionSource = connectionSource;
198 this.tableName = tableName;
199 this.columnConfigs = columnConfigs;
200 }
201 }
202
203
204
205
206 private static final class JdbcDatabaseManagerFactory implements ManagerFactory<JdbcDatabaseManager, FactoryData> {
207 @Override
208 public JdbcDatabaseManager createManager(final String name, final FactoryData data) {
209 final StringBuilder columnPart = new StringBuilder();
210 final StringBuilder valuePart = new StringBuilder();
211 final List<Column> columns = new ArrayList<Column>();
212 int i = 0;
213 for (final ColumnConfig config : data.columnConfigs) {
214 if (i++ > 0) {
215 columnPart.append(',');
216 valuePart.append(',');
217 }
218
219 columnPart.append(config.getColumnName());
220
221 if (config.getLiteralValue() != null) {
222 valuePart.append(config.getLiteralValue());
223 } else {
224 columns.add(new Column(
225 config.getLayout(), config.isEventTimestamp(), config.isUnicode(), config.isClob()
226 ));
227 valuePart.append('?');
228 }
229 }
230
231 final String sqlStatement = "INSERT INTO " + data.tableName + " (" + columnPart + ") VALUES (" +
232 valuePart + ')';
233
234 return new JdbcDatabaseManager(name, data.getBufferSize(), data.connectionSource, sqlStatement, columns);
235 }
236 }
237
238
239
240
241 private static final class Column {
242 private final PatternLayout layout;
243 private final boolean isEventTimestamp;
244 private final boolean isUnicode;
245 private final boolean isClob;
246
247 private Column(final PatternLayout layout, final boolean isEventDate, final boolean isUnicode,
248 final boolean isClob) {
249 this.layout = layout;
250 this.isEventTimestamp = isEventDate;
251 this.isUnicode = isUnicode;
252 this.isClob = isClob;
253 }
254 }
255 }