sitelink1  
sitelink2  
sitelink3 http://1 
extra_vars4 ko 
extra_vars5 http://www.javaworld.com/javaworld/jw-12-2004/jw-1220-toolbox.html?page=3 
extra_vars6 sitelink1 

 

·미리보기 | 소스복사·
  1. log4j.appender.REMOTE=com.holub.log4j.RemoteAppender   
  2. log4j.appender.REMOTE.Port=1234  
  3. log4j.appender.REMOTE.layout=org.apache.log4j.PatternLayout   
  4. log4j.appender.REMOTE.layout.ConversionPattern=[%d{MMM dd HH:mm:ss}] %-5p (%F:%L) - %m%n  

 

Using a remote appender

One of log4j's major strengths is that the tool is easy to extend. My RemoteAppender extension provides a way to log messages across the network to a simple socket-based client application. Log4J actually comes with a means of doing remote logging (an appender called SocketAppender), but this default mechanism is too heavyweight for my needs. It requires you to have the log4j packages on the remote client, for example.

Log4j also comes with an elaborate standalone GUI called Chainsaw that you can use to view messages from a SocketAppender. But Chainsaw is also way more than I need and really badly documented to boot. (I've never have had the time or patience to figure out how to use Chainsaw.) In any event, I just wanted to watch debugging diagnostics scroll by on a console window as I tested. Chainsaw was way too much for this simple need.

Listing 3 shows a simple viewer application for my RemoteAppender. It's just a simple socket-based client application that waits in a loop until it can open a socket to the server application that logs the messages. (See Resources for a discussion of sockets and Java's socket APIs). The port number, which is hard-coded into this simple example (as 1234) is passed to the server via the configuration file in Listing 2. Here's the relevant line:

·미리보기 | 소스복사·
  1. log4j.appender.REMOTE.Port=1234  
The client application waits in a loop until it can connect to the server, and then it just reads messages from the server and prints them to the console. Nothing earth shattering. The client knows nothing about log4j?it just reads strings and prints them?so the coupling to the log4j systems is nonexistent. Launch the client with java Client and terminate it with a Ctrl-C.

Listing 3. Client.java: A client for viewing logging messages

·미리보기 | 소스복사·
  1. import java.net.*;   
  2. import java.io.*;   
  3.   
  4. public class Client   
  5. {   
  6.     public static void main(String[] args) throws Exception   
  7.     {   
  8.         Socket s;   
  9.         whiletrue )   
  10.         {   try  
  11.             {   
  12.                 s = new Socket( "localhost", 1234 );   
  13.                 break;   
  14.             }   
  15.             catch( java.net.ConnectException e )   
  16.             {   // Assume that the host isn't available yet, wait   
  17.                 // a moment, then try again.   
  18.                 Thread.currentThread().sleep(50);   
  19.             }   
  20.         }   
  21.   
  22.         BufferedReader in = new BufferedReader(   
  23.                                 new InputStreamReader( s.getInputStream() ) );   
  24.   
  25.         String line;   
  26.         while( (line = in.readLine()) != null )   
  27.             System.err.println( line );   
  28.     }   
  29. }  

Note, by the way, that the client in Listing 3 is a great example of when not to use Java's NIO (new input/output) classes. There's no need for asynchronous reading here, and NIO would complicate the application considerably.

The remote appender

All that's left is the appender itself, which manages the server-side socket and writes the output to the clients that connect to it. (Several clients can receive logging messages from the same appender simultaneously.) The code is in Listing 4.

Starting with the basic structure, the RemoteAppender extends log4j's AppenderSkeleton class, which does all of the boilerplate work of creating an appender for you. You must do two things to make an appender: First, if your appender needs to be passed arguments from the configuration file (like the port number), you need to provide a getter/setter function with the names getXxx() and setXxx() for a property named Xxx. I've done that for the Port property on line 41 of Listing 4.

Note that both the getter and setter methods are private. They're provided strictly for use by the log4j system when it creates and initializes this appender, and no other object in my program has any business accessing them. Making getPort() and setPort() private guarantees that normal code can't access the methods. Since log4j accesses these methods via the introspection APIs, it can ignore the private attribute. Unfortunately, I've noticed that private getters and setters work only in some systems. I have to redefine these fields as public to get the appender to work correctly under Linux, for example.

The second order of business is to override a few methods from the AppenderSkeleton superclass.

After log4j has parsed the configuration file and called any associated setters, the activateOptions() method (Listing 4, line 49) is called. You can use activeOptions() to validate property values, but here I'm using it to actually open up a server-side socket at the specified port number.

activateOptions() creates a thread that manages the server socket. The thread sits in an endless loop waiting for the client to connect. The accept() call on line 56 blocks, or suspends, the thread until a connection is established; accept() returns a socket connected to the client application. The thread is terminated in close(), which we'll look at shortly, by closing the listenerSocket object. Closing the socket causes a SocketException to be thrown.

Once the connection is established, the thread wraps the output stream for the client socket in a Writer and adds that Writer to a Collection called clients. There's one Writer in this Collection for each client connected to the current appender.

