1. /*
  2. * @(#)FileHandler.java 1.34 04/04/05
  3. *
  4. * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package java.util.logging;
  8. import java.io.*;
  9. import java.nio.channels.FileChannel;
  10. import java.nio.channels.FileLock;
  11. import java.security.*;
  12. /**
  13. * Simple file logging <tt>Handler</tt>.
  14. * <p>
  15. * The <tt>FileHandler</tt> can either write to a specified file,
  16. * or it can write to a rotating set of files.
  17. * <p>
  18. * For a rotating set of files, as each file reaches a given size
  19. * limit, it is closed, rotated out, and a new file opened.
  20. * Successively older files are named by adding "0", "1", "2",
  21. * etc into the base filename.
  22. * <p>
  23. * By default buffering is enabled in the IO libraries but each log
  24. * record is flushed out when it is complete.
  25. * <p>
  26. * By default the <tt>XMLFormatter</tt> class is used for formatting.
  27. * <p>
  28. * <b>Configuration:</b>
  29. * By default each <tt>FileHandler</tt> is initialized using the following
  30. * <tt>LogManager</tt> configuration properties. If properties are not defined
  31. * (or have invalid values) then the specified default values are used.
  32. * <ul>
  33. * <li> java.util.logging.FileHandler.level
  34. * specifies the default level for the <tt>Handler</tt>
  35. * (defaults to <tt>Level.ALL</tt>).
  36. * <li> java.util.logging.FileHandler.filter
  37. * specifies the name of a <tt>Filter</tt> class to use
  38. * (defaults to no <tt>Filter</tt>).
  39. * <li> java.util.logging.FileHandler.formatter
  40. * specifies the name of a </tt>Formatter</tt> class to use
  41. * (defaults to <tt>java.util.logging.XMLFormatter</tt>)
  42. * <li> java.util.logging.FileHandler.encoding
  43. * the name of the character set encoding to use (defaults to
  44. * the default platform encoding).
  45. * <li> java.util.logging.FileHandler.limit
  46. * specifies an approximate maximum amount to write (in bytes)
  47. * to any one file. If this is zero, then there is no limit.
  48. * (Defaults to no limit).
  49. * <li> java.util.logging.FileHandler.count
  50. * specifies how many output files to cycle through (defaults to 1).
  51. * <li> java.util.logging.FileHandler.pattern
  52. * specifies a pattern for generating the output file name. See
  53. * below for details. (Defaults to "%h/java%u.log").
  54. * <li> java.util.logging.FileHandler.append
  55. * specifies whether the FileHandler should append onto
  56. * any existing files (defaults to false).
  57. * </ul>
  58. * <p>
  59. * <p>
  60. * A pattern consists of a string that includes the following special
  61. * components that will be replaced at runtime:
  62. * <ul>
  63. * <li> "/" the local pathname separator
  64. * <li> "%t" the system temporary directory
  65. * <li> "%h" the value of the "user.home" system property
  66. * <li> "%g" the generation number to distinguish rotated logs
  67. * <li> "%u" a unique number to resolve conflicts
  68. * <li> "%%" translates to a single percent sign "%"
  69. * </ul>
  70. * If no "%g" field has been specified and the file count is greater
  71. * than one, then the generation number will be added to the end of
  72. * the generated filename, after a dot.
  73. * <p>
  74. * Thus for example a pattern of "%t/java%g.log" with a count of 2
  75. * would typically cause log files to be written on Solaris to
  76. * /var/tmp/java0.log and /var/tmp/java1.log whereas on Windows 95 they
  77. * would be typically written to C:\TEMP\java0.log and C:\TEMP\java1.log
  78. * <p>
  79. * Generation numbers follow the sequence 0, 1, 2, etc.
  80. * <p>
  81. * Normally the "%u" unique field is set to 0. However, if the <tt>FileHandler</tt>
  82. * tries to open the filename and finds the file is currently in use by
  83. * another process it will increment the unique number field and try
  84. * again. This will be repeated until <tt>FileHandler</tt> finds a file name that
  85. * is not currently in use. If there is a conflict and no "%u" field has
  86. * been specified, it will be added at the end of the filename after a dot.
  87. * (This will be after any automatically added generation number.)
  88. * <p>
  89. * Thus if three processes were all trying to log to fred%u.%g.txt then
  90. * they might end up using fred0.0.txt, fred1.0.txt, fred2.0.txt as
  91. * the first file in their rotating sequences.
  92. * <p>
  93. * Note that the use of unique ids to avoid conflicts is only guaranteed
  94. * to work reliably when using a local disk file system.
  95. *
  96. * @version 1.34, 04/05/04
  97. * @since 1.4
  98. */
  99. public class FileHandler extends StreamHandler {
  100. private MeteredStream meter;
  101. private boolean append;
  102. private int limit; // zero => no limit.
  103. private int count;
  104. private String pattern;
  105. private String lockFileName;
  106. private FileOutputStream lockStream;
  107. private File files[];
  108. private static final int MAX_LOCKS = 100;
  109. private static java.util.HashMap locks = new java.util.HashMap();
  110. // A metered stream is a subclass of OutputStream that
  111. // (a) forwards all its output to a target stream
  112. // (b) keeps track of how many bytes have been written
  113. private class MeteredStream extends OutputStream {
  114. OutputStream out;
  115. int written;
  116. MeteredStream(OutputStream out, int written) {
  117. this.out = out;
  118. this.written = written;
  119. }
  120. public void write(int b) throws IOException {
  121. out.write(b);
  122. written++;
  123. }
  124. public void write(byte buff[]) throws IOException {
  125. out.write(buff);
  126. written += buff.length;
  127. }
  128. public void write(byte buff[], int off, int len) throws IOException {
  129. out.write(buff,off,len);
  130. written += len;
  131. }
  132. public void flush() throws IOException {
  133. out.flush();
  134. }
  135. public void close() throws IOException {
  136. out.close();
  137. }
  138. }
  139. private void open(File fname, boolean append) throws IOException {
  140. int len = 0;
  141. if (append) {
  142. len = (int)fname.length();
  143. }
  144. FileOutputStream fout = new FileOutputStream(fname.toString(), append);
  145. BufferedOutputStream bout = new BufferedOutputStream(fout);
  146. meter = new MeteredStream(bout, len);
  147. setOutputStream(meter);
  148. }
  149. // Private method to configure a FileHandler from LogManager
  150. // properties and/or default values as specified in the class
  151. // javadoc.
  152. private void configure() {
  153. LogManager manager = LogManager.getLogManager();
  154. String cname = getClass().getName();
  155. pattern = manager.getStringProperty(cname + ".pattern", "%h/java%u.log");
  156. limit = manager.getIntProperty(cname + ".limit", 0);
  157. if (limit < 0) {
  158. limit = 0;
  159. }
  160. count = manager.getIntProperty(cname + ".count", 1);
  161. if (count <= 0) {
  162. count = 1;
  163. }
  164. append = manager.getBooleanProperty(cname + ".append", false);
  165. setLevel(manager.getLevelProperty(cname + ".level", Level.ALL));
  166. setFilter(manager.getFilterProperty(cname + ".filter", null));
  167. setFormatter(manager.getFormatterProperty(cname + ".formatter", new XMLFormatter()));
  168. try {
  169. setEncoding(manager.getStringProperty(cname +".encoding", null));
  170. } catch (Exception ex) {
  171. try {
  172. setEncoding(null);
  173. } catch (Exception ex2) {
  174. // doing a setEncoding with null should always work.
  175. // assert false;
  176. }
  177. }
  178. }
  179. /**
  180. * Construct a default <tt>FileHandler</tt>. This will be configured
  181. * entirely from <tt>LogManager</tt> properties (or their default values).
  182. * <p>
  183. * @exception IOException if there are IO problems opening the files.
  184. * @exception SecurityException if a security manager exists and if
  185. * the caller does not have <tt>LoggingPermission("control"))</tt>.
  186. * @exception NullPointerException if pattern property is an empty String.
  187. */
  188. public FileHandler() throws IOException, SecurityException {
  189. checkAccess();
  190. configure();
  191. openFiles();
  192. }
  193. /**
  194. * Initialize a <tt>FileHandler</tt> to write to the given filename.
  195. * <p>
  196. * The <tt>FileHandler</tt> is configured based on <tt>LogManager</tt>
  197. * properties (or their default values) except that the given pattern
  198. * argument is used as the filename pattern, the file limit is
  199. * set to no limit, and the file count is set to one.
  200. * <p>
  201. * There is no limit on the amount of data that may be written,
  202. * so use this with care.
  203. *
  204. * @param pattern the name of the output file
  205. * @exception IOException if there are IO problems opening the files.
  206. * @exception SecurityException if a security manager exists and if
  207. * the caller does not have <tt>LoggingPermission("control")</tt>.
  208. * @exception IllegalArgumentException if pattern is an empty string
  209. */
  210. public FileHandler(String pattern) throws IOException, SecurityException {
  211. if (pattern.length() < 1 ) {
  212. throw new IllegalArgumentException();
  213. }
  214. checkAccess();
  215. configure();
  216. this.pattern = pattern;
  217. this.limit = 0;
  218. this.count = 1;
  219. openFiles();
  220. }
  221. /**
  222. * Initialize a <tt>FileHandler</tt> to write to the given filename,
  223. * with optional append.
  224. * <p>
  225. * The <tt>FileHandler</tt> is configured based on <tt>LogManager</tt>
  226. * properties (or their default values) except that the given pattern
  227. * argument is used as the filename pattern, the file limit is
  228. * set to no limit, the file count is set to one, and the append
  229. * mode is set to the given <tt>append</tt> argument.
  230. * <p>
  231. * There is no limit on the amount of data that may be written,
  232. * so use this with care.
  233. *
  234. * @param pattern the name of the output file
  235. * @param append specifies append mode
  236. * @exception IOException if there are IO problems opening the files.
  237. * @exception SecurityException if a security manager exists and if
  238. * the caller does not have <tt>LoggingPermission("control")</tt>.
  239. * @exception IllegalArgumentException if pattern is an empty string
  240. */
  241. public FileHandler(String pattern, boolean append) throws IOException, SecurityException {
  242. if (pattern.length() < 1 ) {
  243. throw new IllegalArgumentException();
  244. }
  245. checkAccess();
  246. configure();
  247. this.pattern = pattern;
  248. this.limit = 0;
  249. this.count = 1;
  250. this.append = append;
  251. openFiles();
  252. }
  253. /**
  254. * Initialize a <tt>FileHandler</tt> to write to a set of files. When
  255. * (approximately) the given limit has been written to one file,
  256. * another file will be opened. The output will cycle through a set
  257. * of count files.
  258. * <p>
  259. * The <tt>FileHandler</tt> is configured based on <tt>LogManager</tt>
  260. * properties (or their default values) except that the given pattern
  261. * argument is used as the filename pattern, the file limit is
  262. * set to the limit argument, and the file count is set to the
  263. * given count argument.
  264. * <p>
  265. * The count must be at least 1.
  266. *
  267. * @param pattern the pattern for naming the output file
  268. * @param limit the maximum number of bytes to write to any one file
  269. * @param count the number of files to use
  270. * @exception IOException if there are IO problems opening the files.
  271. * @exception SecurityException if a security manager exists and if
  272. * the caller does not have <tt>LoggingPermission("control")</tt>.
  273. * @exception IllegalArgumentException if limit < 0, or count < 1.
  274. * @exception IllegalArgumentException if pattern is an empty string
  275. */
  276. public FileHandler(String pattern, int limit, int count)
  277. throws IOException, SecurityException {
  278. if (limit < 0 || count < 1 || pattern.length() < 1) {
  279. throw new IllegalArgumentException();
  280. }
  281. checkAccess();
  282. configure();
  283. this.pattern = pattern;
  284. this.limit = limit;
  285. this.count = count;
  286. openFiles();
  287. }
  288. /**
  289. * Initialize a <tt>FileHandler</tt> to write to a set of files
  290. * with optional append. When (approximately) the given limit has
  291. * been written to one file, another file will be opened. The
  292. * output will cycle through a set of count files.
  293. * <p>
  294. * The <tt>FileHandler</tt> is configured based on <tt>LogManager</tt>
  295. * properties (or their default values) except that the given pattern
  296. * argument is used as the filename pattern, the file limit is
  297. * set to the limit argument, and the file count is set to the
  298. * given count argument, and the append mode is set to the given
  299. * <tt>append</tt> argument.
  300. * <p>
  301. * The count must be at least 1.
  302. *
  303. * @param pattern the pattern for naming the output file
  304. * @param limit the maximum number of bytes to write to any one file
  305. * @param count the number of files to use
  306. * @param append specifies append mode
  307. * @exception IOException if there are IO problems opening the files.
  308. * @exception SecurityException if a security manager exists and if
  309. * the caller does not have <tt>LoggingPermission("control")</tt>.
  310. * @exception IllegalArgumentException if limit < 0, or count < 1.
  311. * @exception IllegalArgumentException if pattern is an empty string
  312. *
  313. */
  314. public FileHandler(String pattern, int limit, int count, boolean append)
  315. throws IOException, SecurityException {
  316. if (limit < 0 || count < 1 || pattern.length() < 1) {
  317. throw new IllegalArgumentException();
  318. }
  319. checkAccess();
  320. configure();
  321. this.pattern = pattern;
  322. this.limit = limit;
  323. this.count = count;
  324. this.append = append;
  325. openFiles();
  326. }
  327. // Private method to open the set of output files, based on the
  328. // configured instance variables.
  329. private void openFiles() throws IOException {
  330. LogManager manager = LogManager.getLogManager();
  331. manager.checkAccess();
  332. if (count < 1) {
  333. throw new IllegalArgumentException("file count = " + count);
  334. }
  335. if (limit < 0) {
  336. limit = 0;
  337. }
  338. // We register our own ErrorManager during initialization
  339. // so we can record exceptions.
  340. InitializationErrorManager em = new InitializationErrorManager();
  341. setErrorManager(em);
  342. // Create a lock file. This grants us exclusive access
  343. // to our set of output files, as long as we are alive.
  344. int unique = -1;
  345. for (;;) {
  346. unique++;
  347. if (unique > MAX_LOCKS) {
  348. throw new IOException("Couldn't get lock for " + pattern);
  349. }
  350. // Generate a lock file name from the "unique" int.
  351. lockFileName = generate(pattern, 0, unique).toString() + ".lck";
  352. // Now try to lock that filename.
  353. // Because some systems (e.g. Solaris) can only do file locks
  354. // between processes (and not within a process), we first check
  355. // if we ourself already have the file locked.
  356. synchronized(locks) {
  357. if (locks.get(lockFileName) != null) {
  358. // We already own this lock, for a different FileHandler
  359. // object. Try again.
  360. continue;
  361. }
  362. FileChannel fc;
  363. try {
  364. lockStream = new FileOutputStream(lockFileName);
  365. fc = lockStream.getChannel();
  366. } catch (IOException ix) {
  367. // We got an IOException while trying to open the file.
  368. // Try the next file.
  369. continue;
  370. }
  371. try {
  372. FileLock fl = fc.tryLock();
  373. if (fl == null) {
  374. // We failed to get the lock. Try next file.
  375. continue;
  376. }
  377. // We got the lock OK.
  378. } catch (IOException ix) {
  379. // We got an IOException while trying to get the lock.
  380. // This normally indicates that locking is not supported
  381. // on the target directory. We have to proceed without
  382. // getting a lock. Drop through.
  383. }
  384. // We got the lock. Remember it.
  385. locks.put(lockFileName, lockFileName);
  386. break;
  387. }
  388. }
  389. files = new File[count];
  390. for (int i = 0; i < count; i++) {
  391. files[i] = generate(pattern, i, unique);
  392. }
  393. // Create the initial log file.
  394. if (append) {
  395. open(files[0], true);
  396. } else {
  397. rotate();
  398. }
  399. // Did we detect any exceptions during initialization?
  400. Exception ex = em.lastException;
  401. if (ex != null) {
  402. if (ex instanceof IOException) {
  403. throw (IOException) ex;
  404. } else if (ex instanceof SecurityException) {
  405. throw (SecurityException) ex;
  406. } else {
  407. throw new IOException("Exception: " + ex);
  408. }
  409. }
  410. // Install the normal default ErrorManager.
  411. setErrorManager(new ErrorManager());
  412. }
  413. // Generate a filename from a pattern.
  414. private File generate(String pattern, int generation, int unique) throws IOException {
  415. File file = null;
  416. String word = "";
  417. int ix = 0;
  418. boolean sawg = false;
  419. boolean sawu = false;
  420. while (ix < pattern.length()) {
  421. char ch = pattern.charAt(ix);
  422. ix++;
  423. char ch2 = 0;
  424. if (ix < pattern.length()) {
  425. ch2 = Character.toLowerCase(pattern.charAt(ix));
  426. }
  427. if (ch == '/') {
  428. if (file == null) {
  429. file = new File(word);
  430. } else {
  431. file = new File(file, word);
  432. }
  433. word = "";
  434. continue;
  435. } else if (ch == '%') {
  436. if (ch2 == 't') {
  437. String tmpDir = System.getProperty("java.io.tmpdir");
  438. if (tmpDir == null) {
  439. tmpDir = System.getProperty("user.home");
  440. }
  441. file = new File(tmpDir);
  442. ix++;
  443. word = "";
  444. continue;
  445. } else if (ch2 == 'h') {
  446. file = new File(System.getProperty("user.home"));
  447. if (isSetUID()) {
  448. // Ok, we are in a set UID program. For safety's sake
  449. // we disallow attempts to open files relative to %h.
  450. throw new IOException("can't use %h in set UID program");
  451. }
  452. ix++;
  453. word = "";
  454. continue;
  455. } else if (ch2 == 'g') {
  456. word = word + generation;
  457. sawg = true;
  458. ix++;
  459. continue;
  460. } else if (ch2 == 'u') {
  461. word = word + unique;
  462. sawu = true;
  463. ix++;
  464. continue;
  465. } else if (ch2 == '%') {
  466. word = word + "%";
  467. ix++;
  468. continue;
  469. }
  470. }
  471. word = word + ch;
  472. }
  473. if (count > 1 && !sawg) {
  474. word = word + "." + generation;
  475. }
  476. if (unique > 0 && !sawu) {
  477. word = word + "." + unique;
  478. }
  479. if (word.length() > 0) {
  480. if (file == null) {
  481. file = new File(word);
  482. } else {
  483. file = new File(file, word);
  484. }
  485. }
  486. return file;
  487. }
  488. // Rotate the set of output files
  489. private synchronized void rotate() {
  490. Level oldLevel = getLevel();
  491. setLevel(Level.OFF);
  492. super.close();
  493. for (int i = count-2; i >= 0; i--) {
  494. File f1 = files[i];
  495. File f2 = files[i+1];
  496. if (f1.exists()) {
  497. if (f2.exists()) {
  498. f2.delete();
  499. }
  500. f1.renameTo(f2);
  501. }
  502. }
  503. try {
  504. open(files[0], false);
  505. } catch (IOException ix) {
  506. // We don't want to throw an exception here, but we
  507. // report the exception to any registered ErrorManager.
  508. reportError(null, ix, ErrorManager.OPEN_FAILURE);
  509. }
  510. setLevel(oldLevel);
  511. }
  512. /**
  513. * Format and publish a <tt>LogRecord</tt>.
  514. *
  515. * @param record description of the log event. A null record is
  516. * silently ignored and is not published
  517. */
  518. public synchronized void publish(LogRecord record) {
  519. if (!isLoggable(record)) {
  520. return;
  521. }
  522. super.publish(record);
  523. flush();
  524. if (limit > 0 && meter.written >= limit) {
  525. // We performed access checks in the "init" method to make sure
  526. // we are only initialized from trusted code. So we assume
  527. // it is OK to write the target files, even if we are
  528. // currently being called from untrusted code.
  529. // So it is safe to raise privilege here.
  530. AccessController.doPrivileged(new PrivilegedAction() {
  531. public Object run() {
  532. rotate();
  533. return null;
  534. }
  535. });
  536. }
  537. }
  538. /**
  539. * Close all the files.
  540. *
  541. * @exception SecurityException if a security manager exists and if
  542. * the caller does not have <tt>LoggingPermission("control")</tt>.
  543. */
  544. public synchronized void close() throws SecurityException {
  545. super.close();
  546. // Unlock any lock file.
  547. if (lockFileName == null) {
  548. return;
  549. }
  550. try {
  551. // Closing the lock file's FileOutputStream will close
  552. // the underlying channel and free any locks.
  553. lockStream.close();
  554. } catch (Exception ex) {
  555. // Problems closing the stream. Punt.
  556. }
  557. synchronized(locks) {
  558. locks.remove(lockFileName);
  559. }
  560. new File(lockFileName).delete();
  561. lockFileName = null;
  562. lockStream = null;
  563. }
  564. private static class InitializationErrorManager extends ErrorManager {
  565. Exception lastException;
  566. public void error(String msg, Exception ex, int code) {
  567. lastException = ex;
  568. }
  569. }
  570. // Private native method to check if we are in a set UID program.
  571. private static native boolean isSetUID();
  572. }