1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.log4j.jdbc;
18
19 import org.apache.log4j.spi.*;
20 import org.apache.log4j.PatternLayout;
21
22 import java.util.ArrayList;
23 import java.util.Iterator;
24
25 import java.sql.DriverManager;
26 import java.sql.Connection;
27 import java.sql.Statement;
28 import java.sql.SQLException;
29
30
31 /***
32 <p><b><font color="#FF2222">WARNING: This version of JDBCAppender
33 is very likely to be completely replaced in the future. Moreoever,
34 it does not log exceptions</font></b>.
35
36 The JDBCAppender provides for sending log events to a database.
37
38
39 <p>Each append call adds to an <code>ArrayList</code> buffer. When
40 the buffer is filled each log event is placed in a sql statement
41 (configurable) and executed.
42
43 <b>BufferSize</b>, <b>db URL</b>, <b>User</b>, & <b>Password</b> are
44 configurable options in the standard log4j ways.
45
46 <p>The <code>setSql(String sql)</code> sets the SQL statement to be
47 used for logging -- this statement is sent to a
48 <code>PatternLayout</code> (either created automaticly by the
49 appender or added by the user). Therefore by default all the
50 conversion patterns in <code>PatternLayout</code> can be used
51 inside of the statement. (see the test cases for examples)
52
53 <p>Overriding the {@link #getLogStatement} method allows more
54 explicit control of the statement used for logging.
55
56 <p>For use as a base class:
57
58 <ul>
59
60 <li>Override <code>getConnection()</code> to pass any connection
61 you want. Typically this is used to enable application wide
62 connection pooling.
63
64 <li>Override <code>closeConnection(Connection con)</code> -- if
65 you override getConnection make sure to implement
66 <code>closeConnection</code> to handle the connection you
67 generated. Typically this would return the connection to the
68 pool it came from.
69
70 <li>Override <code>getLogStatement(LoggingEvent event)</code> to
71 produce specialized or dynamic statements. The default uses the
72 sql option value.
73
74 </ul>
75
76 @author Kevin Steppe (<A HREF="mailto:ksteppe@pacbell.net">ksteppe@pacbell.net</A>)
77
78 */
79 public class JDBCAppender extends org.apache.log4j.AppenderSkeleton
80 implements org.apache.log4j.Appender {
81
82 /***
83 * URL of the DB for default connection handling
84 */
85 protected String databaseURL = "jdbc:odbc:myDB";
86
87 /***
88 * User to connect as for default connection handling
89 */
90 protected String databaseUser = "me";
91
92 /***
93 * User to use for default connection handling
94 */
95 protected String databasePassword = "mypassword";
96
97 /***
98 * Connection used by default. The connection is opened the first time it
99 * is needed and then held open until the appender is closed (usually at
100 * garbage collection). This behavior is best modified by creating a
101 * sub-class and overriding the <code>getConnection</code> and
102 * <code>closeConnection</code> methods.
103 */
104 protected Connection connection = null;
105
106 /***
107 * Stores the string given to the pattern layout for conversion into a SQL
108 * statement, eg: insert into LogTable (Thread, Class, Message) values
109 * ("%t", "%c", "%m").
110 *
111 * Be careful of quotes in your messages!
112 *
113 * Also see PatternLayout.
114 */
115 protected String sqlStatement = "";
116
117 /***
118 * size of LoggingEvent buffer before writting to the database.
119 * Default is 1.
120 */
121 protected int bufferSize = 1;
122
123 /***
124 * ArrayList holding the buffer of Logging Events.
125 */
126 protected ArrayList buffer;
127
128 /***
129 * Helper object for clearing out the buffer
130 */
131 protected ArrayList removes;
132
133 public JDBCAppender() {
134 super();
135 buffer = new ArrayList(bufferSize);
136 removes = new ArrayList(bufferSize);
137 }
138
139 /***
140 * Adds the event to the buffer. When full the buffer is flushed.
141 */
142 public void append(LoggingEvent event) {
143 buffer.add(event);
144
145 if (buffer.size() >= bufferSize)
146 flushBuffer();
147 }
148
149 /***
150 * By default getLogStatement sends the event to the required Layout object.
151 * The layout will format the given pattern into a workable SQL string.
152 *
153 * Overriding this provides direct access to the LoggingEvent
154 * when constructing the logging statement.
155 *
156 */
157 protected String getLogStatement(LoggingEvent event) {
158 return getLayout().format(event);
159 }
160
161 /***
162 *
163 * Override this to provide an alertnate method of getting
164 * connections (such as caching). One method to fix this is to open
165 * connections at the start of flushBuffer() and close them at the
166 * end. I use a connection pool outside of JDBCAppender which is
167 * accessed in an override of this method.
168 * */
169 protected void execute(String sql) throws SQLException {
170
171 Connection con = null;
172 Statement stmt = null;
173
174 try {
175 con = getConnection();
176
177 stmt = con.createStatement();
178 stmt.executeUpdate(sql);
179 } catch (SQLException e) {
180 if (stmt != null)
181 stmt.close();
182 throw e;
183 }
184 stmt.close();
185 closeConnection(con);
186
187
188 }
189
190
191 /***
192 * Override this to return the connection to a pool, or to clean up the
193 * resource.
194 *
195 * The default behavior holds a single connection open until the appender
196 * is closed (typically when garbage collected).
197 */
198 protected void closeConnection(Connection con) {
199 }
200
201 /***
202 * Override this to link with your connection pooling system.
203 *
204 * By default this creates a single connection which is held open
205 * until the object is garbage collected.
206 */
207 protected Connection getConnection() throws SQLException {
208 if (!DriverManager.getDrivers().hasMoreElements())
209 setDriver("sun.jdbc.odbc.JdbcOdbcDriver");
210
211 if (connection == null) {
212 connection = DriverManager.getConnection(databaseURL, databaseUser,
213 databasePassword);
214 }
215
216 return connection;
217 }
218
219 /***
220 * Closes the appender, flushing the buffer first then closing the default
221 * connection if it is open.
222 */
223 public void close()
224 {
225 flushBuffer();
226
227 try {
228 if (connection != null && !connection.isClosed())
229 connection.close();
230 } catch (SQLException e) {
231 errorHandler.error("Error closing connection", e, ErrorCode.GENERIC_FAILURE);
232 }
233 this.closed = true;
234 }
235
236 /***
237 * loops through the buffer of LoggingEvents, gets a
238 * sql string from getLogStatement() and sends it to execute().
239 * Errors are sent to the errorHandler.
240 *
241 * If a statement fails the LoggingEvent stays in the buffer!
242 */
243 public void flushBuffer() {
244
245 removes.ensureCapacity(buffer.size());
246 for (Iterator i = buffer.iterator(); i.hasNext();) {
247 try {
248 LoggingEvent logEvent = (LoggingEvent)i.next();
249 String sql = getLogStatement(logEvent);
250 execute(sql);
251 removes.add(logEvent);
252 }
253 catch (SQLException e) {
254 errorHandler.error("Failed to excute sql", e,
255 ErrorCode.FLUSH_FAILURE);
256 }
257 }
258
259
260 buffer.removeAll(removes);
261
262
263 removes.clear();
264 }
265
266
267 /*** closes the appender before disposal */
268 public void finalize() {
269 close();
270 }
271
272
273 /***
274 * JDBCAppender requires a layout.
275 * */
276 public boolean requiresLayout() {
277 return true;
278 }
279
280
281 /***
282 *
283 */
284 public void setSql(String s) {
285 sqlStatement = s;
286 if (getLayout() == null) {
287 this.setLayout(new PatternLayout(s));
288 }
289 else {
290 ((PatternLayout)getLayout()).setConversionPattern(s);
291 }
292 }
293
294
295 /***
296 * Returns pre-formated statement eg: insert into LogTable (msg) values ("%m")
297 */
298 public String getSql() {
299 return sqlStatement;
300 }
301
302
303 public void setUser(String user) {
304 databaseUser = user;
305 }
306
307
308 public void setURL(String url) {
309 databaseURL = url;
310 }
311
312
313 public void setPassword(String password) {
314 databasePassword = password;
315 }
316
317
318 public void setBufferSize(int newBufferSize) {
319 bufferSize = newBufferSize;
320 buffer.ensureCapacity(bufferSize);
321 removes.ensureCapacity(bufferSize);
322 }
323
324
325 public String getUser() {
326 return databaseUser;
327 }
328
329
330 public String getURL() {
331 return databaseURL;
332 }
333
334
335 public String getPassword() {
336 return databasePassword;
337 }
338
339
340 public int getBufferSize() {
341 return bufferSize;
342 }
343
344
345 /***
346 * Ensures that the given driver class has been loaded for sql connection
347 * creation.
348 */
349 public void setDriver(String driverClass) {
350 try {
351 Class.forName(driverClass);
352 } catch (Exception e) {
353 errorHandler.error("Failed to load driver", e,
354 ErrorCode.GENERIC_FAILURE);
355 }
356 }
357 }
358