Source for java.util.logging.StreamHandler

   1: /* StreamHandler.java --
   2:    A class for publishing log messages to instances of java.io.OutputStream
   3:    Copyright (C) 2002 Free Software Foundation, Inc.
   4: 
   5: This file is part of GNU Classpath.
   6: 
   7: GNU Classpath is free software; you can redistribute it and/or modify
   8: it under the terms of the GNU General Public License as published by
   9: the Free Software Foundation; either version 2, or (at your option)
  10: any later version.
  11: 
  12: GNU Classpath is distributed in the hope that it will be useful, but
  13: WITHOUT ANY WARRANTY; without even the implied warranty of
  14: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  15: General Public License for more details.
  16: 
  17: You should have received a copy of the GNU General Public License
  18: along with GNU Classpath; see the file COPYING.  If not, write to the
  19: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  20: 02110-1301 USA.
  21: 
  22: Linking this library statically or dynamically with other modules is
  23: making a combined work based on this library.  Thus, the terms and
  24: conditions of the GNU General Public License cover the whole
  25: combination.
  26: 
  27: As a special exception, the copyright holders of this library give you
  28: permission to link this library with independent modules to produce an
  29: executable, regardless of the license terms of these independent
  30: modules, and to copy and distribute the resulting executable under
  31: terms of your choice, provided that you also meet, for each linked
  32: independent module, the terms and conditions of the license of that
  33: module.  An independent module is a module which is not derived from
  34: or based on this library.  If you modify this library, you may extend
  35: this exception to your version of the library, but you are not
  36: obligated to do so.  If you do not wish to do so, delete this
  37: exception statement from your version. */
  38: 
  39: 
  40: package java.util.logging;
  41: 
  42: import java.io.OutputStream;
  43: import java.io.OutputStreamWriter;
  44: import java.io.UnsupportedEncodingException;
  45: import java.io.Writer;
  46: 
  47: /**
  48:  * A <code>StreamHandler</code> publishes <code>LogRecords</code> to
  49:  * a instances of <code>java.io.OutputStream</code>.
  50:  *
  51:  * @author Sascha Brawer (brawer@acm.org)
  52:  */
  53: public class StreamHandler
  54:   extends Handler
  55: {
  56:   private OutputStream  out;
  57:   private Writer        writer;
  58: 
  59: 
  60:  /**
  61:   * Indicates the current state of this StreamHandler.  The value
  62:   * should be one of STATE_FRESH, STATE_PUBLISHED, or STATE_CLOSED.
  63:   */
  64:   private int streamState = STATE_FRESH;
  65: 
  66: 
  67:   /**
  68:    * streamState having this value indicates that the StreamHandler
  69:    * has been created, but the publish(LogRecord) method has not been
  70:    * called yet.  If the StreamHandler has been constructed without an
  71:    * OutputStream, writer will be null, otherwise it is set to a
  72:    * freshly created OutputStreamWriter.
  73:    */
  74:   private static final int STATE_FRESH = 0;
  75: 
  76: 
  77:   /**
  78:    * streamState having this value indicates that the publish(LocRecord)
  79:    * method has been called at least once.
  80:    */
  81:   private static final int STATE_PUBLISHED = 1;
  82: 
  83: 
  84:   /**
  85:    * streamState having this value indicates that the close() method
  86:    * has been called.
  87:    */
  88:   private static final int STATE_CLOSED = 2;
  89: 
  90: 
  91:   /**
  92:    * Creates a <code>StreamHandler</code> without an output stream.
  93:    * Subclasses can later use {@link
  94:    * #setOutputStream(java.io.OutputStream)} to associate an output
  95:    * stream with this StreamHandler.
  96:    */
  97:   public StreamHandler()
  98:   {
  99:     this(null, null);
 100:   }
 101: 
 102: 
 103:   /**
 104:    * Creates a <code>StreamHandler</code> that formats log messages
 105:    * with the specified Formatter and publishes them to the specified
 106:    * output stream.
 107:    *
 108:    * @param out the output stream to which the formatted log messages
 109:    *     are published.
 110:    *
 111:    * @param formatter the <code>Formatter</code> that will be used
 112:    *     to format log messages.
 113:    */
 114:   public StreamHandler(OutputStream out, Formatter formatter)
 115:   {
 116:     this(out, "java.util.logging.StreamHandler", Level.INFO,
 117:          formatter, SimpleFormatter.class);
 118:   }
 119: 
 120: 
 121:   StreamHandler(
 122:     OutputStream out,
 123:     String propertyPrefix,
 124:     Level defaultLevel,
 125:     Formatter formatter, Class defaultFormatterClass)
 126:   {
 127:     this.level = LogManager.getLevelProperty(propertyPrefix + ".level",
 128:                                              defaultLevel);
 129: 
 130:     this.filter = (Filter) LogManager.getInstanceProperty(
 131:       propertyPrefix + ".filter",
 132:       /* must be instance of */       Filter.class,
 133:       /* default: new instance of */  null);
 134: 
 135:     if (formatter != null)
 136:       this.formatter = formatter;
 137:     else
 138:       this.formatter = (Formatter) LogManager.getInstanceProperty(
 139:         propertyPrefix + ".formatter",
 140:         /* must be instance of */       Formatter.class,
 141:         /* default: new instance of */  defaultFormatterClass);
 142: 
 143:     try
 144:     {
 145:       String enc = LogManager.getLogManager().getProperty(propertyPrefix
 146:                                                           + ".encoding");
 147: 
 148:       /* make sure enc actually is a valid encoding */
 149:       if ((enc != null) && (enc.length() > 0))
 150:         new String(new byte[0], enc);
 151: 
 152:       this.encoding = enc;
 153:     }
 154:     catch (Exception _)
 155:     {
 156:     }
 157: 
 158:     if (out != null)
 159:     {
 160:       try
 161:       {
 162:         changeWriter(out, getEncoding());
 163:       }
 164:       catch (UnsupportedEncodingException uex)
 165:       {
 166:         /* This should never happen, since the validity of the encoding
 167:          * name has been checked above.
 168:          */
 169:         throw new RuntimeException(uex.getMessage());
 170:       }
 171:     }
 172:   }
 173: 
 174: 
 175:   private void checkOpen()
 176:   {
 177:     if (streamState == STATE_CLOSED)
 178:       throw new IllegalStateException(this.toString() + " has been closed");
 179:   }
 180: 
 181:   private void checkFresh()
 182:   {
 183:     checkOpen();
 184:     if (streamState != STATE_FRESH)
 185:       throw new IllegalStateException("some log records have been published to " + this);
 186:   }
 187: 
 188: 
 189:   private void changeWriter(OutputStream out, String encoding)
 190:     throws UnsupportedEncodingException
 191:   {
 192:     OutputStreamWriter writer;
 193: 
 194:     /* The logging API says that a null encoding means the default
 195:      * platform encoding. However, java.io.OutputStreamWriter needs
 196:      * another constructor for the default platform encoding,
 197:      * passing null would throw an exception.
 198:      */
 199:     if (encoding == null)
 200:       writer = new OutputStreamWriter(out);
 201:     else
 202:       writer = new OutputStreamWriter(out, encoding);
 203: 
 204:     /* Closing the stream has side effects -- do this only after
 205:      * creating a new writer has been successful.
 206:      */
 207:     if ((streamState != STATE_FRESH) || (this.writer != null))
 208:       close();
 209: 
 210:     this.writer = writer;
 211:     this.out = out;
 212:     this.encoding = encoding;
 213:     streamState = STATE_FRESH;
 214:   }
 215: 
 216: 
 217:   /**
 218:    * Sets the character encoding which this handler uses for publishing
 219:    * log records.  The encoding of a <code>StreamHandler</code> must be
 220:    * set before any log records have been published.
 221:    *
 222:    * @param encoding the name of a character encoding, or <code>null</code>
 223:    *            for the default encoding.
 224:    *
 225:    * @throws SecurityException if a security manager exists and
 226:    *     the caller is not granted the permission to control the
 227:    *     the logging infrastructure.
 228:    *
 229:    * @exception IllegalStateException if any log records have been
 230:    *     published to this <code>StreamHandler</code> before.  Please
 231:    *     be aware that this is a pecularity of the GNU implementation.
 232:    *     While the API specification indicates that it is an error
 233:    *     if the encoding is set after records have been published,
 234:    *     it does not mandate any specific behavior for that case.
 235:    */
 236:   public void setEncoding(String encoding)
 237:     throws SecurityException, UnsupportedEncodingException
 238:   {
 239:     /* The inherited implementation first checks whether the invoking
 240:      * code indeed has the permission to control the logging infra-
 241:      * structure, and throws a SecurityException if this was not the
 242:      * case.
 243:      *
 244:      * Next, it verifies that the encoding is supported and throws
 245:      * an UnsupportedEncodingExcpetion otherwise. Finally, it remembers
 246:      * the name of the encoding.
 247:      */
 248:     super.setEncoding(encoding);
 249: 
 250:     checkFresh();
 251: 
 252:     /* If out is null, setEncoding is being called before an output
 253:      * stream has been set. In that case, we need to check that the
 254:      * encoding is valid, and remember it if this is the case.  Since
 255:      * this is exactly what the inherited implementation of
 256:      * Handler.setEncoding does, we can delegate.
 257:      */
 258:     if (out != null)
 259:     {
 260:       /* The logging API says that a null encoding means the default
 261:        * platform encoding. However, java.io.OutputStreamWriter needs
 262:        * another constructor for the default platform encoding, passing
 263:        * null would throw an exception.
 264:        */
 265:       if (encoding == null)
 266:         writer = new OutputStreamWriter(out);
 267:       else
 268:         writer = new OutputStreamWriter(out, encoding);
 269:     }
 270:   }
 271: 
 272: 
 273:   /**
 274:    * Changes the output stream to which this handler publishes
 275:    * logging records.
 276:    *
 277:    * @throws SecurityException if a security manager exists and
 278:    *         the caller is not granted the permission to control
 279:    *         the logging infrastructure.
 280:    *
 281:    * @throws NullPointerException if <code>out</code>
 282:    *         is <code>null</code>.
 283:    */
 284:   protected void setOutputStream(OutputStream out)
 285:     throws SecurityException
 286:   {
 287:     LogManager.getLogManager().checkAccess();
 288: 
 289:     /* Throw a NullPointerException if out is null. */
 290:     out.getClass();
 291: 
 292:     try
 293:     {
 294:       changeWriter(out, getEncoding());
 295:     }
 296:     catch (UnsupportedEncodingException ex)
 297:     {
 298:       /* This seems quite unlikely to happen, unless the underlying
 299:        * implementation of java.io.OutputStreamWriter changes its
 300:        * mind (at runtime) about the set of supported character
 301:        * encodings.
 302:        */
 303:       throw new RuntimeException(ex.getMessage());
 304:     }
 305:   }
 306: 
 307: 
 308:   /**
 309:    * Publishes a <code>LogRecord</code> to the associated output
 310:    * stream, provided the record passes all tests for being loggable.
 311:    * The <code>StreamHandler</code> will localize the message of the
 312:    * log record and substitute any message parameters.
 313:    *
 314:    * <p>Most applications do not need to call this method directly.
 315:    * Instead, they will use use a {@link Logger}, which will create
 316:    * LogRecords and distribute them to registered handlers.
 317:    *
 318:    * <p>In case of an I/O failure, the <code>ErrorManager</code>
 319:    * of this <code>Handler</code> will be informed, but the caller
 320:    * of this method will not receive an exception.
 321:    *
 322:    * <p>If a log record is being published to a
 323:    * <code>StreamHandler</code> that has been closed earlier, the Sun
 324:    * J2SE 1.4 reference can be observed to silently ignore the
 325:    * call. The GNU implementation, however, intentionally behaves
 326:    * differently by informing the <code>ErrorManager</code> associated
 327:    * with this <code>StreamHandler</code>.  Since the condition
 328:    * indicates a programming error, the programmer should be
 329:    * informed. It also seems extremely unlikely that any application
 330:    * would depend on the exact behavior in this rather obscure,
 331:    * erroneous case -- especially since the API specification does not
 332:    * prescribe what is supposed to happen.
 333:    *
 334:    * @param record the log event to be published.
 335:    */
 336:   public void publish(LogRecord record)
 337:   {
 338:     String formattedMessage;
 339: 
 340:     if (!isLoggable(record))
 341:       return;
 342: 
 343:     if (streamState == STATE_FRESH)
 344:     {
 345:       try
 346:       {
 347:         writer.write(formatter.getHead(this));
 348:       }
 349:       catch (java.io.IOException ex)
 350:       {
 351:         reportError(null, ex, ErrorManager.WRITE_FAILURE);
 352:         return;
 353:       }
 354:       catch (Exception ex)
 355:       {
 356:         reportError(null, ex, ErrorManager.GENERIC_FAILURE);
 357:         return;
 358:       }
 359: 
 360:       streamState = STATE_PUBLISHED;
 361:     }
 362: 
 363:     try
 364:     {
 365:       formattedMessage = formatter.format(record);
 366:     }
 367:     catch (Exception ex)
 368:     {
 369:       reportError(null, ex, ErrorManager.FORMAT_FAILURE);
 370:       return;
 371:     }
 372: 
 373:     try
 374:     {
 375:       writer.write(formattedMessage);
 376:     }
 377:     catch (Exception ex)
 378:     {
 379:       reportError(null, ex, ErrorManager.WRITE_FAILURE);
 380:     }
 381:   }
 382: 
 383: 
 384:   /**
 385:    * Checks whether or not a <code>LogRecord</code> would be logged
 386:    * if it was passed to this <code>StreamHandler</code> for publication.
 387:    *
 388:    * <p>The <code>StreamHandler</code> implementation first checks
 389:    * whether a writer is present and the handler's level is greater
 390:    * than or equal to the severity level threshold.  In a second step,
 391:    * if a {@link Filter} has been installed, its {@link
 392:    * Filter#isLoggable(LogRecord) isLoggable} method is
 393:    * invoked. Subclasses of <code>StreamHandler</code> can override
 394:    * this method to impose their own constraints.
 395:    *
 396:    * @param record the <code>LogRecord</code> to be checked.
 397:    *
 398:    * @return <code>true</code> if <code>record</code> would
 399:    *         be published by {@link #publish(LogRecord) publish},
 400:    *         <code>false</code> if it would be discarded.
 401:    *
 402:    * @see #setLevel(Level)
 403:    * @see #setFilter(Filter)
 404:    * @see Filter#isLoggable(LogRecord)
 405:    *
 406:    * @throws NullPointerException if <code>record</code> is
 407:    *         <code>null</code>.  */
 408:   public boolean isLoggable(LogRecord record)
 409:   {
 410:     return (writer != null) && super.isLoggable(record);
 411:   }
 412: 
 413: 
 414:   /**
 415:    * Forces any data that may have been buffered to the underlying
 416:    * output device.
 417:    *
 418:    * <p>In case of an I/O failure, the <code>ErrorManager</code>
 419:    * of this <code>Handler</code> will be informed, but the caller
 420:    * of this method will not receive an exception.
 421:    *
 422:    * <p>If a <code>StreamHandler</code> that has been closed earlier
 423:    * is closed a second time, the Sun J2SE 1.4 reference can be
 424:    * observed to silently ignore the call. The GNU implementation,
 425:    * however, intentionally behaves differently by informing the
 426:    * <code>ErrorManager</code> associated with this
 427:    * <code>StreamHandler</code>.  Since the condition indicates a
 428:    * programming error, the programmer should be informed. It also
 429:    * seems extremely unlikely that any application would depend on the
 430:    * exact behavior in this rather obscure, erroneous case --
 431:    * especially since the API specification does not prescribe what is
 432:    * supposed to happen.
 433:    */
 434:   public void flush()
 435:   {
 436:     try
 437:     {
 438:       checkOpen();
 439:       if (writer != null)
 440:         writer.flush();
 441:     }
 442:     catch (Exception ex)
 443:     {
 444:       reportError(null, ex, ErrorManager.FLUSH_FAILURE);
 445:     }
 446:   }
 447: 
 448: 
 449:   /**
 450:    * Closes this <code>StreamHandler</code> after having forced any
 451:    * data that may have been buffered to the underlying output
 452:    * device.
 453:    *
 454:    * <p>As soon as <code>close</code> has been called,
 455:    * a <code>Handler</code> should not be used anymore. Attempts
 456:    * to publish log records, to flush buffers, or to modify the
 457:    * <code>Handler</code> in any other way may throw runtime
 458:    * exceptions after calling <code>close</code>.</p>
 459:    *
 460:    * <p>In case of an I/O failure, the <code>ErrorManager</code>
 461:    * of this <code>Handler</code> will be informed, but the caller
 462:    * of this method will not receive an exception.</p>
 463:    *
 464:    * <p>If a <code>StreamHandler</code> that has been closed earlier
 465:    * is closed a second time, the Sun J2SE 1.4 reference can be
 466:    * observed to silently ignore the call. The GNU implementation,
 467:    * however, intentionally behaves differently by informing the
 468:    * <code>ErrorManager</code> associated with this
 469:    * <code>StreamHandler</code>.  Since the condition indicates a
 470:    * programming error, the programmer should be informed. It also
 471:    * seems extremely unlikely that any application would depend on the
 472:    * exact behavior in this rather obscure, erroneous case --
 473:    * especially since the API specification does not prescribe what is
 474:    * supposed to happen.
 475:    *
 476:    * @throws SecurityException if a security manager exists and
 477:    *         the caller is not granted the permission to control
 478:    *         the logging infrastructure.
 479:    */
 480:   public void close()
 481:     throws SecurityException
 482:   {
 483:     LogManager.getLogManager().checkAccess();
 484: 
 485:     try
 486:     {
 487:       /* Although  flush also calls checkOpen, it catches
 488:        * any exceptions and reports them to the ErrorManager
 489:        * as flush failures.  However, we want to report
 490:        * a closed stream as a close failure, not as a
 491:        * flush failure here.  Therefore, we call checkOpen()
 492:        * before flush().
 493:        */
 494:       checkOpen();
 495:       flush();
 496: 
 497:       if (writer != null)
 498:       {
 499:         if (formatter != null)
 500:         {
 501:           /* Even if the StreamHandler has never published a record,
 502:            * it emits head and tail upon closing. An earlier version
 503:            * of the GNU Classpath implementation did not emitted
 504:            * anything. However, this had caused XML log files to be
 505:            * entirely empty instead of containing no log records.
 506:            */
 507:           if (streamState == STATE_FRESH)
 508:             writer.write(formatter.getHead(this));
 509:           if (streamState != STATE_CLOSED)
 510:             writer.write(formatter.getTail(this));
 511:         }
 512:         streamState = STATE_CLOSED;
 513:         writer.close();
 514:       }
 515:     }
 516:     catch (Exception ex)
 517:     {
 518:       reportError(null, ex, ErrorManager.CLOSE_FAILURE);
 519:     }
 520:   }
 521: }