KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > protomatter > syslog > DatabaseLog


1 package com.protomatter.syslog;
2
3 /**
4  * {{{ The Protomatter Software License, Version 1.0
5  * derived from The Apache Software License, Version 1.1
6  *
7  * Copyright (c) 1998-2002 Nate Sammons. All rights reserved.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  *
13  * 1. Redistributions of source code must retain the above copyright
14  * notice, this list of conditions and the following disclaimer.
15  *
16  * 2. Redistributions in binary form must reproduce the above copyright
17  * notice, this list of conditions and the following disclaimer in
18  * the documentation and/or other materials provided with the
19  * distribution.
20  *
21  * 3. The end-user documentation included with the redistribution,
22  * if any, must include the following acknowledgment:
23  * "This product includes software developed for the
24  * Protomatter Software Project
25  * (http://protomatter.sourceforge.net/)."
26  * Alternately, this acknowledgment may appear in the software itself,
27  * if and wherever such third-party acknowledgments normally appear.
28  *
29  * 4. The names "Protomatter" and "Protomatter Software Project" must
30  * not be used to endorse or promote products derived from this
31  * software without prior written permission. For written
32  * permission, please contact support@protomatter.com.
33  *
34  * 5. Products derived from this software may not be called "Protomatter",
35  * nor may "Protomatter" appear in their name, without prior written
36  * permission of the Protomatter Software Project
37  * (support@protomatter.com).
38  *
39  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
40  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
41  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
42  * DISCLAIMED. IN NO EVENT SHALL THE PROTOMATTER SOFTWARE PROJECT OR
43  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
44  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
45  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
46  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
47  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
48  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
49  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
50  * SUCH DAMAGE. }}}
51  */