The synchronization is worth mentioning. I put objects into clients in the socket-management thread, but the collection is also used by whatever thread actually doing the logging.

The code that does the actual appending is in the append() method (Listing 4, line 93). The appender first delegates message formatting to the layout object specified in the configuration file (on line 107) and then writes the message to the various client sockets on line 120. The code formats the message only once, but then iterates through the Writers for each client and sends the message to each of them.

That iteration is tricky. I could have wrapped the clients Collection in a synchronized wrapper by calling Collections.synchronizedCollection() and not explicitly synchronized. Had I done so, however, the add() method back in the socket-management thread would have thrown an exception if an iteration was in progress when it tried to add a new client.

Since clients aren't added often, I've solved the problem simply by locking the clients Collection manually while iterations are in progress. This way, the socket-management thread blocks until the iteration completes. The only downside to this solution is that a client might have to wait a while before the server accepts the connection. I haven't found this wait to be problematic.

The only appender method that remains is an override of close() (Listing 4, line 143), which closes the server socket and cleans up (and closes) the client connections. As I mentioned earlier, closing the listenerSocket terminates the thread that contains the accept() loop.

Listing 4. RemoteAppender.java: A custom log4j appender

·미리보기 | 소스복사·
  1. package com.holub.log;   
  2.   
  3. import org.apache.log4j.AppenderSkeleton;   
  4. import org.apache.log4j.spi.LoggingEvent;   
  5. import org.apache.log4j.spi.ErrorCode;   
  6. import org.apache.log4j.Layout;   
  7. import org.apache.log4j.helpers.LogLog;   
  8.   
  9. import java.util.*;   
  10. import java.io.*;   
  11. import java.net.*;   
  12.   
  13. /** This appender works much like log4j's Socket Appender.  
  14.  *  The main difference is that it sends strings to  
  15.  *  remote clients rather than sending serialized  
  16.  *  LoggingEvent objects. This approach has the  
  17.  *  advantages of being considerably faster (serialization  
  18.  *  is not cheap) and of not requiring the client  
  19.  *  application to be coupled to log4j at all.  
  20.  *  
  21.  *  <p>This appender takes only one "parameter," which specifies  
  22.  *     the port number (defaults to 9999). Set it with:  
  23.  *  <PRE>  
  24.  *  log4j.appender.R=com.holub.log4j.RemoteAppender;  
  25.  *  ...  
  26.  *  log4j.appender.R.Port=1234  
  27.  *  </PRE>  
  28.  *  
  29.  */  
  30.   
  31. public class RemoteAppender extends AppenderSkeleton   
  32. {   
  33.     // The iterator across the "clients" Collection must   
  34.     // support a "remove()" method.   
  35.   
  36.     private Collection   clients = new LinkedList();   
  37.     private int          port    = 9999;   
  38.     private ServerSocket listenerSocket;   
  39.     private Thread       listenerThread;   
  40.   
  41.     private void setPort(int port)  { this.port = port; }   
  42.     private int  getPort()          { return this.port; }   
  43.   
  44.     public boolean requiresLayout(){ return true; }   
  45.   
  46.     /** Called once all the options have been set. Starts  
  47.      *  listening for clients on the specified socket.  
  48.      */  
  49.     public void activateOptions()   
  50.     {   try  
  51.         {   listenerSocket  = new ServerSocket( port );   
  52.             listenerThread  = new Thread()   
  53.             {   public void run()   
  54.                 {   try  
  55.                     {   Socket clientSocket;       
  56.                         while( (clientSocket = listenerSocket.accept()) != null )   
  57.                         {   // Create a (deliberately) unbuffered writer   
  58.                             // to talk to the client and add it to the   
  59.                             // collection of listeners.   
  60.   
  61.                             synchronized( clients )   
  62.                             {   clients.add(   
  63.                                     new OutputStreamWriter(   
  64.                                         clientSocket.getOutputStream()) );   
  65.                             }   
  66.                         }   
  67.                     }   
  68.                     catch( SocketException e )   
  69.                     {   // Normal close operation. Doing nothing   
  70.                         // terminates the thread gracefully.   
  71.                     }   
  72.                     catch( IOException e )   
  73.                     {   // Other IO errors also kill the thread, but with   
  74.                         // a logged message.   
  75.                         errorHandler.error("I/O Exception in accept loop" + e );   
  76.                     }   
  77.                 }   
  78.             };   
  79.             listenerThread.setDaemon( true );   
  80.             listenerThread.start();   
  81.         }   
  82.         catch( IOException e )   
  83.         {   errorHandler.error("Can't open server socket: " + e );   
  84.         }   
  85.     }   
  86.   
  87.     /** Actually do the logging. The AppenderSkeleton's   
  88.      *  doAppend() method calls append() to do the  
  89.      *  actual logging after it takes care of required  
  90.      *  housekeeping operations.  
  91.      */  
  92.   
  93.     public synchronized void append( LoggingEvent event )   
  94.     {      
  95.         // If this Appender has been closed or if there are no   
  96.         // clients to service, just return.   
  97.   
  98.         if( listenerSocket== null || clients.size() <= 0 )   
  99.             return;   
  100.   
  101.         ifthis.layout == null )   
  102.         {   errorHandler.error("No layout for appender " + name ,   
  103.                                 null, ErrorCode.MISSING_LAYOUT );   
  104.             return;   
  105.         }   
  106.   
  107.         String message = this.layout.format(event);   
  108.   
  109.         // Normally, an exception is thrown by the synchronized collection   
  110.         // when somebody (i.e., the listenerThread) tries to modify it   
  111.         // while iterations are in progress. The following synchronized   
  112.         // statement causes the listenerThread to block in this case,   
  113.         // but note that connections that can't be serviced quickly   
  114.         // enough might be refused.   
  115.   
  116.         synchronized( clients )   
  117.         {   for( Iterator i = clients.iterator(); i.hasNext(); )   
  118.             {   Writer out = (Writer)( i.next() );   
  119.                 try  
  120.                 {   out.write( message, 0, message.length() );   
  121.                     out.flush();   
  122.                     // Boilerplate code: handle exceptions if not   
  123.                     // handled by layout object:   
  124.                     //   
  125.                     if( layout.ignoresThrowable() )   
  126.                     {   String[] messages = event.getThrowableStrRep();   
  127.                         if( messages != null )   
  128.                         {   forint j = 0; j < messages.length; ++j )   
  129.                             {   out.write( messages[j], 0, messages[j].length() );   
  130.                                 out.write( 'n' );   
  131.                                 out.flush();   
  132.                             }   
  133.                         }   
  134.                     }   
  135.                 }   
  136.                 catch( IOException e )  // Assume that the write failed   
  137.                 {   i.remove();         // because the connection is closed.       
  138.                 }   
  139.             }   
  140.         }   
  141.     }   
  142.   
  143.     public synchronized void close()   
  144.     {   try  
  145.         {      
  146.             if( listenerSocket == null ) // Already closed.   
  147.                 return;   
  148.             listenerSocket.close();     // Also kills listenerThread.   
  149.   
  150.             // Now close all the client connections.   
  151.             for( Iterator i = clients.iterator(); i.hasNext(); )   
  152.             {   ((Writer) i.next()).close();   
  153.                 i.remove();   
  154.             }   
  155.   
  156.             listenerThread.join();      // Wait for thread to die.   
  157.             listenerThread  = null;     // Allow everything to be   
  158.             listenerSocket  = null;     // garbage collected.   
  159.             clients         = null;   
  160.         }   
  161.         catch( Exception e )   
  162.         {   errorHandler.error("Exception while closing: " + e);   
  163.         }   
  164.     }   
  165. }   

