View Javadoc

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