Logging is a useful technique for recording exceptions and significant events which occur during the execution of an application. While there are many different logging APIs available, we will focus on those classes found in the java.util.logging
package. If you are interested you may find alternate logging technologies such as log4j (http://logging.apache.org/log4j/1.2/) or the Simple Logging Facade for Java (SLF4J) (http://www.slf4j.org/) to be useful.
The process of creating using a logger involves:
java.util.logging
package Logger
class' static method getLogger
to obtain an instance of a logger Logger
to record application dataLogging involves writing messages to a data store. The logged messages can be controlled using levels and filters. The Logging API has seven predefined logging levels:
Level |
Meaning |
---|---|
SEVERE |
The highest level normally reserved for critical errors |
WARNING |
Used for warning type messages |
INFO |
This message does not necessarily indicate an error but rather conveys general information |
CONFIG |
Used for configuration-type information |
FINE |
First of three levels of detail |
FINER |
Intermediate level of detail |
FINEST |
Indicates the greatest level of detail |
The Logger's
overloaded log
method's first argument is a Level
value. The value assigned to this argument denotes the level of the log. However, this does not mean the message will be logged. A logger will have a level specified for it and will log all messages at this or a higher level. The setLevel
method determines the level of logging. Any request using levels below that value will be ignored. In addition, even if the message is accepted for logging, a filter may restrict those messages from being logged.
When the message is logged, it can be processed and formatted to add additional content to the message. For example, a time stamp can be added to the message eliminating the need to include this information in each log
method call.
A java.util.logging.Handler
derived class actually writes the message to a data store. A data store can be one of several different types of stores, though files are the most common type. There are two standard Handler
classes available: MemoryHandler
and StreamHandler
. It is possible to create your own handler if needed.
The following figure depicts the structure of the Java Logging API.
Create a Java EE application called LoggingApplication
. We will use this application to demonstrate various logging techniques. In the EJB module create a package called packt
and a stateless session bean called PhoneNumber
. This EJB consists of a default constructor and four methods. The purpose of the EJB is to validate and format a phone number. Using these elements of the EJB, we will demonstrate logging.
First add a private logger
variable and a default constructor. Within the constructor, create an instance of a Logger
using the getLogger
method and use the instance to log the creation of the Logger
.
@Stateless public class PhoneNumber { private Logger logger; public PhoneNumber() { logger = Logger.getLogger("phonenumber"); logger.log(Level.INFO, "Phone number logger created"); } ... }
Next, add a format
method which accepts three integers representing the three parts of a standard telephone number. Within this method we will call three different validation methods, each of which may throw an exception. If there are no errors, then a simple formatted string is returned and this event is logged at the level FINEST
. If there are errors, then the exception is logged as a simple message at the level FINE
. Using different levels allows us to record events in the application based on our interest.
public String format(int areaCode, int prefix, int lineNumber) { try { validateAreaCode(areaCode); validatePrefix(prefix); validateLineNumber(lineNumber); logger.log(Level.FINEST, "Formatted phone number returned"); return "(" + areaCode + ")" + prefix + "-" + lineNumber; } catch(InvalidAreaCodeException e) { logger.log(Level.FINE, "InvalidAreaCodeException"); } catch(InvalidPrefixException e) { logger.log(Level.FINE, "InvalidPrefixException"); } catch(InvalidLineNumberException e) { logger.log(Level.FINE, "InvalidLineNumberException"); } return ""; }
Next, add the three methods used for validation. These methods perform a simple test on their arguments. A more sophisticated test would be needed in a production application.
private boolean validateAreaCode(int areaCode) throws InvalidAreaCodeException { if (areaCode < 0 || areaCode > 999) { throw new InvalidAreaCodeException(); } return true; } private boolean validatePrefix(int prefix) throws InvalidPrefixException { if (prefix < 0 || prefix > 999) { throw new InvalidPrefixException(); } return true; } private boolean validateLineNumber(int lineNumber) throws InvalidLineNumberException { if (lineNumber < 0 || lineNumber > 9999) { throw new InvalidLineNumberException(); } return true; } }
Add three classes for the three different exceptions.
public class InvalidAreaCodeException extends java.lang.Exception { public InvalidAreaCodeException() {} public InvalidAreaCodeException(String message) {super(message);} } public class InvalidLineNumberException extends Exception { public InvalidLineNumberException() {} public InvalidLineNumberException(String message) {super(message);} } public class InvalidPrefixException extends Exception{ public InvalidPrefixException() {} public InvalidPrefixException(String message) {super(message);} }
Create a servlet called LoggingServlet
in the servlet
package of the Web module. Inject the PhoneNumber
EJB and invoke its format
method as shown below. Notice the line number is too large.
public class LoggingServlet extends HttpServlet { @EJB PhoneNumber phoneNumber; protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=UTF-8"); PrintWriter out = response.getWriter(); try { out.println("<html>"); out.println("<head>"); out.println("<title>Servlet LoggingServlet</title>"); out.println("</head>"); out.println("<body>"); out.println("<h1>Phone Number: " + phoneNumber.format(202, 555, 10003) + "</h1>"); out.println("</body>"); out.println("</html>"); } finally { out.close(); } }
Execute the servlet. The output in part shows the creation of the logger. The INFO: prefix is added automatically reflecting the level of the message.
...
INFO: Phone number logger created
The reason the InvalidLineNumberException
is not displayed is because, by default, the Logger
is set to the level INFO
. It will only log those messages at that level or above. Since the exception is logged at the level FINE
, it does not show up.
To rectify this situation, use the Logger's setLevel
command after the creation of logger
with an argument of Level.ALL
.
logger.setLevel(Level.ALL);
Re-executing the servlet will produce the expected results.
...
INFO: Phone number logger created
FINE: InvalidLineNumberException
The PhoneNumber
EJB was created to illustrate the logging approach. In its constructor an instance of a Logger
class was created. The string argument of the getLogger
method was used to name the Logger
instance. While it is not discussed here, the Java Logger API provides a sophisticated hierarchy of Loggers
that can provide a richer set of logging capabilities than presented here.
The log
method is overloaded. In this example, we used a simple variation of the method. The first argument specified the logging level and the second argument was the message we wanted to log. In the format
method, exceptions that were caught were logged. The servlet used an invalid line number which resulted in the exception being logged once the appropriate logging level was set using the setLevel
method.
At the beginning of this recipe the creation of a Logger
was accomplished as follows:
@Stateless public class PhoneNumber { private Logger logger; public PhoneNumber() { logger = Logger.getLogger("phonenumber"); logger.log(Level.INFO, "Phone number logger created"); } ... }
However, this approach may not be suitable in all environments. The technique is not thread-safe and we cannot use the logger from a static method. An alternative approach declares the logger as follows:
@Stateless public class PhoneNumber { private static final Logger logger = Logger.getLogger("phonenumber"); public PhoneNumber() { logger.log(Level.INFO, "Phone number logger created"); } ... }
The static
keyword means the logger can be used by static and instance methods. Making it final
results in a single instance of the object and avoids many thread issues as the methods of the Logger
class are thread-safe.
3.149.228.138