KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > sun > enterprise > server > logging > FileandSyslogHandler


1 /*
2  * The contents of this file are subject to the terms
3  * of the Common Development and Distribution License
4  * (the License). You may not use this file except in
5  * compliance with the License.
6  *
7  * You can obtain a copy of the license at
8  * https://glassfish.dev.java.net/public/CDDLv1.0.html or
9  * glassfish/bootstrap/legal/CDDLv1.0.txt.
10  * See the License for the specific language governing
11  * permissions and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL
14  * Header Notice in each file and include the License file
15  * at glassfish/bootstrap/legal/CDDLv1.0.txt.
16  * If applicable, add the following below the CDDL Header,
17  * with the fields enclosed by brackets [] replaced by
18  * you own identifying information:
19  * "Portions Copyrighted [year] [name of copyright owner]"
20  *
21  * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
22  */

23 package com.sun.enterprise.server.logging;
24
25 import java.io.*;
26 import java.util.Date JavaDoc;
27 import java.util.LinkedList JavaDoc;
28 import java.util.ArrayList JavaDoc;
29 import java.text.MessageFormat JavaDoc;
30 import java.text.DateFormat JavaDoc;
31 import java.text.SimpleDateFormat JavaDoc;
32 import java.text.FieldPosition JavaDoc;
33 import java.util.logging.StreamHandler JavaDoc;
34 import java.util.logging.LogRecord JavaDoc;
35 import java.util.logging.Level JavaDoc;
36 import java.util.logging.ErrorManager JavaDoc;
37 import java.util.logging.Formatter JavaDoc;
38
39 import javax.management.ObjectName JavaDoc;
40 import javax.management.MBeanServer JavaDoc;
41
42 import com.sun.enterprise.util.StringUtils;
43 import com.sun.enterprise.server.ApplicationServer;
44 import com.sun.enterprise.server.ServerContext;
45
46 import com.sun.enterprise.server.logging.stats.ErrorStatistics;
47 import com.sun.enterprise.server.logging.logviewer.backend.LogFilter;
48 import com.sun.enterprise.server.logging.logviewer.backend.LogFile;
49
50 import com.sun.enterprise.config.serverbeans.LogService;
51
52 // Delete
53
import java.util.HashMap JavaDoc;
54 import java.util.logging.Logger JavaDoc;
55 import java.util.Vector JavaDoc;
56
57 /**
58  * FileandSyslogHandler publishes formatted log Messages to a FILE.
59  *
60  * @AUTHOR: Hemanth Puttaswamy
61  * _REVISIT_:
62  * TODO:
63  * 1. Ability to lock log files to get exclusive write access
64  */

