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 (final 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.closeSilently(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 (final SQLException e) {
131 throw new AppenderLoggingException("Failed to commit transaction logging event or flushing buffer.", e);
132 } finally {
133 try {
134 Closer.close(this.statement);
135 } catch (final Exception e) {
136 LOGGER.warn("Failed to close SQL statement logging event or flushing buffer.", e);
137 } finally {
138 this.statement = null;
139 }
140
141 try {
142 Closer.close(this.connection);
143 } catch (final Exception e) {
144 LOGGER.warn("Failed to close database connection logging event or flushing buffer.", e);
145 } finally {
146 this.connection = null;
147 }
148 }
149 }
150
151
152
153
154
155
156
157
158
159
160
161 public static JdbcDatabaseManager getJDBCDatabaseManager(final String name, final int bufferSize,
162 final ConnectionSource connectionSource,
163 final String tableName,
164 final ColumnConfig[] columnConfigs) {
165
166 return AbstractDatabaseManager.getManager(
167 name, new FactoryData(bufferSize, connectionSource, tableName, columnConfigs), FACTORY
168 );
169 }
170
171
172
173
174 private static final class FactoryData extends AbstractDatabaseManager.AbstractFactoryData {
175 private final ColumnConfig[] columnConfigs;
176 private final ConnectionSource connectionSource;
177 private final String tableName;
178
179 protected FactoryData(final int bufferSize, final ConnectionSource connectionSource, final String tableName,
180 final ColumnConfig[] columnConfigs) {
181 super(bufferSize);
182 this.connectionSource = connectionSource;
183 this.tableName = tableName;
184 this.columnConfigs = columnConfigs;
185 }
186 }
187
188
189
190
191 private static final class JDBCDatabaseManagerFactory implements ManagerFactory<JdbcDatabaseManager, FactoryData> {
192 @Override
193 public JdbcDatabaseManager createManager(final String name, final FactoryData data) {
194 final StringBuilder columnPart = new StringBuilder();
195 final StringBuilder valuePart = new StringBuilder();
196 final List<Column> columns = new ArrayList<Column>();
197 int i = 0;
198 for (final ColumnConfig config : data.columnConfigs) {
199 if (i++ > 0) {
200 columnPart.append(',');
201 valuePart.append(',');
202 }
203
204 columnPart.append(config.getColumnName());
205
206 if (config.getLiteralValue() != null) {
207 valuePart.append(config.getLiteralValue());
208 } else {
209 columns.add(new Column(
210 config.getLayout(), config.isEventTimestamp(), config.isUnicode(), config.isClob()
211 ));
212 valuePart.append('?');
213 }
214 }
215
216 final String sqlStatement = "INSERT INTO " + data.tableName + " (" + columnPart + ") VALUES (" +
217 valuePart + ')';
218
219 return new JdbcDatabaseManager(name, data.getBufferSize(), data.connectionSource, sqlStatement, columns);
220 }
221 }
222
223
224
225
226 private static final class Column {
227 private final PatternLayout layout;
228 private final boolean isEventTimestamp;
229 private final boolean isUnicode;
230 private final boolean isClob;
231
232 private Column(final PatternLayout layout, final boolean isEventDate, final boolean isUnicode,
233 final boolean isClob) {
234 this.layout = layout;
235 this.isEventTimestamp = isEventDate;
236 this.isUnicode = isUnicode;
237 this.isClob = isClob;
238 }
239 }
240 }