Conclusion

That's all there is to building an appender. You override a few methods of AppenderSkeleton and add getter/setter methods for the parameters. Most of the nastiness here is socket-and-thread related. The actual log4j code was simplicity itself (extend a class and overwrite a few methods). Writing your own appenders for things like logging to a database, for example, is equally simple.


번호 제목 글쓴이 날짜 조회 수
57 셀 크기 조정 (자동 크기 조정) 황제낙엽 2011.05.03 7740
56 Cell 의 wrap 설정 (텍스트 개행) file 황제낙엽 2011.05.09 2966
55 POI HSSF, XSSF, SXSSF 성능 분석 file 황제낙엽 2013.11.05 1590
54 병합된 셀의 스타일( border) 설정하기 황제낙엽 2011.05.03 1564
53 Parsing and Processing Large XML Documents with Digester Rules (해석중) file 황제낙엽 2008.05.13 1478
52 POI 셀 스타일 설정을 위한 예제 소스 file 황제낙엽 2008.05.16 1379
51 엑셀(Excel)문서 처리 패키지 황제낙엽 2007.01.22 1334
50 POI-HSSF and POI-XSSF - Java API To Access Microsoft Excel Format Files 황제낙엽 2013.11.05 984
49 Comma Separated Values (CSV) - au.com.bytecode.opencsv file 황제낙엽 2007.01.23 626
48 Parsing, indexing, and searching XML with Digester and Lucene 황제낙엽 2008.05.07 429
47 POI HSSF 기능 가이드 -- 퀵·가이드 (한글) 황제낙엽 2008.05.16 373
46 JUnit 3.8에서 JUnit 4, TestNG 활용으로 황제낙엽 2007.09.17 369
45 Junit 을 이용한 효율적인 단위 테스트 전략 황제낙엽 2007.01.30 317
44 Library & Properties 파일 file 황제낙엽 2011.12.23 313
43 XSSF Examples file 황제낙엽 2011.05.04 254
» 사용자 정의 Appender 정의하여 Log4j 확장하기 황제낙엽 2009.05.28 220
41 log4j-1.2.15.jar 와 log4j.properties 예제 file 황제낙엽 2017.08.04 187
40 접속 클라이언트의 아이피별로 로그 화일 기록하기 file 황제낙엽 2009.06.01 183
39 Comma Separated Values (CSV) - com.Ostermiller.util Java Utilities 황제낙엽 2007.01.23 177
38 Apache Log4j 2 Configuration 파일 설정 황제낙엽 2020.04.01 150