[Java][log4j]支持同一时候按日期和文件大小切割日志

依据DailyRollingFileAppender和RollingFileAppender改编,支持按日期和文件大小切割日志。 

源文件:

  1. package com.bao.logging;
  2. import java.io.File;
  3. import java.io.IOException;
  4. import java.io.Writer;
  5. import java.text.SimpleDateFormat;
  6. import java.util.Calendar;
  7. import java.util.Date;
  8. import java.util.GregorianCalendar;
  9. import java.util.Locale;
  10. import java.util.TimeZone;
  11. import org.apache.log4j.FileAppender;
  12. import org.apache.log4j.Layout;
  13. import org.apache.log4j.helpers.CountingQuietWriter;
  14. import org.apache.log4j.helpers.LogLog;
  15. import org.apache.log4j.helpers.OptionConverter;
  16. import org.apache.log4j.spi.LoggingEvent;
  17. /**
  18. * MyDailyRollingFileAppender extends {@link FileAppender} so that the underlying
  19. * file is rolled over at a user chosen frequency.
  20. *
  21. * <p>
  22. * The rolling schedule is specified by the <b>DatePattern</b> option. This
  23. * pattern should follow the {@link SimpleDateFormat} conventions. In
  24. * particular, you <em>must</em> escape literal text within a pair of single
  25. * quotes. A formatted version of the date pattern is used as the suffix for the
  26. * rolled file name.
  27. *
  28. * <p>
  29. * For example, if the <b>File</b> option is set to <code>/foo/bar.log</code>
  30. * and the <b>DatePattern</b> set to <code>'.'yyyy-MM-dd</code>, on 2001-02-16
  31. * at midnight, the logging file <code>/foo/bar.log</code> will be copied to
  32. * <code>/foo/bar.log.2001-02-16</code> and logging for 2001-02-17 will continue
  33. * in <code>/foo/bar.log</code> until it rolls over the next day.
  34. *
  35. * <p>
  36. * Is is possible to specify monthly, weekly, half-daily, daily, hourly, or
  37. * minutely rollover schedules.
  38. *
  39. * <p>
  40. * <table border="1" cellpadding="2">
  41. * <tr>
  42. * <th>DatePattern</th>
  43. * <th>Rollover schedule</th>
  44. * <th>Example</th>
  45. *
  46. * <tr>
  47. * <td><code>'.'yyyy-MM</code>
  48. * <td>Rollover at the beginning of each month</td>
  49. *
  50. * <td>At midnight of May 31st, 2002 <code>/foo/bar.log</code> will be copied to
  51. * <code>/foo/bar.log.2002-05</code>. Logging for the month of June will be
  52. * output to <code>/foo/bar.log</code> until it is also rolled over the next
  53. * month.
  54. *
  55. * <tr>
  56. * <td><code>'.'yyyy-ww</code>
  57. *
  58. * <td>Rollover at the first day of each week. The first day of the week depends
  59. * on the locale.</td>
  60. *
  61. * <td>Assuming the first day of the week is Sunday, on Saturday midnight, June
  62. * 9th 2002, the file <i>/foo/bar.log</i> will be copied to
  63. * <i>/foo/bar.log.2002-23</i>. Logging for the 24th week of 2002 will be output
  64. * to <code>/foo/bar.log</code> until it is rolled over the next week.
  65. *
  66. * <tr>
  67. * <td><code>'.'yyyy-MM-dd</code>
  68. *
  69. * <td>Rollover at midnight each day.</td>
  70. *
  71. * <td>At midnight, on March 8th, 2002, <code>/foo/bar.log</code> will be copied
  72. * to <code>/foo/bar.log.2002-03-08</code>. Logging for the 9th day of March
  73. * will be output to <code>/foo/bar.log</code> until it is rolled over the next
  74. * day.
  75. *
  76. * <tr>
  77. * <td><code>'.'yyyy-MM-dd-a</code>
  78. *
  79. * <td>Rollover at midnight and midday of each day.</td>
  80. *
  81. * <td>At noon, on March 9th, 2002, <code>/foo/bar.log</code> will be copied to
  82. * <code>/foo/bar.log.2002-03-09-AM</code>. Logging for the afternoon of the 9th
  83. * will be output to <code>/foo/bar.log</code> until it is rolled over at
  84. * midnight.
  85. *
  86. * <tr>
  87. * <td><code>'.'yyyy-MM-dd-HH</code>
  88. *
  89. * <td>Rollover at the top of every hour.</td>
  90. *
  91. * <td>At approximately 11:00.000 o'clock on March 9th, 2002,
  92. * <code>/foo/bar.log</code> will be copied to
  93. * <code>/foo/bar.log.2002-03-09-10</code>. Logging for the 11th hour of the 9th
  94. * of March will be output to <code>/foo/bar.log</code> until it is rolled over
  95. * at the beginning of the next hour.
  96. *
  97. *
  98. * <tr>
  99. * <td><code>'.'yyyy-MM-dd-HH-mm</code>
  100. *
  101. * <td>Rollover at the beginning of every minute.</td>
  102. *
  103. * <td>At approximately 11:23,000, on March 9th, 2001, <code>/foo/bar.log</code>
  104. * will be copied to <code>/foo/bar.log.2001-03-09-10-22</code>. Logging for the
  105. * minute of 11:23 (9th of March) will be output to <code>/foo/bar.log</code>
  106. * until it is rolled over the next minute.
  107. *
  108. * </table>
  109. *
  110. * <p>
  111. * Do not use the colon ":" character in anywhere in the <b>DatePattern</b>
  112. * option. The text before the colon is interpeted as the protocol specificaion
  113. * of a URL which is probably not what you want.
  114. */
  115. public class MyDailyRollingFileAppender extends FileAppender {
  116. // The code assumes that the following constants are in a increasing
  117. // sequence.
  118. ;
  119. ;
  120. ;
  121. ;
  122. ;
  123. ;
  124. ;
  125. /**
  126. * The default maximum file size is 10MB.
  127. */
  128. * 1024 * 1024;
  129. /**
  130. * There is one backup file by default.
  131. */
  132. ;
  133. /**
  134. * The date pattern. By default, the pattern is set to "'.'yyyy-MM-dd"
  135. * meaning daily rollover.
  136. */
  137. private String datePattern = "'.'yyyy-MM-dd";
  138. /**
  139. * The log file will be renamed to the value of the scheduledFilename
  140. * variable when the next interval is entered. For example, if the rollover
  141. * period is one hour, the log file will be renamed to the value of
  142. * "scheduledFilename" at the beginning of the next hour.
  143. *
  144. * The precise time when a rollover occurs depends on logging activity.
  145. */
  146. private String scheduledFilename;
  147. /**
  148. * The next time we estimate a rollover should occur.
  149. */
  150. ;
  151. Date now = new Date();
  152. SimpleDateFormat sdf;
  153. RollingCalendar rc = new RollingCalendar();
  154. int checkPeriod = TOP_OF_TROUBLE;
  155. // The gmtTimeZone is used only in computeCheckPeriod() method.
  156. static final TimeZone gmtTimeZone = TimeZone.getTimeZone("GMT");
  157. /**
  158. * The default constructor does nothing.
  159. */
  160. public MyDailyRollingFileAppender() {
  161. }
  162. /**
  163. * Instantiate a <code>MyDailyRollingFileAppender</code> and open the file
  164. * designated by <code>filename</code>. The opened filename will become the
  165. * ouput destination for this appender.
  166. */
  167. public MyDailyRollingFileAppender(Layout layout, String filename,
  168. String datePattern) throws IOException {
  169. super(layout, filename, true);
  170. this.datePattern = datePattern;
  171. activateOptions();
  172. }
  173. /**
  174. * Get the maximum size that the output file is allowed to reach before
  175. * being rolled over to backup files.
  176. *
  177. * @since 1.1
  178. */
  179. public long getMaximumFileSize() {
  180. return maxFileSize;
  181. }
  182. /**
  183. * Set the maximum size that the output file is allowed to reach before
  184. * being rolled over to backup files.
  185. *
  186. * <p>
  187. * This method is equivalent to {@link #setMaxFileSize} except that it is
  188. * required for differentiating the setter taking a <code>long</code>
  189. * argument from the setter taking a <code>String</code> argument by the
  190. * JavaBeans {@link java.beans.Introspector Introspector}.
  191. *
  192. * @see #setMaxFileSize(String)
  193. */
  194. public void setMaximumFileSize(long maxFileSize) {
  195. this.maxFileSize = maxFileSize;
  196. }
  197. /**
  198. * Set the maximum size that the output file is allowed to reach before
  199. * being rolled over to backup files.
  200. *
  201. * <p>
  202. * In configuration files, the <b>MaxFileSize</b> option takes an long
  203. * integer in the range 0 - 2^63. You can specify the value with the
  204. * suffixes "KB", "MB" or "GB" so that the integer is interpreted being
  205. * expressed respectively in kilobytes, megabytes or gigabytes. For example,
  206. * the value "10KB" will be interpreted as 10240.
  207. */
  208. public void setMaxFileSize(String value) {
  209. );
  210. }
  211. /**
  212. * Returns the value of the <b>MaxBackupIndex</b> option.
  213. */
  214. public int getMaxBackupIndex() {
  215. return maxBackupIndex;
  216. }
  217. /**
  218. * Set the maximum number of backup files to keep around.
  219. *
  220. * <p>
  221. * The <b>MaxBackupIndex</b> option determines how many backup files are
  222. * kept before the oldest is erased. This option takes a positive integer
  223. * value. If set to zero, then there will be no backup files and the log
  224. * file will be truncated when it reaches <code>MaxFileSize</code>.
  225. */
  226. public void setMaxBackupIndex(int maxBackups) {
  227. this.maxBackupIndex = maxBackups;
  228. }
  229. /**
  230. * The <b>DatePattern</b> takes a string in the same format as expected by
  231. * {@link SimpleDateFormat}. This options determines the rollover schedule.
  232. */
  233. public void setDatePattern(String pattern) {
  234. datePattern = pattern;
  235. }
  236. /** Returns the value of the <b>DatePattern</b> option. */
  237. public String getDatePattern() {
  238. return datePattern;
  239. }
  240. public void activateOptions() {
  241. super.activateOptions();
  242. if (datePattern != null && fileName != null) {
  243. now.setTime(System.currentTimeMillis());
  244. sdf = new SimpleDateFormat(datePattern);
  245. int type = computeCheckPeriod();
  246. printPeriodicity(type);
  247. rc.setType(type);
  248. File file = new File(fileName);
  249. scheduledFilename = fileName
  250. + sdf.format(new Date(file.lastModified()));
  251. } else {
  252. LogLog.error("Either File or DatePattern options are not set for appender ["
  253. + name + "].");
  254. }
  255. }
  256. void printPeriodicity(int type) {
  257. switch (type) {
  258. case TOP_OF_MINUTE:
  259. LogLog.debug("Appender [" + name + "] to be rolled every minute.");
  260. break;
  261. case TOP_OF_HOUR:
  262. LogLog.debug("Appender [" + name
  263. + "] to be rolled on top of every hour.");
  264. break;
  265. case HALF_DAY:
  266. LogLog.debug("Appender [" + name
  267. + "] to be rolled at midday and midnight.");
  268. break;
  269. case TOP_OF_DAY:
  270. LogLog.debug("Appender [" + name + "] to be rolled at midnight.");
  271. break;
  272. case TOP_OF_WEEK:
  273. LogLog.debug("Appender [" + name
  274. + "] to be rolled at start of week.");
  275. break;
  276. case TOP_OF_MONTH:
  277. LogLog.debug("Appender [" + name
  278. + "] to be rolled at start of every month.");
  279. break;
  280. default:
  281. LogLog.warn("Unknown periodicity for appender [" + name + "].");
  282. }
  283. }
  284. // This method computes the roll over period by looping over the
  285. // periods, starting with the shortest, and stopping when the r0 is
  286. // different from from r1, where r0 is the epoch formatted according
  287. // the datePattern (supplied by the user) and r1 is the
  288. // epoch+nextMillis(i) formatted according to datePattern. All date
  289. // formatting is done in GMT and not local format because the test
  290. // logic is based on comparisons relative to 1970-01-01 00:00:00
  291. // GMT (the epoch).
  292. int computeCheckPeriod() {
  293. RollingCalendar rollingCalendar = new RollingCalendar(gmtTimeZone,
  294. Locale.ENGLISH);
  295. // set sate to 1970-01-01 00:00:00 GMT
  296. );
  297. if (datePattern != null) {
  298. for (int i = TOP_OF_MINUTE; i <= TOP_OF_MONTH; i++) {
  299. SimpleDateFormat simpleDateFormat = new SimpleDateFormat(
  300. datePattern);
  301. simpleDateFormat.setTimeZone(gmtTimeZone); // do all date
  302. // formatting in GMT
  303. String r0 = simpleDateFormat.format(epoch);
  304. rollingCalendar.setType(i);
  305. Date next = new Date(rollingCalendar.getNextCheckMillis(epoch));
  306. String r1 = simpleDateFormat.format(next);
  307. // System.out.println("Type = "+i+", r0 = "+r0+", r1 = "+r1);
  308. if (r0 != null && r1 != null && !r0.equals(r1)) {
  309. return i;
  310. }
  311. }
  312. }
  313. return TOP_OF_TROUBLE; // Deliberately head for trouble...
  314. }
  315. /**
  316. * Implements the usual roll over behaviour.
  317. *
  318. * <p>
  319. * If <code>MaxBackupIndex</code> is positive, then files {
  320. * <code>File.1</code>, ..., <code>File.MaxBackupIndex -1</code> are renamed
  321. * to {<code>File.2</code>, ..., <code>File.MaxBackupIndex</code> .
  322. * Moreover, <code>File</code> is renamed <code>File.1</code> and closed. A
  323. * new <code>File</code> is created to receive further log output.
  324. *
  325. * <p>
  326. * If <code>MaxBackupIndex</code> is equal to zero, then the
  327. * <code>File</code> is truncated with no backup files created.
  328. */
  329. public// synchronization not necessary since doAppend is alreasy synched
  330. void sizeRollOver() {
  331. File target;
  332. File file;
  333. LogLog.debug("rolling over count="
  334. + ((CountingQuietWriter) qw).getCount());
  335. LogLog.debug("maxBackupIndex=" + maxBackupIndex);
  336. String datedFilename = fileName + sdf.format(now);
  337. ) {
  338. // Delete the oldest file, to keep Windows happy.
  339. file = new File(datedFilename + '.' + maxBackupIndex);
  340. if (file.exists())
  341. file.delete();
  342. // Map {(maxBackupIndex - 1), ..., 2, 1} to {maxBackupIndex, ..., 3,
  343. // 2}
  344. ; i >= 1; i--) {
  345. file = new File(datedFilename + "." + i);
  346. if (file.exists()) {
  347. ));
  348. LogLog.debug("Renaming file " + file + " to " + target);
  349. file.renameTo(target);
  350. }
  351. }
  352. // Rename fileName to datedFilename.1
  353. );
  354. this.closeFile(); // keep windows happy.
  355. file = new File(fileName);
  356. LogLog.debug("Renaming file " + file + " to " + target);
  357. file.renameTo(target);
  358. ){//infinite number of files
  359. //find the max backup index
  360. ; i < Integer.MAX_VALUE; i++) {
  361. target = new File(datedFilename + "." + i);
  362. if (! target.exists()) {//Rename fileName to datedFilename.i
  363. this.closeFile();
  364. file = new File(fileName);
  365. file.renameTo(target);
  366. LogLog.debug("Renaming file " + file + " to " + target);
  367. break;
  368. }
  369. }
  370. }
  371. try {
  372. // This will also close the file. This is OK since multiple
  373. // close operations are safe.
  374. this.setFile(fileName, false, bufferedIO, bufferSize);
  375. } catch (IOException e) {
  376. LogLog.error("setFile(" + fileName + ", false) call failed.", e);
  377. }
  378. scheduledFilename = datedFilename;
  379. }
  380. public synchronized void setFile(String fileName, boolean append,
  381. boolean bufferedIO, int bufferSize) throws IOException {
  382. super.setFile(fileName, append, this.bufferedIO, this.bufferSize);
  383. if (append) {
  384. File f = new File(fileName);
  385. ((CountingQuietWriter) qw).setCount(f.length());
  386. }
  387. }
  388. protected void setQWForFiles(Writer writer) {
  389. this.qw = new CountingQuietWriter(writer, errorHandler);
  390. }
  391. /**
  392. * Rollover the current file to a new file.
  393. */
  394. void timeRollOver() throws IOException {
  395. /* Compute filename, but only if datePattern is specified */
  396. if (datePattern == null) {
  397. errorHandler.error("Missing DatePattern option in rollOver().");
  398. return;
  399. }
  400. String datedFilename = fileName + sdf.format(now);
  401. // It is too early to roll over because we are still within the
  402. // bounds of the current interval. Rollover will occur once the
  403. // next interval is reached.
  404. if (scheduledFilename.equals(datedFilename)) {
  405. return;
  406. }
  407. // close current file, and rename it to datedFilename
  408. this.closeFile();
  409. File target = new File(scheduledFilename);
  410. if (target.exists()) {
  411. target.delete();
  412. }
  413. File file = new File(fileName);
  414. boolean result = file.renameTo(target);
  415. if (result) {
  416. LogLog.debug(fileName + " -> " + scheduledFilename);
  417. } else {
  418. LogLog.error("Failed to rename [" + fileName + "] to ["
  419. + scheduledFilename + "].");
  420. }
  421. try {
  422. // This will also close the file. This is OK since multiple
  423. // close operations are safe.
  424. super.setFile(fileName, false, this.bufferedIO, this.bufferSize);
  425. } catch (IOException e) {
  426. errorHandler.error("setFile(" + fileName + ", false) call failed.");
  427. }
  428. scheduledFilename = datedFilename;
  429. }
  430. /**
  431. * This method differentiates MyDailyRollingFileAppender from its super class.
  432. *
  433. * <p>
  434. * Before actually logging, this method will check whether it is time to do
  435. * a rollover. If it is, it will schedule the next rollover time and then
  436. * rollover.
  437. * */
  438. protected void subAppend(LoggingEvent event) {
  439. long n = System.currentTimeMillis();
  440. if (n >= nextCheck) {
  441. now.setTime(n);
  442. nextCheck = rc.getNextCheckMillis(now);
  443. try {
  444. timeRollOver();
  445. } catch (IOException ioe) {
  446. LogLog.error("rollOver() failed.", ioe);
  447. }
  448. } else if ((fileName != null)
  449. && ((CountingQuietWriter) qw).getCount() >= maxFileSize) {
  450. sizeRollOver();
  451. }
  452. super.subAppend(event);
  453. }
  454. }
  455. /**
  456. * RollingCalendar is a helper class to MyDailyRollingFileAppender. Given a
  457. * periodicity type and the current time, it computes the start of the next
  458. * interval.
  459. * */
  460. class RollingCalendar extends GregorianCalendar {
  461. int type = MyDailyRollingFileAppender.TOP_OF_TROUBLE;
  462. RollingCalendar() {
  463. super();
  464. }
  465. RollingCalendar(TimeZone tz, Locale locale) {
  466. super(tz, locale);
  467. }
  468. void setType(int type) {
  469. this.type = type;
  470. }
  471. public long getNextCheckMillis(Date now) {
  472. return getNextCheckDate(now).getTime();
  473. }
  474. public Date getNextCheckDate(Date now) {
  475. this.setTime(now);
  476. switch (type) {
  477. case MyDailyRollingFileAppender.TOP_OF_MINUTE:
  478. );
  479. );
  480. );
  481. break;
  482. case MyDailyRollingFileAppender.TOP_OF_HOUR:
  483. );
  484. );
  485. );
  486. );
  487. break;
  488. case MyDailyRollingFileAppender.HALF_DAY:
  489. );
  490. );
  491. );
  492. int hour = get(Calendar.HOUR_OF_DAY);
  493. ) {
  494. );
  495. } else {
  496. );
  497. );
  498. }
  499. break;
  500. case MyDailyRollingFileAppender.TOP_OF_DAY:
  501. );
  502. );
  503. );
  504. );
  505. );
  506. break;
  507. case MyDailyRollingFileAppender.TOP_OF_WEEK:
  508. this.set(Calendar.DAY_OF_WEEK, getFirstDayOfWeek());
  509. );
  510. );
  511. );
  512. );
  513. break;
  514. case MyDailyRollingFileAppender.TOP_OF_MONTH:
  515. );
  516. );
  517. );
  518. );
  519. );
  520. break;
  521. default:
  522. throw new IllegalStateException("Unknown periodicity type.");
  523. }
  524. return getTime();
  525. }
  526. }

使用方法:

  1. <appender name="PROJECT" class="com.bao.logging.MyDailyRollingFileAppender">
  2. <param name="file" value="e:/test.log"/>
  3. <param name="DatePattern" value="'.'yyyy-MM-dd'.log'" />
  4. <param name="append" value="true"/>
  5. <param name="MaxFileSize" value="500MB"/>
  6. <param name="MaxBackupIndex" value="20"/>
  7. <!--         <param name="MaxBackupIndex" value="-1"/> --><!-- 无限的文件数量,index顺序按时间顺序递增 -->
  8. <param name="encoding" value="UTF-8"/>
  9. <param name="threshold" value="info"/>
  10. <layout class="org.apache.log4j.PatternLayout">
  11. <param name="ConversionPattern" value="[%d{dd HH:mm:ss,SSS\} %-5p] [%t] %c{2\} - %m%n"/>
  12. </layout>
  13. </appender>
上一篇:log4j日志配置(按天/按日)


下一篇:log4j配置参数详解——按日志文件大小、日期切分日志文件