65 public class FileandSyslogHandler extends StreamHandler JavaDoc {
66     // This is a OutputStream to keep track of number of bytes
67
// written out to the stream
68
private MeteredStream meter;
69  
70     private final AMXLoggingHook mAMXLoggingHook;
71
72     private Date JavaDoc date = new Date JavaDoc( );
73     private static final String JavaDoc LOGS_DIR = "logs";
74     private String JavaDoc logFileName = "server.log";
75
76     private String JavaDoc absoluteFileName = null;
77
78     private static boolean isInitialized = false;
79
80     private LogMBean logMBean;
81
82     private LogService logService = null;
83
84     private static final int WARNING = Level.WARNING.intValue();
85
86     private static final int SEVERE = Level.SEVERE.intValue();
87
88     private static final String JavaDoc INSTANCE_ROOT_PROPERTY =
89         "com.sun.aas.instanceRoot";
90
91     private static final String JavaDoc LOGGING_MAX_HISTORY_FILES = "com.sun.enterprise.server.logging.max_history_files";
92
93     // For now the mimimum rotation value is 0.5 MB.
94
private static final int MINIMUM_FILE_ROTATION_VALUE = 500000;
95
96     // Initially the LogRotation will be off until the domain.xml value is read.
97
private int limitForFileRotation = 0;
98
99     private Object JavaDoc fileUpdateLock = new Object JavaDoc( );
100
101     private boolean rotationInProgress = false;
102
103     private static final FileandSyslogHandler thisInstance =
104         new FileandSyslogHandler( );
105
106     private LinkedList JavaDoc pendingLogRecordList = new LinkedList JavaDoc();
107
108     // Rotation can be done in 3 ways
109
// 1. Based on the Size: Rotate when some Threshold number of bytes are
110
// written to server.log
111
// 2. Based on the Time: Rotate ever 'n' minutes, mostly 24 hrs
112
// 3. Rotate now
113
// For mechanisms 2 and 3 we will use this flag. The rotate() will always
114
// be fired from the publish( ) method for consistency
115
private static boolean rotationRequested = false;
116
117     public static synchronized FileandSyslogHandler getInstance( ) {
118         return thisInstance;
119     }
120
121     private static final String JavaDoc LOG_ROTATE_DATE_FORMAT =
122         "yyyy-MM-dd'T'HH-mm-ss";
123
124     private static final SimpleDateFormat JavaDoc logRotateDateFormatter =
125         new SimpleDateFormat JavaDoc( LOG_ROTATE_DATE_FORMAT );
126    
127     // We maintain a list (Vector) of the last MAX_RECENT_ERRORS WARNING
128
// or SEVERE error messages that were logged. The DAS (or any other
129
// client) can then obtain these messages through the ServerRuntimeMBean
130
// and determine if the server instance (or Node Agent) is in an
131
// error state.
132
private static Vector JavaDoc _recentErrors = new Vector JavaDoc();
133
134     private static final int MAX_RECENT_ERRORS = 4;
135     
136     public synchronized static Vector JavaDoc getRecentErrorMessages() {
137         return _recentErrors;
138     }
139         
140     public synchronized static void clearRecentErrorMessages() {
141         _recentErrors = new Vector JavaDoc();
142     }
143     
144     private synchronized static void addRecentErrorMessage(String JavaDoc error) {
145         if (_recentErrors.size() < MAX_RECENT_ERRORS) {
146             _recentErrors.add(error);
147         } else {
148             _recentErrors.removeElementAt(0);
149             _recentErrors.add(MAX_RECENT_ERRORS - 1, error);
150         }
151     }
152
153     /**
154      * This method is invoked from LogManager.reInitializeLoggers() to
155      * change the location of the file.
156      */

157     void changeFileName( String JavaDoc fileName ) {
158         // If the file name is same as the current file name, there
159
// is no need to change the filename
160
if( fileName.trim().equals( absoluteFileName ) ) {
161             return;
162         }
163         synchronized( this ) {
164             super.flush( );
165             super.close();
166             try {
167                 openFile( fileName );
168                 // Change the file for LogViewer
169
LogFilter.setLogFile( new LogFile( fileName ) );
170                 absoluteFileName = fileName;
171             } catch( IOException ix ) {
172                 new ErrorManager JavaDoc().error(
173                     "FATAL ERROR: COULD NOT OPEN LOG FILE. " +
174                     "Please Check to make sure that the directory for " +
175                     "Logfile exists. Currently reverting back to use the " +
176                     " default server.log", ix, ErrorManager.OPEN_FAILURE );
177                 try {
178                     // Reverting back to the old server.log
179
openFile( absoluteFileName );
180                 } catch( Exception JavaDoc e ) {
181                     new ErrorManager JavaDoc().error(
182                         "FATAL ERROR: COULD NOT RE-OPEN SERVER LOG FILE. ", e,
183                         ErrorManager.OPEN_FAILURE );
184                 }
185             }
186         }
187     }
188
189     /**
190      * A simple getter to access the complete fileName used by this FileHandler.
191      */

192     String JavaDoc getAbsoluteLogFileName( ) {
193         return absoluteFileName;
194     }
195
196     /**
197      * A package private method to set the limit for File Rotation.
198      */

199     synchronized void setLimitForRotation( int rotationLimitInBytes ) {
200         if ((rotationLimitInBytes == 0) ||
201             (rotationLimitInBytes >= MINIMUM_FILE_ROTATION_VALUE )) {
202             limitForFileRotation = rotationLimitInBytes;
203         }
204     }
205     
206
207
208     // NOTE: This private class is copied from java.util.logging.FileHandler
209
// A metered stream is a subclass of OutputStream that
210
// (a) forwards all its output to a target stream
211
// (b) keeps track of how many bytes have been written
212
private class MeteredStream extends OutputStream {
213         OutputStream out;
214         long written;
215
216         MeteredStream(OutputStream out, long written) {
217             this.out = out;
218             this.written = written;
219         }
220
221         public void write(int b) throws IOException {
222             out.write(b);
223             written++;
224         }
225
226         public void write(byte buff[]) throws IOException {
227             out.write(buff);
228             written += buff.length;
229         }
230
231         public void write(byte buff[], int off, int len) throws IOException {
232             out.write(buff,off,len);
233             written += len;
234         }
235
236         public void flush() throws IOException {
237             out.flush();
238         }
239
240         public void close() throws IOException {
241             out.close();
242         }
243     }
244
245
246     /**
247      * The Default constructor creates the File and sets the
248      * UniformLogFormatter.
249      */

250     protected FileandSyslogHandler( ) {
251         mAMXLoggingHook = createAMXLoggingHook();
252         try {
253             setFormatter( new UniformLogFormatter( ) );
254         } catch( Exception JavaDoc e ) {
255             new ErrorManager JavaDoc().error(
256                 "FATAL ERROR: COULD NOT INSTANTIATE FILE AND SYSLOG HANDLER",
257                 e, ErrorManager.GENERIC_FAILURE );
258         }
259     }
260
261
262
263     /**
264      * Creates the File under the specified instance directory
265      */

266     public String JavaDoc createFileName( ) {
267         ServerContext sc = ApplicationServer.getServerContext();
268         String JavaDoc instDir = "";
269         if (sc != null) {
270             instDir = sc.getInstanceEnvironment().getInstancesRoot();
271         } else {
272             instDir = System.getProperty( INSTANCE_ROOT_PROPERTY );
273         }
274         String JavaDoc[] names = {instDir, LOGS_DIR, getLogFileName() };
275         // Create an absolute log filename
276
return StringUtils.makeFilePath(names, false);
277     }
278          
279     /**
280      * Creates the file and initialized MeteredStream and passes it on to
281      * Superclass (java.util.logging.StreamHandler).
282      */

283     private void openFile( String JavaDoc fileName ) throws IOException {
284         File file = new File( fileName );
285         FileOutputStream fout = new FileOutputStream( fileName, true );
286         BufferedOutputStream bout = new BufferedOutputStream( fout );
287         meter = new MeteredStream( bout, file.length() );
288         setOutputStream( meter );
289     }
290
291     /**
292      * Request Rotation called from Rotation Timer Task or LogMBean
293      */

294     void requestRotation( ) {
295         synchronized( this ) {
296             rotationRequested = true;
297         }
298     }
299
300     /**
301      * cleanup the history log file based on JVM system property "max_history_files".
302      *
303      * If it is defined with valid number, we only keep that number of history logfiles;
304      * If "max_history_files" is defined without value, then default that number to be 10;
305      * If "max_history_files" is defined with value 0, no histry log file will
306      * be keeped; and server.log is the only log file.
307      */

308     public void cleanUpHistoryLogFiles() {
309         String JavaDoc nStr = System.getProperty(LOGGING_MAX_HISTORY_FILES);
310         if (nStr==null) return;
311
312         int maxHistryFiles = 10;
313         if (!"".equals(nStr)) {
314             try {
315                 maxHistryFiles = Integer.parseInt(nStr);
316             } catch (NumberFormatException JavaDoc e) {};
317         }
318         if (maxHistryFiles<0) return;
319
320         File dir = new File(absoluteFileName).getParentFile();
321         if (dir==null) return;
322
323         File[] fset = dir.listFiles();
324         ArrayList JavaDoc candidates = new ArrayList JavaDoc();
325         for (int i=0; fset!=null && i<fset.length; i++) {
326             if ( !logFileName.equals(fset[i].getName()) &&
327                  fset[i].isFile() &&
328                  fset[i].getName().startsWith(logFileName) ) {
329                  candidates.add(fset[i].getAbsolutePath() );
330             }
331         }
332         if (candidates.size() <= maxHistryFiles) return;
333
334         Object JavaDoc[] pathes = candidates.toArray();
335         java.util.Arrays.sort(pathes);
336         try {
337             for (int i=0; i<pathes.length-maxHistryFiles; i++) {
338         new File((String JavaDoc)pathes[i]).delete();
339             }
340         } catch (Exception JavaDoc e) {
341             new ErrorManager JavaDoc().error("FATAL ERROR: COULD NOT DELETE LOG FILE..",
342                                      e, ErrorManager.GENERIC_FAILURE );
343         }
344     }
345
346
347     /**
348      * A Simple rotate method to close the old file and start the new one
349      * when the limit is reached.
350      */

351     private void rotate( ) {
352         final FileandSyslogHandler thisInstance = this;
353         java.security.AccessController.doPrivileged(
354             new java.security.PrivilegedAction JavaDoc() {
355                 public Object JavaDoc run( ) {
356                     thisInstance.flush( );
357                     thisInstance.close();
358                     StringBuffer JavaDoc renamedFileName = null;
359                     try {
360                         File oldFile = new File( absoluteFileName );
361                         renamedFileName =
362                             new StringBuffer JavaDoc( absoluteFileName + "_" );
363                                 logRotateDateFormatter.format(
364                                     new Date JavaDoc(), renamedFileName,
365                                     new FieldPosition JavaDoc( 0 ) );
366                         File rotatedFile = new File(
367                             renamedFileName.toString() );
368                         boolean renameSuccess = oldFile.renameTo( rotatedFile );
369                         if( !renameSuccess ) {
370                             // If we don't succeed with file rename which
371
// most likely can happen on Windows because
372
// of multiple file handles opened. We go through
373
// Plan B to copy bytes explicitly to a renamed
374
// file.
375
planBLogRotate(absoluteFileName,
376                                 renamedFileName.toString( ) );
377                             String JavaDoc freshServerLogFileName = createFileName( );
378                             // We do this to make sure that server.log
379
// contents are flushed out to start from a
380
// clean file again after the rename..
381
FileOutputStream fo =
382                                 new FileOutputStream( freshServerLogFileName );
383                             fo.close( );
384                         }
385                         openFile( createFileName( ) );
386                         LogFilter.setLogFile( new LogFile( absoluteFileName) );
387                         // This will ensure that the log rotation timer
388
// will be restarted if there is a value set
389
// for time based log rotation
390
LogRotationTimer.getInstance( ).restartTimer( );
391
392                         cleanUpHistoryLogFiles();
393                     } catch( IOException ix ) {
394                         new ErrorManager JavaDoc().error(
395                             "FATAL ERROR: COULD NOT OPEN LOG FILE..", ix,
396                             ErrorManager.OPEN_FAILURE );
397                     }
398                     return null;
399                 }
400             }
401         );
402     }
403
404
405     /**
406      * This method is solely provided as a plan B for Windows. On Windows
407      * the file rename fails if there are open handles to the file.
408      * We want to make sure that the log rotation succeeds in this case also.
409      * Basically we copy the contents of the file instead of renaming.
410      */

411     private void planBLogRotate( String JavaDoc originalFileName,
412         String JavaDoc renamedFileName )
413     {
414         FileOutputStream fo = null;
415         FileInputStream fi = null;
416         try {
417             fo = new FileOutputStream( renamedFileName );
418             fi = new FileInputStream( originalFileName );
419
420             int BUF_SIZE = 4096;
421
422             byte[] buffer = new byte[BUF_SIZE];
423             int i = -1;
424             do {
425                 i = fi.read( buffer );
426                 // Reached EOF.
427
if( i == -1 ) { break; }
428                 if( i == BUF_SIZE ) {
429                     fo.write( buffer );
430                 } else {
431                     // We have less number of bytes to read than the BUF_SIZE
432
// So, just create a temporary buffer with the exact
433
// number of bytes read and write that to the rotated
434
// file.
435
byte[] tempBuffer = new byte[i];
436                     int j = 0;
437                     for( j = 0; j < i; j++ ) {
438                         tempBuffer[j] = buffer[j];
439                     }
440                     fo.write( tempBuffer );
441                 }
442             } while( true );
443         } catch( Exception JavaDoc e ) {
444            // If we fail in Plan B. Too bad, we should not
445
// log a message here as it may lead to recursion.
446
} finally {
447             try {
448                 fo.close( );
449                 fi.close( );
450             } catch( Exception JavaDoc e ) { }
451         }
452     }
453
454
455     /**
456      * Publishes the logrecord using the super class and checks to see
457      * if a file rotation is required.
458      */

459     public synchronized void publish( LogRecord JavaDoc record ) {
460         // This is provided to avoid the recursion. When Log Rotation
461
// is called, there is a chance that any of the called routines
462
// in the Log Rotation call path may log a message, which may
463
// result in a recursion. Hence, we shall log those log messages
464
// after the rotation is completed, so as to avoid the recursion.
465
if( rotationInProgress ) {
466         pendingLogRecordList.addLast(record);
467         return;
468     }
469         // Important: There are issues with double instantiation of
470
// FileandSyslogHandler if we move the createFile/openFile calls
471
// to the constructor. For now, we will do this for the first
472
// publish call.
473
if( meter == null ) {
474             try {
475                 absoluteFileName = createFileName( );
476                 openFile( absoluteFileName );
477             } catch( Exception JavaDoc e ) {
478                 throw new RuntimeException JavaDoc(
479                     "Serious Error Couldn't open Log File" + e );
480             }
481         }
482         super.publish( record );
483         flush( );
484         if ( ( rotationRequested )
485         || ( ( limitForFileRotation > 0 )
486         && ( meter.written >= limitForFileRotation ) ) )
487         {
488             rotationInProgress = true;
489             // If we have written more than the limit set for the
490
// file, or rotation requested from the Timer Task or LogMBean
491
// start fresh with a new file after renaming the old file.
492
rotate( );
493             rotationInProgress = false;
494             rotationRequested = false;
495         while (pendingLogRecordList.size() != 0) {
496         publish((LogRecord JavaDoc) pendingLogRecordList.removeFirst());
497         }
498         }
499         
500         if (mAMXLoggingHook != null) {
501             mAMXLoggingHook.publish( record, getFormatter() );
502         }
503         
504         int level = record.getLevel( ).intValue();
505         // The processing here is to raise alarms if
506
// alarm flag in domain.xml is on and the log level is SEVERE or
507
// WARNING
508
if(( level != WARNING ) && (level != SEVERE)) {
509             return;
510         }
511         
512         //Any WARNING or SEVERE message is placed onto a singleton rolling
513
// error message list. This can be queried as part of a running
514
// server's status. This allows the last N error messages to be easily
515
// viewed without having to consult the log file.
516
String JavaDoc logMessage = getFormatter().format(record);
517         addRecentErrorMessage(logMessage);
518         ErrorStatistics.singleton().updateStatistics(record); // error stats.
519

520         if( logService == null ) {
521             logService = ServerLogManager.getLogService( );
522         }
523         // _REVISIT_: Deprecate Alarms flag
524
if( logService == null ) return;
525        
526         // Raise an alarm if the logged message is a SEVERE or WARNING
527
// message.
528
if( record.getLevel( ).intValue() == WARNING ) {
529             LogMBean.getInstance().raiseWarningAlarm( record );
530         } else if( record.getLevel( ).intValue() == SEVERE ) {
531             LogMBean.getInstance().raiseSevereAlarm( record );
532         }
533     }
534     
535     /**
536      *Returns the AMXLoggingHook to use for this logger.
537      *<p>
538      *This method is here primarily so a subclass can override it if needed.
539      *@return AMXLoggingHook to use
540      */

541     protected AMXLoggingHook createAMXLoggingHook() {
542         return new AMXLoggingHook();
543     }
544     
545     protected String JavaDoc getLogFileName() {
546         return logFileName;
547     }
548 }
549
550
Popular Tags