|
SYS-CON.TV Webcasts
Comments
Did you read today's front page stories & breaking news?
SYS-CON.TV
|
Top Links You Must Click On
General Java Extending the J2SE 1.4 Logging Package - Monitoring made easy
Extending the J2SE 1.4 Logging Package - Monitoring made easy
By: Jim Mangione
Mar. 1, 2002 12:00 AM
The most basic way to capture these elements of interest is through application logs. Most Java-based production systems have them in some form, and most of them probably implement a custom API or use one of a handful of third-party packages that may or may not be cross-compatible. Out comes java.util.logging in the new Java 2 Platform, Standard Edition (J2SE) v1.4. Developed collaboratively with input from several key contributors (see “JSRs: Java Specification Requests” at http://jcp.org/jsr/detail/47.jsp for details), this package can be used as is, extended for additional functionality, and in conjunction with enterprise application services. How does it work out of the box? What are its limitations and how easy is it to extend its capabilities? I’ll discuss these issues, plus show how to add database-level logging to the package’s framework. J2SE Logging: Out of the Box Here’s how it works: the package provides an API for producing a LogRecord. This record is what the logs are populated with; it contains such properties as the datetime, log level (there are seven currently, from FINEST all the way to SEVERE), and, of course, the message itself. Formatting and output for log records are done through Formatter and Handler classes. Formatters determine which format the LogRecord will be in, currently either plaintext or XML (the default). Handlers define how the logs are exported (through a file, a socket, to the console, or in-memory). To set up a Handler class to point to a file called “mylog.xml” that writes each LogRecord as an XML message, you’d use the FileHandler class and set its formatter to an instance of an XMLFormatter, as follows: FileHandler flhandler = new FileHandler( "mylog.xml" ); To integrate logging into an application you must obtain a static instance of the Logger class. Through this class a user will define the output type, specify a log format, and publish the individual log records. To publish an informational level message in the “mylog.xml” file, you first need to retrieve a Logger for your subsystem (commonly the class itself), then attach the handler to it (in our case it’s the FileHandler). Now you’re ready to generate messages. private static Logger logger = For the logger.info() method, you can also use the more generic logger.log(), which takes Level as a param, as well as the message: logger.log(Level.INFO, "TestDriver(): The code above will produce an XML <record> message with each field in the LogRecord shown as a separate tag: <record> A complete example of how logging is added to an application appears in Listing 1. (Listings 1–5 can be downloaded from www.sys-con.com/java/sourcec.cfm.) What isn’t obvious in this example is the presence of a LogManager. This class contains default configurations for loggers and their supporting classes (read in from a logging.properties file), and is a single global entity shared by each logger within the application. It also maintains a list of global handlers to which, by default, each logger sends its output. Extending Its Capabilities First, it’s important to understand the class diagram, which shows what we’ll be extending. Referring to Figure 1, you’ll notice an abstract formatter and handler. The formatter decides only how the LogRecord will look. It works on the individual properties of the LogRecord and creates a single string (usually) to be returned that reflects a complete record in a particular format. The handler, which calls the formatter for a LogRecord, worries only about what to do with the outcome of that format’s record when returned. Take the common FileHandler, for instance. This class specifies the filename to which logs are written, then for each incoming LogRecord calls the default XMLFormatter to retrieve the entire record as an XML document and write it out to the log. With the ease of this framework, it’s only a small change to switch from outputting the XML document from a file to the console, or using a plaintext representation instead of XML. Database Handler and Customized Formatters As shown in Figure 1, the three new classes are DBHandler, TblXMLFormatter, and TblFieldFormatter. How do they work together? The user instantiates a DBHandler and passes the JDBC driver being used and the appropriate connect string. In this case we’re using MySQL: DBHandler dbhandler = new DBHandler("org.gjt.mm.mysql.Driver", Then, instead of using the XmlFormatter, a new TblXmlFormatter is instantiated and attached to the DBHandler. This will return column names and LogRecord values in a format suitable for inclusion in a database transaction. TblXMLFormatter tblxmlfrmtr = new
TblXMLFormatter(); The handler will also write a default table name, which can be changed. The schema of this table depends on which formatter is used. If the user wants the log to consist of XML documents, the TblXmlFormatter is used; this will populate a single column with the entire log (see Figure 2, Logtable). If individual columns have to be populated, the TblFieldFormatter will do the job (see Figure 2, logtable_detail). This class inserts each field of the LogRecord into a separate column and can also control whether a field is written to the table. Both formatters allow for changing the column names of the table, just as the handler allows for changing the table name. This provides flexibility in the location the logs are written to and allows integration into an existing enterprise schema, if required. DBHandler For the DBHandler we’ll inherit directly from the Handler base class. This requires minimally implementing the publish(), close(), and flush() methods, as well as a constructor that will set up our connection. The first step will be designing that constructor. Since we want this class to be database-independent, we’ll need to pass in the JDBC driver that will be used, plus the connect string, including username and password. Some standard configuration activities are required next, such as setting the LogManager and default Level and Formatting. Then the database-specific code is executed, which involves setting class-level variables to the driver and URL, and establishing and holding a connection in another variable. public DBHandler( String driver, String url ) This connection will be used for each transaction performed by the publish() method, where the bulk of the work is done. This method is responsible for dynamically creating an insert statement from the formatter, executing the transaction, and catching any SQLExceptions. To do this, it expects the formatter to return both the table column names and the log values. The column names are a static string obtained by the header portion of the formatter (to be discussed in the next section). The log values are obtained by passing in the LogRecord and getting back a single string containing each column’s value. The logic looks like this: public void publish( LogRecord record ) { Why not pass the entire insert statement back from the format() method? That could be done, but it requires the formatter to know the name of the table. This would break the rules of encapsulation for that class, since the formatter should only work with a LogRecord and not have to worry about the table name or database information. Another design decision was to stay away from prepared statements. Using them would require an added level of complexity since we’d need to return individual values to set them rather than a single string, which is what format() is designed to do. The flush() method isn’t applicable to this type of handler, so no implementation is present. For any of the StreamHandlers, however, this would flush the writer. The last required method, close(), will attempt to close the database connection. A complete listing of this class can be found in Listing 2. TblXMLFormatter To extend the formatter requires implementing format() and, optionally, getHeader() and getTail(), which return an empty string by default. Although we can use the base class formatter to create our new class, you’ll notice we’re inheriting directly from the XML Formatter class, so we can take advantage of the code that already produces the XML from the LogRecord. First, we have the getHeader() method, which is responsible for returning the names and the order of the columns being returned. String returnColNames = col_time stamp+","+col_msg; If we use the default column names from this class, the SQL in the DBHandler will go from this: "insert into LOGTABLE (" + frmtr.getHead() + ") values (" + frmtr.format(logrecord) + ")"; to this: "insert into LOGTABLE (LOG_TIMESTAMP, LOG_MSG) values (" + frmtr.format(logrecord) + ")" Next, in the format() method, we call super.format() to extract the XML and concatenate it with the system datetime that will be returned. This matches the order of what getHeader() returns. Also note that we’re using the JDBC escape sequence for the timestamp to ensure database independency. java.sql.Timestamp tm = new java.sql.Timestamp(System.currentTimeMillis()); After format() is called, the final SQL in DBHandler will look similar to this: "insert into LOGTABLE (LOG_TIMESTAMP, LOG_MSG) values ( {ts ‘2001-11-25 18:25:00.61‘}, ‘<record>xml message elements</record>’)" The complete code for this class can be found in Listing 3. TblFieldFormatter As with the TblXMLFormatter, this class also overrides getHeader() and doesn’t use getTail(). Since the behavior of format() is entirely different from SimpleFormatter or XMLFormatter, we extend the Formatter class directly. For getHeader(), we again return the names and order of the columns being used. Extra logic must also be introduced to provide the flexibility of turning on/off columns, but the order must remain consistent. The format() method must also apply logic to determine which columns to display, and also to keep the order consistent with what is returned by getHeader(). Unlike TblXMLFormatter, we must manually extract each field from the LogRecord. Following is an example of how to include Level directly from the LogRecord, but only if enabled. if ( enablecol_level ) returnVals += ",'" + record.getLevel().toString() + "'"; Finally, as with TblXMLFormatter, there are get/set methods that allow changes to the names of the database columns, and an additional “enable” method per column for enabling/disabling their output. Listing 4 provides the entire code for this class; Listing 5 shows how this formatter is incorporated into our original example application. Conclusion It was my intention in this article to give insight into this framework and outline the steps necessary to customize where your logs go and how they look when they get there. This framework is a powerful utility in the new J2SE, whether used out of the box or customized to your specific requirements. I’ll certainly be watching how it matures over time! Reader Feedback: Page 1 of 1
Enterprise Open Source Magazine Latest Stories . . .
Subscribe to the World's Most Powerful Newsletters
Subscribe to Our Rss Feeds & Get Your SYS-CON News Live!
|
SYS-CON Featured Whitepapers
Most Read This Week |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||