项目需要使用log4j来记录日志,要求是【按设定迭代日期删除过期日志文件,同一天内按设定大小分文件】。废话不说上代码
package org.apache.log4j; /** * Copyright (C) The Apache Software Foundation. All rights reserved. * * This software is published under the terms of the Apache Software * License version 1.1, a copy of which has been included with this * distribution in the LICENSE.APL file. */ import java.io.File; import java.io.IOException; import java.io.Writer; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; import org.apache.commons.lang.time.DateUtils; import org.apache.log4j.helpers.CountingQuietWriter; import org.apache.log4j.helpers.LogLog; import org.apache.log4j.helpers.OptionConverter; import org.apache.log4j.spi.LoggingEvent; /*** * <p> * CompositeRollingAppender combines RollingFileAppender and DailyRollingFileAppender<br> * It can function as either or do both at the same time (making size based rolling files like RollingFileAppender until * a data/time boundary is crossed at which time it rolls all of those files as per the DailyRollingFileAppender) based * on the setting for <code>rollingStyle</code>.<br> * <br> * To use CompositeRollingAppender to roll log files as they reach a certain size (like RollingFileAppender), set * rollingStyle=1 (@see config.size)<br> * To use CompositeRollingAppender to roll log files at certain time intervals (daily for example), set rollingStyle=2 * and a datePattern (@see config.time)<br> * To have CompositeRollingAppender roll log files at a certain size AND rename those according to time intervals, set * rollingStyle=3 (@see config.composite)<br> * * <p> * A of few additional optional features have been added:<br> * -- Attach date pattern for current log file (@see staticLogFileName)<br> * -- Backup number increments for newer files (@see countDirection)<br> * -- Infinite number of backups by file size (@see maxSizeRollBackups)<br> * <br> * <p> * A few notes and warnings: For large or infinite number of backups countDirection > 0 is highly recommended, with * staticLogFileName = false if time based rolling is also used -- this will reduce the number of file renamings to few * or none. Changing staticLogFileName or countDirection without clearing the directory could have nasty side effects. * If Date/Time based rolling is enabled, CompositeRollingAppender will attempt to roll existing files in the directory * without a date/time tag based on the last modified date of the base log files last modification.<br> * <br> * <p> * A maximum number of backups based on date/time boundries would be nice but is not yet implemented.<br> * * @author Kevin Steppe * @author Heinz Richter * @author Eirik Lygre * @author Ceki Gülcü */ /** * 实现 按天 删除日志 * 按指定大小分文件记录 * * @author zhangjianying * */ public class CompositeRollingAppender extends org.apache.log4j.FileAppender { // The code assumes that the following 'time' constants are in a increasing // sequence. static final int TOP_OF_TROUBLE = -1; static final int TOP_OF_MINUTE = 0; static final int TOP_OF_HOUR = 1; static final int HALF_DAY = 2; static final int TOP_OF_DAY = 3; static final int TOP_OF_WEEK = 4; static final int TOP_OF_MONTH = 5; /*** Style of rolling to use */ static final int BY_SIZE = 1; static final int BY_DATE = 2; static final int BY_COMPOSITE = 3; // Not currently used static final String S_BY_SIZE = "Size"; static final String S_BY_DATE = "Date"; static final String S_BY_COMPOSITE = "Composite"; /*** * The date pattern. By default, the pattern is set to "'.'yyyy-MM-dd" meaning daily rollover. */ private String datePattern = "'.'yyyy-MM-dd"; /*** * The actual formatted filename that is currently being written to or will be the file transferred to on roll over * (based on staticLogFileName). */ private String scheduledFilename = null; /*** The timestamp when we shall next recompute the filename. */ private long nextCheck = System.currentTimeMillis() - 1; /*** Holds date of last roll over */ Date now = new Date(); SimpleDateFormat sdf; /*** Helper class to determine next rollover time */ RollingCalendar rc = new RollingCalendar(); /*** Current period for roll overs */ int checkPeriod = TOP_OF_TROUBLE; /*** The default maximum file size is 10MB. */ protected long maxFileSize = 10 * 1024 * 1024; /*** There is zero backup files by default. */ protected int maxSizeRollBackups = 0; /*** How many sized based backups have been made so far */ protected int curSizeRollBackups = 0; /*** not yet implemented */ protected int maxTimeRollBackups = -1; protected int curTimeRollBackups = 0; /*** * By default newer files have lower numbers. (countDirection < 0) ie. log.1 is most recent, log.5 is the 5th * backup, etc... countDirection > 0 does the opposite ie. log.1 is the first backup made, log.5 is the 5th backup * made, etc. For infinite backups use countDirection > 0 to reduce rollOver costs. */ protected int countDirection = -1; /*** Style of rolling to Use. BY_SIZE (1), BY_DATE(2), BY COMPOSITE(3) */ protected int rollingStyle = BY_COMPOSITE; protected boolean rollDate = true; protected boolean rollSize = true; /*** * By default file.log is always the current file. Optionally file.log.yyyy-mm-dd for current formated datePattern * can by the currently logging file (or file.log.curSizeRollBackup or even file.log.yyyy-mm-dd.curSizeRollBackup) * This will make time based roll overs with a large number of backups much faster -- it won't have to rename all * the backups! */ protected boolean staticLogFileName = true; /*** FileName provided in configuration. Used for rolling properly */ protected String baseFileName; /*** The default constructor does nothing. */ public CompositeRollingAppender() { } /*** * Instantiate a <code>CompositeRollingAppender</code> and open the file designated by <code>filename</code>. The * opened filename will become the ouput destination for this appender. */ public CompositeRollingAppender(Layout layout, String filename, String datePattern) throws IOException { this(layout, filename, datePattern, true); } /*** * Instantiate a CompositeRollingAppender and open the file designated by <code>filename</code>. The opened filename * will become the ouput destination for this appender. * * <p> * If the <code>append</code> parameter is true, the file will be appended to. Otherwise, the file desginated by * <code>filename</code> will be truncated before being opened. */ public CompositeRollingAppender(Layout layout, String filename, boolean append) throws IOException { super(layout, filename, append); } /*** * Instantiate a CompositeRollingAppender and open the file designated by <code>filename</code>. The opened filename * will become the ouput destination for this appender. */ public CompositeRollingAppender(Layout layout, String filename, String datePattern, boolean append) throws IOException { super(layout, filename, append); this.datePattern = datePattern; activateOptions(); } /*** * Instantiate a CompositeRollingAppender and open the file designated by <code>filename</code>. The opened filename * will become the output destination for this appender. * * <p> * The file will be appended to. DatePattern is default. */ public CompositeRollingAppender(Layout layout, String filename) throws IOException { super(layout, filename); } /*** * The <b>DatePattern</b> takes a string in the same format as expected by {@link SimpleDateFormat}. This options * determines the rollover schedule. */ public void setDatePattern(String pattern) { datePattern = pattern; } /*** Returns the value of the <b>DatePattern</b> option. */ public String getDatePattern() { return datePattern; } /*** * Returns the value of the <b>maxSizeRollBackups</b> option. */ public int getMaxSizeRollBackups() { return maxSizeRollBackups; } /*** * Get the maximum size that the output file is allowed to reach before being rolled over to backup files. * * @since 1.1 */ public long getMaximumFileSize() { return maxFileSize; } /*** * <p> * Set the maximum number of backup files to keep around based on file size. * * <p> * The <b>MaxSizeRollBackups</b> option determines how many backup files are kept before the oldest is erased. This * option takes an integer value. If set to zero, then there will be no backup files and the log file will be * truncated when it reaches <code>MaxFileSize</code>. If a negative number is supplied then no deletions will be * made. Note that this could result in very slow performance as a large number of files are rolled over unless * {@link #setCountDirection} up is used. * * <p> * The maximum applys to -each- time based group of files and -not- the total. Using a daily roll the maximum total * files would be (#days run) * (maxSizeRollBackups) */ public void setMaxSizeRollBackups(int maxBackups) { maxSizeRollBackups = maxBackups; } /*** * Set the maximum size that the output file is allowed to reach before being rolled over to backup files. * * <p> * This method is equivalent to {@link #setMaxFileSize} except that it is required for differentiating the setter * taking a <code>long</code> argument from the setter taking a <code>String</code> argument by the JavaBeans * {@link java.beans.Introspector Introspector}. * * @see #setMaxFileSize(String) */ public void setMaxFileSize(String maxFileSize) { //默认 10M Long size = 1024 * 1024 * 10L ; maxFileSize = maxFileSize.toLowerCase(); if(maxFileSize.endsWith("kb")){ size = Long.valueOf(maxFileSize.replaceAll("kb", ""))*1024; }else if (maxFileSize.endsWith("m")){ size = Long.valueOf(maxFileSize.replaceAll("m", ""))*1024*1024; }else { size = 1024 * 1024 * 10L ; } this.maxFileSize = size; } /*** * Set the maximum size that the output file is allowed to reach before being rolled over to backup files. * * <p> * This method is equivalent to {@link #setMaxFileSize} except that it is required for differentiating the setter * taking a <code>long</code> argument from the setter taking a <code>String</code> argument by the JavaBeans * {@link java.beans.Introspector Introspector}. * * @see #setMaxFileSize(String) */ public void setMaximumFileSize(long maxFileSize) { this.maxFileSize = maxFileSize; } protected void setQWForFiles(Writer writer) { qw = new CountingQuietWriter(writer, errorHandler); } // Taken verbatum from DailyRollingFileAppender int computeCheckPeriod() { RollingCalendar c = new RollingCalendar(); // set sate to 1970-01-01 00:00:00 GMT Date epoch = new Date(0); if (datePattern != null) { for (int i = TOP_OF_MINUTE; i <= TOP_OF_MONTH; i++) { String r0 = sdf.format(epoch); c.setType(i); Date next = new Date(c.getNextCheckMillis(epoch)); String r1 = sdf.format(next); // LogLog.debug("Type = "+i+", r0 = "+r0+", r1 = "+r1); if (r0 != null && r1 != null && !r0.equals(r1)) { return i; } } } return TOP_OF_TROUBLE; // Deliberately head for trouble... } // Now for the new stuff /*** * Handles append time behavior for CompositeRollingAppender. This checks if a roll over either by date (checked * first) or time (checked second) is need and then appends to the file last. */ protected void subAppend(LoggingEvent event) { if (rollDate) { long n = System.currentTimeMillis(); if (n >= nextCheck) { now.setTime(n); nextCheck = rc.getNextCheckMillis(now); System.out.println("下次检查:" + nextCheck); rollOverTime(); /* * 这里我故意让程序 根据 curTimeRollBackups 来删除文件 * 也就是说 程序必须是连续的 正常的(无人为重启) 的运行到 curTimeRollBackups > maxTimeRollBackups * 的时间 才会根据 maxTimeRollBackups 去删除日志 * 如果中途重启过,那么就会留下 从上次启动至 maxTimeRollBackups 这段时间的日志,这段时间的日志 * 只能人为删除 */ // 如果开启过期日期删除 if (maxTimeRollBackups != -1) { if (countDirection < 0) { // Delete the oldest file, to keep Windows happy. if (curTimeRollBackups > maxTimeRollBackups) { String pathDir =fileName.substring(0, getEndStart(fileName,'/')); String filterName =getDateString(new Date(), -1* (maxTimeRollBackups+1)); File srcFolder = new File(pathDir); CompositeFilenameFilter compositeFilenameFilter = new CompositeFilenameFilter(filterName); if(srcFolder.isDirectory()){ File[] files = srcFolder.listFiles(compositeFilenameFilter); for (File file : files) { file.delete(); } } curTimeRollBackups--; } } } } } if (rollSize) { if ((fileName != null) && ((CountingQuietWriter)qw).getCount() >= maxFileSize) { rollOverSize(); } } super.subAppend(event); } public void setFile(String file) { baseFileName = file.trim(); fileName = file.trim(); } /*** * Creates and opens the file for logging. If <code>staticLogFileName</code> is false then the fully qualified name * is determined and used. */ public synchronized void setFile(String fileName, boolean append) throws IOException { if (!staticLogFileName) { scheduledFilename = fileName = fileName.trim() + sdf.format(now); if (countDirection > 0) { scheduledFilename = fileName = fileName + '.' + (++curSizeRollBackups); } } super.setFile(fileName, append, append, 1); // super.setFile(fileName, append); if (append) { File f = new File(fileName); ((CountingQuietWriter)qw).setCount(f.length()); } } public int getCountDirection() { return countDirection; } public void setCountDirection(int direction) { countDirection = direction; } public int getRollingStyle() { return rollingStyle; } public void setRollingStyle(int style) { rollingStyle = style; switch (rollingStyle) { case BY_SIZE: rollDate = false; rollSize = true; break; case BY_DATE: rollDate = true; rollSize = false; break; case BY_COMPOSITE: rollDate = true; rollSize = true; break; default: errorHandler.error("Invalid rolling Style, use 1 (by size only), 2 (by date only) or 3 (both)"); } } /** * public void setRollingStyle(String style) { if (style == S_BY_SIZE) { rollingStyle = BY_SIZE; } else if (style == * S_BY_DATE) { rollingStyle = BY_DATE; } else if (style == S_BY_COMPOSITE) { rollingStyle = BY_COMPOSITE; } } */ public boolean getStaticLogFileName() { return staticLogFileName; } public void setStaticLogFileName(boolean s) { staticLogFileName = s; } public void setStaticLogFileName(String value) { setStaticLogFileName(OptionConverter.toBoolean(value, true)); } /*** * Initializes based on exisiting conditions at time of <code> * activateOptions</code>. The following is done:<br> * <br> * A) determine curSizeRollBackups<br> * B) determine curTimeRollBackups (not implemented)<br> * C) initiates a roll over if needed for crossing a date boundary since the last run. */ protected void existingInit() { curSizeRollBackups = 0; curTimeRollBackups = 0; // part A starts here String filter; if (staticLogFileName || !rollDate) { filter = baseFileName + ".*"; } else { filter = scheduledFilename + ".*"; } File f = new File(baseFileName); f = f.getParentFile(); if (f == null) f = new File("."); LogLog.debug("Searching for existing files in: " + f); String[] files = f.list(); if (files != null) { for (int i = 0; i < files.length; i++) { if (!files[i].startsWith(baseFileName)) continue; int index = files[i].lastIndexOf("."); if (staticLogFileName) { int endLength = files[i].length() - index; if (baseFileName.length() + endLength != files[i].length()) { // file is probably scheduledFilename + .x so I don't care continue; } } try { int backup = Integer.parseInt(files[i].substring(index + 1, files[i].length())); LogLog.debug("From file: " + files[i] + " -> " + backup); if (backup > curSizeRollBackups) curSizeRollBackups = backup; } catch (Exception e) { // this happens when file.log -> file.log.yyyy-mm-dd which is normal // when staticLogFileName == false LogLog.debug("Encountered a backup file not ending in .x " + files[i]); } } } LogLog.debug("curSizeRollBackups starts at: " + curSizeRollBackups); // part A ends here // part B not yet implemented // part C if (staticLogFileName && rollDate) { File old = new File(baseFileName); if (old.exists()) { Date last = new Date(old.lastModified()); if (!(sdf.format(last).equals(sdf.format(now)))) { scheduledFilename = baseFileName + sdf.format(last); LogLog.debug("Initial roll over to: " + scheduledFilename); rollOverTime(); } } } LogLog.debug("curSizeRollBackups after rollOver at: " + curSizeRollBackups); // part C ends here } /*** * Sets initial conditions including date/time roll over information, first check, scheduledFilename, and calls * <code>existingInit</code> to initialize the current # of backups. */ public void activateOptions() { // REMOVE removed rollDate from boolean to enable Alex's change if (datePattern != null) { now.setTime(System.currentTimeMillis()); sdf = new SimpleDateFormat(datePattern); int type = computeCheckPeriod(); // printPeriodicity(type); rc.setType(type); // next line added as this removes the name check in rollOver nextCheck = rc.getNextCheckMillis(now); } else { if (rollDate) LogLog.error("Either DatePattern or rollingStyle options are not set for [" + name + "]."); } existingInit(); super.activateOptions(); if (rollDate && fileName != null && scheduledFilename == null) scheduledFilename = fileName + sdf.format(now); } /*** * Rollover the file(s) to date/time tagged file(s). Opens the new file (through setFile) and resets * curSizeRollBackups. */ protected void rollOverTime() { System.out.println("rollOverTime()"); curTimeRollBackups++; // delete the old stuff here if (staticLogFileName) { /** Compute filename, but only if datePattern is specified */ if (datePattern == null) { errorHandler.error("Missing DatePattern option in rollOver()."); return; } // is the new file name equivalent to the 'current' one // something has gone wrong if we hit this -- we should only // roll over if the new file will be different from the old String dateFormat = sdf.format(now); if (scheduledFilename.equals(fileName + dateFormat)) { errorHandler.error("Compare " + scheduledFilename + " : " + fileName + dateFormat); return; } // close current file, and rename it to datedFilename this.closeFile(); // we may have to roll over a large number of backups here String from, to; for (int i = 1; i <= curSizeRollBackups; i++) { from = fileName + '.' + i; to = scheduledFilename + '.' + i; rollFile(from, to); } rollFile(fileName, scheduledFilename); } try { // This will also close the file. This is OK since multiple // close operations are safe. curSizeRollBackups = 0; // We're cleared out the old date and are ready for the new // new scheduled name scheduledFilename = fileName + sdf.format(now); this.setFile(baseFileName, false); } catch (IOException e) { errorHandler.error("setFile(" + fileName + ", false) call failed."); } } /*** * Renames file <code>from</code> to file <code>to</code>. It also checks for existence of target file and deletes * if it does. */ protected static void rollFile(String from, String to) { File target = new File(to); if (target.exists()) { LogLog.debug("deleting existing target file: " + target); target.delete(); } File file = new File(from); file.renameTo(target); LogLog.debug(from + " -> " + to); } /*** Delete's the specified file if it exists */ protected static void deleteFile(String fileName) { File file = new File(fileName); if (file.exists()) { file.delete(); } } /*** * Implements roll overs base on file size. * * <p> * If the maximum number of size based backups is reached (<code>curSizeRollBackups == maxSizeRollBackups</code) * then the oldest file is deleted -- it's index determined by the sign of countDirection.<br> * If <code>countDirection</code> < 0, then files {<code>File.1</code>, ..., <code>File.curSizeRollBackups -1</code> * are renamed to {<code>File.2</code>, ..., <code>File.curSizeRollBackups</code> . Moreover, <code>File</code> is * renamed <code>File.1</code> and closed.<br> * * A new file is created to receive further log output. * * <p> * If <code>maxSizeRollBackups</code> is equal to zero, then the <code>File</code> is truncated with no backup files * created. * * <p> * If <code>maxSizeRollBackups</code> < 0, then <code>File</code> is renamed if needed and no files are deleted. */ // synchronization not necessary since doAppend is alreasy synched protected void rollOverSize() { File file; this.closeFile(); // keep windows happy. LogLog.debug("rolling over count=" + ((CountingQuietWriter)qw).getCount()); LogLog.debug("maxSizeRollBackups = " + maxSizeRollBackups); LogLog.debug("curSizeRollBackups = " + curSizeRollBackups); LogLog.debug("countDirection = " + countDirection); // If maxBackups <= 0, then there is no file renaming to be done. if (maxSizeRollBackups != 0) { if (countDirection < 0) { // Delete the oldest file, to keep Windows happy. if (curSizeRollBackups == maxSizeRollBackups) { deleteFile(fileName + '.' + maxSizeRollBackups); curSizeRollBackups--; } // Map {(maxBackupIndex - 1), ..., 2, 1} to {maxBackupIndex, ..., 3, 2} for (int i = curSizeRollBackups; i >= 1; i--) { rollFile((fileName + "." + i), (fileName + '.' + (i + 1))); } curSizeRollBackups++; // Rename fileName to fileName.1 rollFile(fileName, fileName + ".1"); } // REMOVE This code branching for Alexander Cerna's request else if (countDirection == 0) { // rollFile based on date pattern curSizeRollBackups++; now.setTime(System.currentTimeMillis()); scheduledFilename = fileName + sdf.format(now); rollFile(fileName, scheduledFilename); } else { // countDirection > 0 if (curSizeRollBackups >= maxSizeRollBackups && maxSizeRollBackups > 0) { // delete the first and keep counting up. int oldestFileIndex = curSizeRollBackups - maxSizeRollBackups + 1; deleteFile(fileName + '.' + oldestFileIndex); } if (staticLogFileName) { curSizeRollBackups++; rollFile(fileName, fileName + '.' + curSizeRollBackups); } } } try { // This will also close the file. This is OK since multiple // close operations are safe. this.setFile(baseFileName, false); } catch (IOException e) { LogLog.error("setFile(" + fileName + ", false) call failed.", e); } } public int getMaxTimeRollBackups() { return maxTimeRollBackups; } public void setMaxTimeRollBackups(int maxTimeRollBackups) { this.maxTimeRollBackups = maxTimeRollBackups; } public static void main(String[] args) { CompositeFilenameFilter compositeFilenameFilter = new CompositeFilenameFilter(getDateString(new Date(), -1)); File srcFolder = new File("E:/eclipse-jee-indigo-SR2-win32/logs"); File[] files = srcFolder.listFiles(compositeFilenameFilter); for (File file : files) { System.out.println(file.getName()); } } private static String getDateString(Date date, int pos) { DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); return dateFormat.format(DateUtils.addDays(date, pos)); } public static int getEndStart(String source, char tarChar) { int length = source.length(); for (int i = 0; i < length; i++) { if (source.indexOf(tarChar, length - i) > -1) { return length - i; } } return -1; } }
package org.apache.log4j; import java.io.File; import java.io.FilenameFilter; /** * 用于通过指定preix 过滤文件的 过滤器 只要文件名中包含preix字符串就算匹配成功 * * @author zhangjianying * */ public class CompositeFilenameFilter implements FilenameFilter { // 需要查找文件中必须包含的文字 private String preix = ""; public CompositeFilenameFilter(String preix) { this.preix = preix; } @Override public boolean accept(File dir, String name) { return name.indexOf(preix) > -1 ? true : false; } }
OK,记得包名不能改
然后配置log4j.properties
log4j.appender.logfile=org.apache.log4j.CompositeRollingAppender log4j.appender.logfile.RollingStyle=3 log4j.appender.logfile.layout=org.apache.log4j.PatternLayout log4j.appender.logfile.staticLogFileName=true log4j.appender.logfile.File=../logs/XXXXXXXXX.log # Keep three backup files. log4j.appender.logfile.MaxSizeRollBackups=9999 log4j.appender.logfile.CountDirection=-1 log4j.appender.logfile.maxTimeRollBackups=7 log4j.appender.logfile.MaxFileSize=10M log4j.appender.logfile.datePattern='.'yyyy-MM-dd # Pattern to output: date priority [category] - message log4j.appender.logfile.layout.ConversionPattern=[工程名] %d{yyyy-MM-dd HH:mm:ss} [%c]-[%p] %m%n
最后生成的文件同一天内就会按照 10M来分文件,最多分9999个,如果超过9999就会迭代覆盖。
maxTimeRollBackups 是指超期日期是7天,会自动删除7天前的日志(当然应该是打压缩包转移,不过目前项目的SE没做这个要求)
这里我处理有点特殊:我要求应用再超期日期这段时间内没有重启过才会删除超期后的日志,如果重启过,超期时间内的日志都不会自动删除可供分析问题。
时间: 2024-09-16 00:10:06