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