52
53 import java.io.*;
54 import java.net.*;
55 import java.sql.*;
56 import java.util.*;
57 import java.text.*;
58
59 import com.protomatter.util.*;
60
61 /**
62  * A logger that writes to a database. The tables it requires are:<P>
63  *
64  * <BLOCKQUOTE><PRE>
65  * CREATE TABLE SYSLOG_LEVEL
66  * (
67  * SYSLOG_LEVEL INT PRIMARY KEY,
68  * SYSLOG_LEVEL_NAME VARCHAR(8) NOT NULL
69  * )
70  *
71  * CREATE TABLE SYSLOG_CHANNEL
72  * (
73  * CHANNEL INT PRIMARY KEY,
74  * CHANNEL_NAME VARCHAR(128) NOT NULL
75  * )
76  *
77  * CREATE TABLE SYSLOG_LOG
78  * (
79  * LOG_TIME DATETIME NOT NULL,
80  * SYSLOG_LEVEL INT NOT NULL
81  * REFERENCES SYSLOG_LEVEL(SYSLOG_LEVEL),
82  * HOST VARCHAR(32) NOT NULL,
83  * LOGGER VARCHAR(64) NOT NULL,
84  * CHANNEL INT NOT NULL
85  * REFERENCES SYSLOG_CHANNEL(CHANNEL),
86  * MESSAGE VARCHAR(255) NOT NULL,
87  * DETAIL LONG NULL,
88  * THREAD_NAME VARCHAR(255) NULL
89  * )
90  * </PRE></BLOCKQUOTE><P>
91  *
92  * NOTE: To use this logger you must specify the "<tt>hostname</tt>"
93  * attribute for the <TT>&lt;Syslog&gt;</TT> tag in your configuration
94  * so that the hostname is correctly recorded.
95  * <P>
96  *
97  * Column widths are defined as follows:<P>
98  * <DL>
99  * <DT><TT>HOST</TT></DT>
100  * <DD><TT>getHostWidth()</TT> on format class.</DD><P>
101  *
102  * <DT><TT>LOGGER</TT></DT>
103  * <DD><TT>getClassWidth()</TT> on format class.</DD><P>
104  *
105  * <DT><TT>THREAD_NAME</TT></DT>
106  * <DD><TT>getThreadWidth()</TT> on format class.</DD><P>
107  *
108  * <DT><TT>MESSAGE</TT></DT>
109  * <DD><TT>getMessageWidth()</TT> on this class.</DD><P>
110  *
111  * <DT><TT>DETAIL</TT></DT>
112  * <DD><TT>getDetailWidth()</TT> on this class.</DD><P>
113  * </DL>
114  * <P>
115  *
116  * You may add a prefix to the names of each of the tables and specify
117  * that with the "<tt>jdbc.tablePrefix</tt>" property when initializing
118  * this logger. The default prefix is nothing.<P>
119  *
120  * You may have to change the type for <tt>SYSLOG_LOG.DETAIL</tt>
121  * to be something different for your database
122  * (<TT>VARCHAR2</TT>, <tt>LONG VARCHAR</tt> or <tt>LONG RAW</tt>, etc).
123  * It is written using an adapter class which implements
124  * the {@link DatabaseLogStatementAdapter DatabaseLogStatementAdapter}
125  * interface. Currently there are adapters for drivers which
126  * use the {@link StringDatabaseLogStatementAdapter setString()} ,
127  * {@link AsciiStreamDatabaseLogStatementAdapter setAsciiStream()}, or
128  * {@link CharacterStreamDatabaseLogStatementAdapter setCharacterStream()}
129  * methods.<P>
130  *
131  * The <tt>SYSLOG_LEVEL</tt> table needs to contain the following rows:<P>
132  *
133  * <UL>
134  * <TABLE BORDER=1 CELLPADDING=2 CELLSPACING=0>
135  * <TR CLASS="TableHeadingColor">
136  * <TD><B>SYSLOG_LEVEL</B></TD>
137  * <TD><B>SYSLOG_LEVEL_NAME</B></TD>
138  * </TR>
139  *
140  * <TR CLASS="TableRowColor">
141  * <TD VALIGN=TOP><tt>1</tt></TD>
142  * <TD VALIGN=TOP><TT>DEBUG</TT></TD>
143  * </TR>
144  *
145  * <TR CLASS="TableRowColor">
146  * <TD VALIGN=TOP><tt>2</tt></TD>
147  * <TD VALIGN=TOP><TT>INFO</TT></TD>
148  * </TR>
149  *
150  * <TR CLASS="TableRowColor">
151  * <TD VALIGN=TOP><tt>4</tt></TD>
152  * <TD VALIGN=TOP><TT>WARNING</TT></TD>
153  * </TR>
154  *
155  * <TR CLASS="TableRowColor">
156  * <TD VALIGN=TOP><tt>8</tt></TD>
157  * <TD VALIGN=TOP><TT>ERROR</TT></TD>
158  * </TR>
159  *
160  * <TR CLASS="TableRowColor">
161  * <TD VALIGN=TOP><tt>16</tt></TD>
162  * <TD VALIGN=TOP><TT>FATAL</TT></TD>
163  * </TR>
164  *
165  * </TABLE>
166  * </UL><P>
167  *
168  * The <tt>SYSLOG_CHANNEL</tt> table is populated automatically. Channel
169  * IDs start at 1 and go up from there.
170  *
171  * @see com.protomatter.syslog.xml.DatabaseLog_Helper XML configuration class
172  */

