Comments
Richard Davies wrote: The UK has a good crop of technology pioneers in cloud computing - for example ElasticHosts, FlexiScale, Flexiant, OnApp - and also some strong government initiatives such as G-Cloud. We will have to see whether this kind of technical leadership converts into swift mass-market adoption or not.
Cloud Expo on Google News

SYS-CON.TV
Cloud Expo & Virtualization 2009 East
PLATINUM SPONSORS:
IBM
Smarter Business Solutions Through Dynamic Infrastructure
IBM
Smarter Insights: How the CIO Becomes a Hero Again
Microsoft
Windows Azure
GOLD SPONSORS:
Appsense
Why VDI?
CA
Maximizing the Business Value of Virtualization in Enterprise and Cloud Computing Environments
ExactTarget
Messaging in the Cloud - Email, SMS and Voice
Freedom OSS
Stairway to the Cloud
Sun
Sun's Incubation Platform: Helping Startups Serve the Enterprise
POWER PANELS:
Cloud Computing & Enterprise IT: Cost & Operational Benefits
How and Why is a Flexible IT Infrastructure the Key To the Future?
Click For 2008 West
Event Webcasts
Extending the J2SE 1.4 Logging Package - Monitoring made easy
Extending the J2SE 1.4 Logging Package - Monitoring made easy

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
Let’s start by discussing what can be done with the basic logging package. I’ll give a general overview, but I suggest you take a trip to http://java.sun.com/ for a more complete picture.

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" );
XMLFormatter xmlfrmtr = new XMLFormatter();
flhandler.setFormatter( xmlfrmtr );

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 =
Logger.getLogger("com.mylogger.TestDriver");
logger.addHandler( flhandler );
logger.info("TestDriver(): Testing INFO");

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():
Testing INFO").

The code above will produce an XML <record> message with each field in the LogRecord shown as a separate tag:

<record>
<date>2001-11-20T20:21:03</date>
<millis>1006305663460</millis>
<sequence>0</sequence>
<logger>com.mylogger.TestDriver</logger>
<level>INFO</level>
<class>com.mylogger.TestDriverFile
</class>
<method>main</method>
<thread>10</thread>
<message>TestDriver(): Testing INFO</message>
</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
As with any thorough framework, the logging package can be extended to increase its capabilities and provide custom functionality on how to format and generate logs. Perhaps e-mailing levels of SEVERE to administrators or providing the ability for other applications to query and report on the logs is necessary. In the latter case we can replace the common log file with a simple database table. I’ll demonstrate how to do this by extending both the Handler and Formatter classes, giving total control of where the database is located and how to write a log record to it.

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
Providing database support to this logging framework will involve implementing only two classes: a formatter to build the SQL necessary to insert an entire LogRecord into the database, and a handler to do the transactions. We’ll also include a third class, another formatter that will split each field in the log record into separate database fields.

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",
"jdbc:mysql://localhost/logger?
user=jim&password=changeme");

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();
dbhandler.setFormatter( tblxmlfrmtr );

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
Implementing a new target to send logs to requires developing a new Handler class. For the target of a database table, this class should encapsulate all necessary logic for connecting to the database, inserting formatted log records into the specified table, and handling any SQLExceptions.

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 )
throws SQLException, ClassNotFoundException, SecurityException {
// … other configuration code here
this.jdbcdriver = driver;
this.jdbcurl = url;
dbconnect(); // connect to the database
}
private void dbconnect()
throws SQLException, ClassNotFoundException {
Class.forName(jdbcdriver);
this.conn = DriverManager.
getConnection(jdbcurl);
}

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 ) {
// …
Statement stmt = conn.
createStatement();
String sql = "insert into " + tblname + " (" + frmtr.getHead() + ") values (" + frmtr.format(logrecord) + ")";
stmt.executeUpdate( sql );
stmt.close();
// 
}

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
This formatter will need to produce two columns to incorporate into the SQL generated by the DBHandler. The first column will be the timestamp, which will use the standard System datetime. The second column will be a large string containing the entire XML message.

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());
// include timestamp, plus xml message from super
String returnVals = "{ts '" + tm.toString() + "'},'"+ super.format(record) + "'";

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
This formatter is provided to give the user the option of using only a selected number of fields from the LogRecord and separating them into individual columns in the database.

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
By adding the foregoing database capabilities to the new J2SE logging package, we’ve expanded the target of where logs can be stored and increased our options for how to query them. Your logs can now fit into any JDBC-compliant enterprise database schema and use standard database tools to produce reports.

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!

About Jim Mangione
Jim Mangione is Director of Software Engineering for the Report & Query
division of CareScience, Inc. located in Philadelphia, PA. He has over
12 years experience in client/server and web-based application
development and holds a Masters Degree in Computer Science from Drexel
University. He can be reached at jmangione@carescience.com.

In order to post a comment you need to be registered and logged in.

Register | Sign-in

Reader Feedback: Page 1 of 1

Latest Cloud Developer Stories
Swisscom, the Swiss telecom, is going into the cloud business. Its subsidiary Swisscom IT Services AG has signed up with Red Hat as a Certified Cloud Provider and launched a public cloud Infrastructure-as-a-Service (IaaS) cloud targeting enterprise-class customers primarily in ...
Apache Deltacloud, the Red Hat-contributed ReSTful API that abstracts differences between clouds so services on any cloud can be managed – provided of course there’s a driver – has graduated from the Apache Foundation’s incubator and is now a full-fledged Top-Level Project (TLP)....
In a surprise move on Tuesday, January 10, Oracle wheeled out its Big Data Appliance. That’s the one it said in October would be ready sometime in the first half. Only nobody believed it meant early in the first half. Heck, it’s not even clear anybody thought Oracle could make ...
Rackspace Hosting, the service leader in cloud computing, on Thursday announced its acquisition of SharePoint911, an industry leader in SharePoint consulting, training, and "JumpStart" services within SharePoint. The unification of both companies provides capabilities to deliver ...
CloudLinux, Inc., on Thursday released CafeFS 3, a virtualized file system for shared hosters that cages each customer within its own virtualized file system. CageFS becomes part of CloudLinux OS at no additional charge. CloudLinux OS, the only commercially-supported Linux OS m...
Subscribe to the World's Most Powerful Newsletters
Subscribe to Our Rss Feeds & Get Your SYS-CON News Live!
Click to Add our RSS Feeds to the Service of Your Choice:
Google Reader or Homepage Add to My Yahoo! Subscribe with Bloglines Subscribe in NewsGator Online
myFeedster Add to My AOL Subscribe in Rojo Add 'Hugg' to Newsburst from CNET News.com Kinja Digest View Additional SYS-CON Feeds
Publish Your Article! Please send it to editorial(at)sys-con.com!

Advertise on this site! Contact advertising(at)sys-con.com! 201 802-3021

SYS-CON Featured Whitepapers
ADS BY GOOGLE

Breaking Cloud Computing News
As client demand for engagements increases, Revel Consulting (www.revelconsulting.com), a Kirkland, ...