• No results found

Before logging had become a de facto requirement for Java applications it was common to use System.out.printlnto output the debug traces. The disadvantages of this approach are abundant and obvious. Once written, such traces cannot be turned on or off without chang- ing the code. Even though the application output stream can be redirected to a file for persis- tence, there is no rollover and because the file is kept open, it cannot be deleted until the application is shut down (hence, the file size can get exorbitant). When dealing with legacy Java code riddled with System.out.println()calls, a common problem is converting them to calls to a logging framework (see Chapter 6, “Using Effective Tracing,” for a discussion of logging and tracing). It is also important to capture the standard error stream, which receives output of methods such as Exception’s printStackTrace(). One of the neat solutions to this is intercepting the output to System.outand System.errand sending it to the log file

instead. The technique relies on the fact that the system output stream can be redirected to a custom PrintStreamusing the setOutmethod of java.lang.System. PrintStreamis a decora- tor class around an instance of OutputStream, which is responsible for the actual output. The task at hand is therefore to develop a redirecting OutputStreamthat writes to a log file instead of the process standard output and to then assign the System.outto it.

We are going to develop a class called LogOutputStreamthat extends java.io.OutputStream and writes its output to a log file using Apache Log4J. The Java input/output framework is very well designed, and all methods of OutputStreameventually delegate to a single

method—write()—that takes an integer parameter. LogOutputStreamuses a StringBufferto accumulate characters that it gets in the write(int)method and, when a line separator is detected, the whole buffer is written to disk using Log4J. The only tricky part about the implementation is detecting the end of a line. As you are undoubtedly aware, on Unix the end of a line is marked by a single character: \n(new line). On Windows, the end of a line is marked by a combination of two characters: \rand \n(carriage return and new line). To write truly cross-platform code in Java, you must rely on a system property called line.separator. Because the property is a string, the implementation has to rely on a substring search rather than character comparison. Our implementation is optimized to first use the character comparison to check for the possibleend of a line and then use a substring search to ensure that it is the end of a line indeed. The overridden write()method is shown in Listing 16.1.

LISTING 16.1 The write()Method of LogOutputStream public void write(int b) throws IOException {

char ch = (char)b; this.buffer.append(ch);

if (ch == this.lineSeparatorEnd) {

// Check on a char by char basis for speed String s = buffer.toString();

if (s.indexOf(lineSeparator) != -1) { // The whole separator string is written

logger.info(s.substring(0, s.length() - lineSeparator.length())); buffer.setLength(0);

} } }

The logger here is a reference to a static variable of type org.apache.log4j.Loggerdeclared in LogOutputStreamas follows:

static Logger logger = Logger.getLogger(LogOutputStream.class.getName());

157

Thus, the entire output to System.outis redirected to the Log4J framework as INFO-level messages from the LogOutputStreamclass. To see our class in action, we have to configure a file appender in log4j.propertiesand install the interceptor as shown in Listing 16.2.

LISTING 16.2 Installing System.outInterception

public static void main(String[] args) {

System.out.println(“Installing the interceptor...”);

PrintStream out = new PrintStream(new LogOutputStream(), true); System.setOut(out);

System.out.println(“Hello, world”); System.out.println(“Done”);

}

Running the main()method of LogOutputStreamdisplays an Installing the

interceptor...message on the console but writes Hello, worldand Donemessages to the log file. The same interceptor can be installed for the System.errstream. To make it flexible, it can be parameterized to take in the logging level and a stream name in the constructor. In a similar fashion, System.In streamcan be programmatically set using System.setIn()to feed a desired input into an application.

Intercepting a Call to System.exit

The JVM process normally terminates when no active threads exist. Threads running as daemons(Thread.isDaemon() == true) do not prevent the JVM from being shut down. In multithreaded applications, which include Swing GUIs and RMI servers, it is not easy to achieve a clean shutdown by letting all threads end gracefully. Frequently, a call to

System.exit()is made to forcefully shut down the JVM and terminate the process. Relying on System.exit()has become a common practice even in programs that are not very sophis- ticated; even though it makes the life of the application developer easier, it can present a problem for middle-tier products such as Web and application servers. An inadvertent call to

System.exit()by a Web application, for example, can bring down the Web server process and possibly prevent users from accessing other Web applications and static HTML pages. This is no way to make friends with the system administrators, and every good developer knows the value of a healthy relationship with that team.

This section examines a simple way to intercept a call to System.exit()and prevent the shutdown of the JVM. This technique can be discovered by examining the source code of the

exit()method in java.lang.System. The first thing the method does is check whether a security manager is installed. If it is, the method verifies that the caller has a permission to

exit the JVM. Our task, therefore, is to install a custom security manager (or modify the secu- rity policy if a security manager is already installed) that disallows the exit until it is explic- itly allowed. The InterceptingSecurityManagerclass located in the covertjava.intercept package extends the SecurityManagerclass and overrides the isExitAllowed()method to control the JVM shutdown. It uses an internal flag that can be set via the setExitAllowed() method to determine whether to allow the JVM to shut down. If the exit is not allowed, an unchecked SecurityExceptionis thrown to alter the control flow. The main()method shown in Listing 16.3 shows how to install the intercepting security manager and how it affects the execution flow.

LISTING 16.3 Intercepting System.exit() public static void main(String[] args) {

InterceptingSecurityManager secManager = new InterceptingSecurityManager(); System.setSecurityManager(secManager);

try {

System.out.println(“Run some logic...”); System.exit(1); } catch (Throwable x) { if (x instanceof SecurityException) System.out.println(“Intercepted System.exit()”); else x.printStackTrace(); }

System.out.println(“Run more logic...”); secManager.setExitAllowed(true);

System.out.println(“Finished”); }

To keep the example simple, the actual business logic that would normally be invoked inside the tryblock was replaced with a Run some logic...message. The key is to catch the

Throwableclass rather than the usual Exceptionbecause the intercepted System.exit()is reported as an unchecked exception. Running the main()method shown in Listing 16.3 produces the following output:

Run some logic...

Intercepted System.exit() Run more logic...

Finished

Process terminated with exit code 0

Instead of terminating the JVM after a call to System.exit()inside the tryblock, the program continues to run until the exit is allowed.

159