Home | History | Annotate | Download | only in logging
      1 /*
      2  * Licensed to the Apache Software Foundation (ASF) under one or more
      3  * contributor license agreements.  See the NOTICE file distributed with
      4  * this work for additional information regarding copyright ownership.
      5  * The ASF licenses this file to You under the Apache License, Version 2.0
      6  * (the "License"); you may not use this file except in compliance with
      7  * the License.  You may obtain a copy of the License at
      8  *
      9  *     http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  * Unless required by applicable law or agreed to in writing, software
     12  * distributed under the License is distributed on an "AS IS" BASIS,
     13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  * See the License for the specific language governing permissions and
     15  * limitations under the License.
     16  */
     17 
     18 package java.util.logging;
     19 
     20 import java.io.OutputStream;
     21 import java.io.OutputStreamWriter;
     22 import java.io.UnsupportedEncodingException;
     23 import java.io.Writer;
     24 
     25 /**
     26  * A {@code StreamHandler} object writes log messages to an output stream, that
     27  * is, objects of the class {@link java.io.OutputStream}.
     28  * <p>
     29  * A {@code StreamHandler} object reads the following properties from the log
     30  * manager to initialize itself. A default value will be used if a property is
     31  * not found or has an invalid value.
     32  * <ul>
     33  * <li>java.util.logging.StreamHandler.encoding specifies the encoding this
     34  * handler will use to encode log messages. Default is the encoding used by the
     35  * current platform.
     36  * <li>java.util.logging.StreamHandler.filter specifies the name of the filter
     37  * class to be associated with this handler. No <code>Filter</code> is used by
     38  * default.
     39  * <li>java.util.logging.StreamHandler.formatter specifies the name of the
     40  * formatter class to be associated with this handler. Default is
     41  * {@code java.util.logging.SimpleFormatter}.
     42  * <li>java.util.logging.StreamHandler.level specifies the logging level.
     43  * Defaults is {@code Level.INFO}.
     44  * </ul>
     45  * <p>
     46  * This class is not thread-safe.
     47  */
     48 public class StreamHandler extends Handler {
     49 
     50     // the output stream this handler writes to
     51     private OutputStream os;
     52 
     53     // the writer that writes to the output stream
     54     private Writer writer;
     55 
     56     // the flag indicating whether the writer has been initialized
     57     private boolean writerNotInitialized;
     58 
     59     /**
     60      * Constructs a {@code StreamHandler} object. The new stream handler
     61      * does not have an associated output stream.
     62      */
     63     public StreamHandler() {
     64         initProperties("INFO", null, "java.util.logging.SimpleFormatter", null);
     65         this.os = null;
     66         this.writer = null;
     67         this.writerNotInitialized = true;
     68     }
     69 
     70     /**
     71      * Constructs a {@code StreamHandler} object with the supplied output
     72      * stream. Default properties are read.
     73      *
     74      * @param os
     75      *            the output stream this handler writes to.
     76      */
     77     StreamHandler(OutputStream os) {
     78         this();
     79         this.os = os;
     80     }
     81 
     82     /**
     83      * Constructs a {@code StreamHandler} object. The specified default values
     84      * will be used if the corresponding properties are not found in the log
     85      * manager's properties.
     86      */
     87     StreamHandler(String defaultLevel, String defaultFilter,
     88             String defaultFormatter, String defaultEncoding) {
     89         initProperties(defaultLevel, defaultFilter, defaultFormatter,
     90                 defaultEncoding);
     91         this.os = null;
     92         this.writer = null;
     93         this.writerNotInitialized = true;
     94     }
     95 
     96     /**
     97      * Constructs a {@code StreamHandler} object with the supplied output stream
     98      * and formatter.
     99      *
    100      * @param os
    101      *            the output stream this handler writes to.
    102      * @param formatter
    103      *            the formatter this handler uses to format the output.
    104      * @throws NullPointerException
    105      *             if {@code os} or {@code formatter} is {@code null}.
    106      */
    107     public StreamHandler(OutputStream os, Formatter formatter) {
    108         this();
    109         if (os == null) {
    110             throw new NullPointerException("os == null");
    111         }
    112         if (formatter == null) {
    113             throw new NullPointerException("formatter == null");
    114         }
    115         this.os = os;
    116         internalSetFormatter(formatter);
    117     }
    118 
    119     // initialize the writer
    120     private void initializeWriter() {
    121         this.writerNotInitialized = false;
    122         if (getEncoding() == null) {
    123             this.writer = new OutputStreamWriter(this.os);
    124         } else {
    125             try {
    126                 this.writer = new OutputStreamWriter(this.os, getEncoding());
    127             } catch (UnsupportedEncodingException e) {
    128                 /*
    129                  * Should not happen because it's checked in
    130                  * super.initProperties().
    131                  */
    132             }
    133         }
    134         write(getFormatter().getHead(this));
    135     }
    136 
    137     // Write a string to the output stream.
    138     private void write(String s) {
    139         try {
    140             this.writer.write(s);
    141         } catch (Exception e) {
    142             getErrorManager().error("Exception occurred when writing to the output stream", e,
    143                     ErrorManager.WRITE_FAILURE);
    144         }
    145     }
    146 
    147     /**
    148      * Sets the output stream this handler writes to. Note it does nothing else.
    149      *
    150      * @param newOs
    151      *            the new output stream
    152      */
    153     void internalSetOutputStream(OutputStream newOs) {
    154         this.os = newOs;
    155     }
    156 
    157     /**
    158      * Sets the output stream this handler writes to. If there's an existing
    159      * output stream, the tail string of the associated formatter will be
    160      * written to it. Then it will be flushed, closed and replaced with
    161      * {@code os}.
    162      *
    163      * @param os
    164      *            the new output stream.
    165      * @throws NullPointerException
    166      *             if {@code os} is {@code null}.
    167      */
    168     protected void setOutputStream(OutputStream os) {
    169         if (os == null) {
    170             throw new NullPointerException();
    171         }
    172         LogManager.getLogManager().checkAccess();
    173         close(true);
    174         this.writer = null;
    175         this.os = os;
    176         this.writerNotInitialized = true;
    177     }
    178 
    179     /**
    180      * Sets the character encoding used by this handler. A {@code null} value
    181      * indicates that the default encoding should be used.
    182      *
    183      * @param encoding
    184      *            the character encoding to set.
    185      * @throws UnsupportedEncodingException
    186      *             if the specified encoding is not supported by the runtime.
    187      */
    188     @Override
    189     public void setEncoding(String encoding) throws UnsupportedEncodingException {
    190         // flush first before set new encoding
    191         this.flush();
    192         super.setEncoding(encoding);
    193         // renew writer only if the writer exists
    194         if (this.writer != null) {
    195             if (getEncoding() == null) {
    196                 this.writer = new OutputStreamWriter(this.os);
    197             } else {
    198                 try {
    199                     this.writer = new OutputStreamWriter(this.os, getEncoding());
    200                 } catch (UnsupportedEncodingException e) {
    201                     /*
    202                      * Should not happen because it's checked in
    203                      * super.initProperties().
    204                      */
    205                     throw new AssertionError(e);
    206                 }
    207             }
    208         }
    209     }
    210 
    211     /**
    212      * Closes this handler, but the underlying output stream is only closed if
    213      * {@code closeStream} is {@code true}. Security is not checked.
    214      *
    215      * @param closeStream
    216      *            whether to close the underlying output stream.
    217      */
    218     void close(boolean closeStream) {
    219         if (this.os != null) {
    220             if (this.writerNotInitialized) {
    221                 initializeWriter();
    222             }
    223             write(getFormatter().getTail(this));
    224             try {
    225                 this.writer.flush();
    226                 if (closeStream) {
    227                     this.writer.close();
    228                     this.writer = null;
    229                     this.os = null;
    230                 }
    231             } catch (Exception e) {
    232                 getErrorManager().error("Exception occurred when closing the output stream", e,
    233                         ErrorManager.CLOSE_FAILURE);
    234             }
    235         }
    236     }
    237 
    238     /**
    239      * Closes this handler. The tail string of the formatter associated with
    240      * this handler is written out. A flush operation and a subsequent close
    241      * operation is then performed upon the output stream. Client applications
    242      * should not use a handler after closing it.
    243      */
    244     @Override
    245     public void close() {
    246         LogManager.getLogManager().checkAccess();
    247         close(true);
    248     }
    249 
    250     /**
    251      * Flushes any buffered output.
    252      */
    253     @Override
    254     public void flush() {
    255         if (this.os != null) {
    256             try {
    257                 if (this.writer != null) {
    258                     this.writer.flush();
    259                 } else {
    260                     this.os.flush();
    261                 }
    262             } catch (Exception e) {
    263                 getErrorManager().error("Exception occurred when flushing the output stream",
    264                         e, ErrorManager.FLUSH_FAILURE);
    265             }
    266         }
    267     }
    268 
    269     /**
    270      * Accepts a logging request. The log record is formatted and written to the
    271      * output stream if the following three conditions are met:
    272      * <ul>
    273      * <li>the supplied log record has at least the required logging level;
    274      * <li>the supplied log record passes the filter associated with this
    275      * handler, if any;
    276      * <li>the output stream associated with this handler is not {@code null}.
    277      * </ul>
    278      * If it is the first time a log record is written out, the head string of
    279      * the formatter associated with this handler is written out first.
    280      *
    281      * @param record
    282      *            the log record to be logged.
    283      */
    284     @Override
    285     public synchronized void publish(LogRecord record) {
    286         try {
    287             if (this.isLoggable(record)) {
    288                 if (this.writerNotInitialized) {
    289                     initializeWriter();
    290                 }
    291                 String msg = null;
    292                 try {
    293                     msg = getFormatter().format(record);
    294                 } catch (Exception e) {
    295                     getErrorManager().error("Exception occurred when formatting the log record",
    296                             e, ErrorManager.FORMAT_FAILURE);
    297                 }
    298                 write(msg);
    299             }
    300         } catch (Exception e) {
    301             getErrorManager().error("Exception occurred when logging the record", e,
    302                     ErrorManager.GENERIC_FAILURE);
    303         }
    304     }
    305 
    306     /**
    307      * Determines whether the supplied log record needs to be logged. The
    308      * logging levels are checked as well as the filter. The output stream of
    309      * this handler is also checked. If it is {@code null}, this method returns
    310      * {@code false}.
    311      * <p>
    312      * Notice : Case of no output stream will return {@code false}.
    313      *
    314      * @param record
    315      *            the log record to be checked.
    316      * @return {@code true} if {@code record} needs to be logged, {@code false}
    317      *         otherwise.
    318      */
    319     @Override
    320     public boolean isLoggable(LogRecord record) {
    321         if (record == null) {
    322             return false;
    323         }
    324         if (this.os != null && super.isLoggable(record)) {
    325             return true;
    326         }
    327         return false;
    328     }
    329 }
    330