StreamHandler.java 17 KB

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