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