173 public class DatabaseLog
174 extends BasicLogger
175 {
176   private String JavaDoc driver = null;
177   private String JavaDoc url = null;
178   private Properties props = null;
179
180   private DatabaseLogStatementAdapter statementAdapter = null;
181
182   private int numRetries = 3;
183
184   private Connection conn = null;
185
186   private PreparedStatement stmt = null;
187
188   // lengths... so we can truncate text.
189
private int messageWidth = 255;
190   private int detailWidth = 4000;
191
192   private String JavaDoc tablePrefix = "";
193
194   // channels we know are listed in the SYSLOG_CHANNEL
195
// table. Key is channel name, value is an Integer.
196
private Hashtable knownChannels = new Hashtable();
197
198   private String JavaDoc INSERT_PREFIX = "INSERT INTO ";
199
200   private String JavaDoc INSERT_LOG_SQL = "SYSLOG_LOG (LOG_TIME, SYSLOG_LEVEL, HOST, LOGGER, CHANNEL, MESSAGE, DETAIL, THREAD_NAME) VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
201
202   private String JavaDoc INSERT_CHANNEL_SQL = "SYSLOG_CHANNEL (CHANNEL, CHANNEL_NAME) VALUES (?, ?)";
203
204   private String JavaDoc FIND_CHANNEL_SQL_PREFIX = "SELECT CHANNEL FROM ";
205   private String JavaDoc FIND_CHANNEL_SQL = "SYSLOG_CHANNEL WHERE CHANNEL_NAME = ?";
206
207   private String JavaDoc FIND_MAX_CHANNEL_SQL_PREFIX = "SELECT MAX(CHANNEL) FROM ";
208   private String JavaDoc FIND_MAX_CHANNEL_SQL = "SYSLOG_CHANNEL";
209
210
211    /**
212     * You will need to call the configure() method if you use this constructor.
213     */

214    public DatabaseLog()
215    {
216      super();
217
218      statementAdapter = new StringDatabaseLogStatementAdapter();
219    }
220
221    /**
222     * Close the database connection and cleanup.
223     */

224    public void shutdown()
225    {
226      try
227      {
228        this.conn.close();
229      }
230      catch (SQLException x)
231      {
232        // don't really care that there was an error.
233
}
234    }
235
236    /**
237     * Set the driver to use.
238     */

239    public void setDriver(String JavaDoc driver)
240    {
241       this.driver = driver;
242    }
243
244    /**
245     * Set the table prefix to use.
246     */

247    public void setTablePrefix(String JavaDoc tablePrefix)
248    {
249       this.tablePrefix = tablePrefix;
250    }
251
252    /**
253     * Set the JDBC URL.
254     */

255    public void setURL(String JavaDoc url)
256    {
257       this.url = url;
258    }
259
260    /**
261     * Set the number of retries before failure.
262     */

263    public void setNumRetries(int retries)
264    {
265      this.numRetries = retries;
266    }
267
268    /**
269     * Set the JDBC connection properties.
270     */

271    public void setProperties(Properties props)
272    {
273       this.props = props;
274    }
275
276    /**
277     * Set the database statement adapter.
278     */

279    public void setStatementAdapter(DatabaseLogStatementAdapter adapter)
280    {
281      this.statementAdapter = adapter;
282    }
283
284    /**
285     * Get the database statement adapter.
286     */

287    public DatabaseLogStatementAdapter getStatementAdapter()
288    {
289      return this.statementAdapter;
290    }
291
292    /**
293     * Get the classname of the driver.
294     */

295    public String JavaDoc getDriver()
296    {
297       return this.driver;
298    }
299
300    /**
301     * Get the table prefix.
302     */

303    public String JavaDoc getTablePrefix()
304    {
305       return this.tablePrefix;
306    }
307
308    /**
309     * Get the JDBC URL.
310     */

311    public String JavaDoc getURL()
312    {
313       return this.url;
314    }
315
316    /**
317     * Get the number of retries before failure.
318     */

319    public int getNumRetries()
320    {
321      return this.numRetries;
322    }
323
324    /**
325     * Get the JDBC connection properties.
326     */

327    public Properties getProperties()
328    {
329       return this.props;
330    }
331
332    /**
333     * Set the width of the short message column.
334     */

335    public void setMessageWidth(int width)
336    {
337      this.messageWidth = width;
338    }
339
340    /**
341     * Get the width of the short message column.
342     */

343    public int getMessageWidth()
344    {
345      return this.messageWidth;
346    }
347
348    /**
349     * Set the width of the message detail column.
350     */

351    public void setDetailWidth(int width)
352    {
353      this.detailWidth = width;
354    }
355
356    /**
357     * Get the width of the message detail column.
358     */

359    public int getDetailWidth()
360    {
361      return this.detailWidth;
362    }
363
364    /**
365     * (re)initialize the connection to the database.
366     */

367    public void initDatabase()
368    throws Exception JavaDoc
369    {
370      if (stmt != null)
371      {
372        try { stmt.close(); } catch (Exception JavaDoc x) { ; }
373      }
374      if (conn != null)
375      {
376        try { conn.close(); } catch (Exception JavaDoc x) { ; }
377      }
378
379      // make sure the driver is registered.
380
DatabaseUtil.registerDriver(driver);
381
382      // get a connection
383
conn = DriverManager.getConnection(url, props);
384
385      // prepare our insert statement.
386
stmt = conn.prepareStatement(INSERT_PREFIX + tablePrefix + INSERT_LOG_SQL);
387    }
388
389    private synchronized int ensureChannelExists(String JavaDoc channel, Connection c)
390    throws Exception JavaDoc
391    {
392      SimpleSyslogTextFormatter format = (SimpleSyslogTextFormatter)getTextFormatter();
393
394      String JavaDoc channelName = truncate(channel, format.getChannelWidth());
395
396      Integer JavaDoc id = (Integer JavaDoc)knownChannels.get(channelName);
397      if (id != null)
398        return id.intValue();
399
400      // see if it's been put in before.
401
PreparedStatement s = null;
402      ResultSet r = null;
403
404      try
405      {
406        s = c.prepareStatement(FIND_CHANNEL_SQL_PREFIX + tablePrefix + FIND_CHANNEL_SQL);
407        s.setString(1, channelName);
408        r = s.executeQuery();
409        if (r.next())
410        {
411          // channel exists, use that ID.
412
id = new Integer JavaDoc(r.getInt(1));
413          knownChannels.put(channelName, id);
414          return id.intValue();
415        }
416
417        // we'll need to insert the channel
418
r.close();
419        s.close();
420
421        // find max channel id
422
s = c.prepareStatement(FIND_MAX_CHANNEL_SQL_PREFIX + tablePrefix + FIND_MAX_CHANNEL_SQL);
423        r = s.executeQuery();
424        int max = 0;
425        if (r.next())
426          max = r.getInt(1);
427        r.close();
428        s.close();
429
430        // insert new channel.
431
s = c.prepareStatement(INSERT_PREFIX + tablePrefix + INSERT_CHANNEL_SQL);
432        s.setInt(1, max +1);
433        s.setString(2, channelName);
434        s.executeUpdate();
435
436        knownChannels.put(channelName, new Integer JavaDoc(max +1));
437        return (max +1);
438      }
439      finally
440      {
441        // close up shop
442
if (r != null)
443        {
444          try { r.close(); } catch (Exception JavaDoc x) { ; }
445        }
446        if (s != null)
447        {
448          try { s.close(); } catch (Exception JavaDoc x) { ; }
449        }
450      }
451    }
452
453    public final void log(SyslogMessage message)
454    {
455      log(message, 1, null);
456    }
457
458    private final void log(SyslogMessage message, int tries, Exception JavaDoc exception)
459    {
460      SimpleSyslogTextFormatter format = (SimpleSyslogTextFormatter)getTextFormatter();
461      if (tries > numRetries)
462      {
463        // try a few times and then just give up.
464
System.err.println("############################################################");
465        System.err.println(MessageFormat.format(
466          Syslog.getResourceString(MessageConstants.DATABASELOG_ATTEMPT_MESSAGE_1),
467          new Object JavaDoc[] { "DatabaseLog", String.valueOf(numRetries) }));
468        System.err.println(MessageFormat.format(
469          Syslog.getResourceString(MessageConstants.DATABASELOG_ATTEMPT_MESSAGE_2),
470          new Object JavaDoc[] { getName() }));
471        System.err.println(MessageFormat.format(
472          Syslog.getResourceString(MessageConstants.DATABASELOG_ATTEMPT_MESSAGE_3),
473          new Object JavaDoc[] { driver }));
474        System.err.println(MessageFormat.format(
475          Syslog.getResourceString(MessageConstants.DATABASELOG_ATTEMPT_MESSAGE_4),
476          new Object JavaDoc[] { url }));
477        System.err.println(MessageFormat.format(
478          Syslog.getResourceString(MessageConstants.DATABASELOG_ATTEMPT_MESSAGE_5),
479          new Object JavaDoc[] { props }));
480        if (exception != null)
481        {
482          System.err.println("");
483          System.err.println(Syslog.getResourceString(MessageConstants.DATABASELOG_ATTEMPT_CAUSE_MESSAGE));
484          exception.printStackTrace();
485        }
486        System.err.println("");
487        System.err.println(Syslog.getResourceString(MessageConstants.DATABASELOG_ORIGINAL_MESSAGE));
488        StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
489        format.formatLogEntry(buf, message);
490        System.err.println(buf);
491        System.err.println("############################################################");
492        return;
493      }
494      try
495      {
496        // if our connection is null, statement is null,
497
// or we're trying more than once to log this message,
498
// re-initialize the database.
499
if (conn == null || stmt == null || tries > 1)
500          initDatabase();
501
502        int chanID = ensureChannelExists(message.channel, conn);
503        String JavaDoc hostname = getHostname(message.host);
504
505        stmt.setTimestamp(1, new Timestamp(message.time));
506
507        stmt.setInt(2, message.level);
508
509        stmt.setString(3, truncate(hostname, format.getHostWidth()));
510
511        stmt.setString(4, truncate(message.loggerClassname, format.getClassWidth()));
512
513        stmt.setInt(5, chanID);
514
515        stmt.setString(6, truncate(message.msg.toString(), messageWidth));
516
517        if (message.detail == null)
518        {
519          stmt.setNull(7, Types.LONGVARCHAR);
520        }
521        else
522        {
523          StringBuffer JavaDoc buffer = new StringBuffer JavaDoc(detailWidth);
524          format.formatMessageDetail(buffer, message);
525          String JavaDoc messageDetail = truncate(buffer.toString(), detailWidth);
526          statementAdapter.handleLogStatement(stmt, messageDetail, 7);
527        }
528
529        stmt.setString(8, truncate(message.threadName, format.getThreadWidth()));
530
531        stmt.executeUpdate();
532      }
533      catch (Exception JavaDoc x)
534      {
535        log(message, tries +1, x);
536      }
537    }
538
539    private static final String JavaDoc getHostname(InetAddress host)
540    {
541      return host.getHostName();
542    }
543
544    private static final String JavaDoc truncate(String JavaDoc s, int length)
545    {
546      return (s.length() > length) ? s.substring(0, length) : s;
547    }
548
549   /**
550    * Set the log formatter object used by this logger. DatabaseLog
551    * makes the extra requirement that this formatter be a subclass
552    * of the <TT>SimpleSyslogTextFormatter</TT> class.
553    */

554   public void setTextFormatter(SyslogTextFormatter formatter)
555   {
556     if (!(formatter instanceof SimpleSyslogTextFormatter))
557     {
558       throw new IllegalArgumentException JavaDoc(MessageFormat.format(
559         Syslog.getResourceString(MessageConstants.DATABASELOG_NO_TEXT_FORMATTER_MESSAGE),
560         new Object JavaDoc[] { "DatabaseLog", "SimpleSyslogTextFormatter" }));
561     }
562     super.setTextFormatter(formatter);
563   }
564
565    public void flush()
566    {
567      // do nothing.
568
}
569 }
570
Popular Tags