最近闲来无事,刚好拾起了来公司的第一个项目,发现里面的logging功能有点弱,竟然不支持按日期文件名输出,更惨的是由于引入了些apache的公用了包,一个项目里竟然同时出现logging,log4j,commons-logging,这年头引用下开源包还要用不同的loger.记得log4j好像支持输出的文件按日期来分类的,jdk自带的logging好像还没有,于是google了一把,发现JAVA中自定义日志输出格式及自定义文件处理器的实现 ,功能还不错,有些bug及不合理的地方,于是再修改下,凑合用用.
直接代码:
FileStreamHandler实现
package com.xunlei.demo.util; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.TreeSet; import java.util.logging.Filter; import java.util.logging.Formatter; import java.util.logging.Handler; import java.util.logging.Level; import java.util.logging.LogManager; import java.util.logging.LogRecord; import java.util.logging.Logger; import java.util.logging.StreamHandler; /** * 自定义日志文件处理器---日期及文件大小存储,并自动清除过期日志文件 * * @author ZengDong * @since 2008-9-11下午11:16:39 */ public class XLFileStreamHandler extends StreamHandler { /** * 抄自FileHandler的实现,用于跟踪写入文件的字节数 这样以便提高效率 */ private class MeteredStream extends OutputStream { private OutputStream out; // 记录当前写入字节数 private int written; MeteredStream(OutputStream out, int written) { this.out = out; this.written = written; } public void close() throws IOException { out.close(); } public void flush() throws IOException { out.flush(); } public void write(byte buff[]) throws IOException { out.write(buff); written += buff.length; } public void write(byte buff[], int off, int len) throws IOException { out.write(buff, off, len); written += len; } public void write(int b) throws IOException { out.write(b); written++; } } private class XLLogFile extends File { private static final long serialVersionUID = 952141123094287978L; private Date date; private String dateString; private int sid; public int getSid() { return this.sid; } public void setSid(int sid) { this.sid = sid; } public Date getDate() { return this.date; } public void setDate(Date date) { this.date = date; } public String getDateString() { return this.dateString; } public void setDateString(String dateString) { this.dateString = dateString; } public int compareTo(File another) { XLLogFile ano = (XLLogFile) another; int dateComResult = date.compareTo(ano.getDate()); if (dateComResult == 0) { return sid - ano.getSid(); } return dateComResult; } SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); public XLLogFile(String pathname) { super(pathname); try { int dot = pathname.lastIndexOf('.'); int split = pathname.lastIndexOf(splitDateIndexChar); int underline = pathname.lastIndexOf('_'); dateString = pathname.substring(underline + 1, split); String numStr = pathname.substring(split + 1, dot); date = sdf.parse(dateString); sid = Integer.valueOf(numStr); } catch (Exception e) { System.err.println("log对应文件夹中包含了不符合XLLOG格式的文件!!"); e.printStackTrace(); } } } private static final String undifine = "xlcallcenter"; // 是否将日志写入已存在的日志文件中 private boolean append; // 保存几天之内的日志文件 // 时间间隔小于等于0时表明不删除历史记录 private int dateinterval = 5; // 保存存在的日志文件 private Map<String, TreeSet<XLLogFile>> files; // 每个日志希望写入的最大字节数,如果日志达到最大字节数则当天日期的一个新的编号的日志文件将被创建,最新的日志记录在最大编号的文件中 // 文件大小为小于等于0时表明不限制日志文件大小 private int limit = 1048576 * 5; // 输出流 private MeteredStream msOut; // 文件路径, 可以是个目录或希望的日志名称,如果是个目录则日志为"callcenter_zd" // 指定日志名称时不需要包括日期,程序会自动生成日志文件的生成日期及相应的编号 private String pattern = "./log/xunleidemo"; private char splitDateIndexChar = '#'; public XLFileStreamHandler() throws Exception { configure(); openWriteFiles(); } /** * 初始化自定义文件流处理器 * * @param fileUrl * 文件路径, 可以是个目录或希望的日志名称,如果是个目录则日志为"callcenter_zd" * 指定日志名称时不需要包括日期,程序会自动生成日志文件的生成日期及相应的编号 * @param limit * 每个日志希望写入的最大字节数,如果日志达到最大字节数则当天日期的一个新的编 * 号的日志文件将被创建,最新的日志记录在最大编号的文件中 * @param dateinterval * 保存几天之内的日志文件 * @param append * 是否将日志写入已存在的日志文件中 * @throws java.lang.Exception */ public XLFileStreamHandler(String fileUrl, int limit, int dateinterval, boolean append) throws Exception { super(); this.pattern = fileUrl; this.limit = limit; this.dateinterval = dateinterval; this.append = append; openWriteFiles(); } /** * 检查当前日志时间,删除过期日志 */ private void deleteExpiredLog() { try { // 今天作为基准 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); String today = sdf.format(new Date().getTime()); // 删除过期日志 for (String keyDate : files.keySet()) { if ((sdf.parse(today).getTime() - sdf.parse(keyDate).getTime()) / (86400 * 1000) > dateinterval) { TreeSet<XLLogFile> traceDateFiles = files.get(keyDate); for (File deletingFile : traceDateFiles) { // if(deletingFile.exists()) { deletingFile.delete(); // } } files.remove(today); } } } catch (Exception ex) { Logger.getLogger(XLFileStreamHandler.class.getName()).log( Level.SEVERE, null, ex); } } // Private method to configure a FileHandler from LogManager // properties and/or default values as specified in the class // javadoc. private void configure() { LogManager manager = LogManager.getLogManager(); String cname = getClass().getName(); // 获得pattern pattern = manager.getProperty(cname + ".pattern"); if (pattern == null) { pattern = "./log/xunleidemo"; } // 获得limit String limitVal = manager.getProperty(cname + ".limit"); if (limitVal == null) { limit = 1048576 * 5; } else { try { limit = Integer.parseInt(limitVal.trim()); // if (limit < 0) { // limit = 1048576 * 5; // } } catch (Exception ex) { limit = 1048576 * 5; } } // 获得formatter String formatVal = manager.getProperty(cname + ".formatter"); if (formatVal == null) { setFormatter(new XLLogFormatter()); } else { try { Class clz = ClassLoader.getSystemClassLoader().loadClass( formatVal); setFormatter((Formatter) clz.newInstance()); } catch (Exception ex) { // We got one of a variety of exceptions in creating the // class or creating an instance. // Drop through. } } // 获得append String appendVal = manager.getProperty(cname + ".append"); if (appendVal == null) { append = false; } else { if (appendVal.equalsIgnoreCase("true") || appendVal.equals("1")) { append = true; } else if (appendVal.equalsIgnoreCase("false") || appendVal.equals("0")) { append = false; } } // 获得level String levelVal = manager.getProperty(cname + ".level"); if (levelVal == null) { setLevel(Level.ALL); } else { try { setLevel(Level.parse(levelVal.trim())); } catch (Exception ex) { setLevel(Level.ALL); } } // 获得dateinterval String dateintervalVal = manager.getProperty(cname + ".dateinterval"); if (dateintervalVal == null) { dateinterval = 5; } else { try { dateinterval = Integer.parseInt(dateintervalVal.trim()); if (dateinterval <= 0) { dateinterval = 5; } } catch (Exception ex) { dateinterval = 5; } } // 获得filter String filterVal = manager.getProperty(cname + ".filter"); if (filterVal == null) { setFilter(null); } else { try { Class clz = ClassLoader.getSystemClassLoader().loadClass( filterVal); setFilter((Filter) clz.newInstance()); } catch (Exception ex) { // We got one of a variety of exceptions in creating the // class or creating an instance. // Drop through. } } // 获得encoding String encodingVal = manager.getProperty(cname + ".encoding"); if (encodingVal == null) { try { setEncoding(null); } catch (Exception ex2) { // doing a setEncoding with null should always work. // assert false; } } else { try { setEncoding(encodingVal); } catch (Exception ex) { try { setEncoding(null); } catch (Exception ex2) { // doing a setEncoding with null should always work. // assert false; } } } } /** * 将离现在最近的文件作为写入文件的文件 例如 xunleidemo_2008-02-19#30.log * xunleidemo表示自定义的日志文件名,2008-02-19表示日志文件的生成日期,30 表示此日期的第30个日志文件 */ private void openLastFile(boolean append) { try { super.close(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); String today = sdf.format(new Date().getTime()); // 如果没有包含当天的日期,则添加当天日期的日志文件 boolean isFirstLogToday = false; if (!files.containsKey(today)) { String logIndex = today + splitDateIndexChar + 1; TreeSet<XLLogFile> todayFiles = new TreeSet<XLLogFile>(); todayFiles.add(getNewFile(logIndex)); files.put(today, todayFiles); isFirstLogToday = true; } // 获得今天最大的日志文件编号 XLLogFile todayLastFile = files.get(today).last(); int maxLogCount = todayLastFile.getSid(); String logIndex = today + splitDateIndexChar + (maxLogCount + (isFirstLogToday ? 0 : (append ? 0 : 1))); XLLogFile wantWriteFile = getNewFile(logIndex); files.get(today).add(wantWriteFile); openFile(wantWriteFile, append); } catch (Exception ex) { Logger.getLogger(XLFileStreamHandler.class.getName()).log( Level.SEVERE, null, ex); } } /** * 根据logIndex要建立File * * @param logIndex * 包含今天日期及编号,如2008-09-11#1 * @return File */ private XLLogFile getNewFile(String logIndex) { File file = new File(pattern); XLLogFile wantWriteFile = null; StringBuilder filePath = new StringBuilder(pattern); if (file.isDirectory()) { filePath.append(File.separator); filePath.append(undifine); } filePath.append('_'); filePath.append(logIndex); filePath.append(".log"); wantWriteFile = new XLLogFile(filePath.toString()); return wantWriteFile; } /** * 读取已经记录的日志的时间信息 */ private Map<String, TreeSet<XLLogFile>> getRecodedLog() { Map<String, TreeSet<XLLogFile>> filesMap = new HashMap<String, TreeSet<XLLogFile>>(); try { // 建立相关目录 File file = new File(pattern); File fileDir = null; if (pattern.endsWith("/") || pattern.endsWith("\\")) { // 是个目录 fileDir = file; if (!file.exists()) { file.mkdirs(); } } else { // 带了前缀 File parentFile = new File(file.getParent()); fileDir = parentFile; // 父目录不存在则新建目录 if (!parentFile.exists()) { parentFile.mkdirs(); } } // 加入到filesMap中 for (File contentFile : fileDir.listFiles()) { if (contentFile.isFile()) { XLLogFile newXLLogFile = new XLLogFile(contentFile .getAbsolutePath()); TreeSet<XLLogFile> fileListToDate = filesMap .get(newXLLogFile.getDateString()); if (fileListToDate == null) { fileListToDate = new TreeSet<XLLogFile>(); } fileListToDate.add(newXLLogFile); filesMap.put(newXLLogFile.getDateString(), fileListToDate); } } files = filesMap; return filesMap; } catch (Exception ex) { Logger.getLogger(XLFileStreamHandler.class.getName()).log( Level.SEVERE, null, ex); } return null; } /** * 打开需要写入的文件 * * @param file * 需要打开的文件 * @param append * 是否将内容添加到文件末尾 */ private void openFile(File file, boolean append) throws Exception { int len = 0; if (append) { len = (int) file.length(); } FileOutputStream fout = new FileOutputStream(file.toString(), append); BufferedOutputStream bout = new BufferedOutputStream(fout); msOut = new MeteredStream(bout, len); setOutputStream(msOut); } /** * 获得将要写入的文件 */ private synchronized void openWriteFiles() throws Exception { if (!getLevel().equals(Level.OFF)) { getRecodedLog(); deleteExpiredLog(); openLastFile(append); } } /** * 发布日志信息 */ public synchronized void publish(LogRecord record) { super.publish(record); super.flush(); if (getLevel().equals(Level.OFF)) { return; } if (limit > 0 && msOut.written >= limit) { openLastFile(false); } } public static void main(String[] args) { Logger fileLogger = Logger.getLogger(XLFileStreamHandler.class .getName()); fileLogger.setLevel(Level.INFO); Handler[] hs = fileLogger.getHandlers(); for (Handler h : hs) { h.close(); fileLogger.removeHandler(h); } try { // 文件 日志文件名为mylog 日志最大写入为4000个字节 保存5天内的日志文件 // 如果文件没有达到规定大小则将日志文件添加到已有文件 XLFileStreamHandler fh = new XLFileStreamHandler("d:\\mylog\\test", 200, 10, false); fh.setLevel(Level.OFF); fh.setEncoding("UTF-8"); fh.setFormatter(new XLLogFormatter()); fileLogger.setUseParentHandlers(false); fileLogger.addHandler(fh); } catch (Exception ex) { ex.printStackTrace(); } for (int i = 0; i < 30; i++) { fileLogger.log(Level.INFO, "我被记录了吗?"); } } }
附带用到的
日志格式
package com.xunlei.demo.util; import java.io.PrintWriter; import java.io.StringWriter; import java.text.DecimalFormat; import java.util.Calendar; import java.util.logging.LogRecord; /** * Print a brief summary of the LogRecord in a human readable. The summary will * typically be on a single line (unless it's too long :) ... what I meant to * say is that we don't add any line breaks). * * @author ZengDong */ public class XLLogFormatter extends java.util.logging.Formatter { private static String lineSeparator = System.getProperty("line.separator"); static long startTime = System.currentTimeMillis(); private static DecimalFormat threeDigFmt = new DecimalFormat("000"); private static DecimalFormat twoDigFmt = new DecimalFormat("00"); /** * Format the given LogRecord. * * @param record * the log record to be formatted. * @return a formatted log record */ // private final static String format = "{0,date} {0,time}"; // private MessageFormat formatter; // Date dat = new Date(); // private Object args[] = new Object[1]; public synchronized String format(LogRecord record) { StringBuilder sb = new StringBuilder(); // dat.setTime(record.getMillis()); // args[0] = dat; // StringBuffer text = new StringBuffer(); // if (formatter == null) { // formatter = new MessageFormat(format); // } // formatter.format(args, text, null); // sb.append(text); // current time // Calendar cal = Calendar.getInstance(); // int millis = cal.get(Calendar.MILLISECOND); // sb.append('.').append(threeDigFmt.format(millis)).append(' '); // current time Calendar cal = Calendar.getInstance(); int hour = cal.get(Calendar.HOUR_OF_DAY); int minutes = cal.get(Calendar.MINUTE); int seconds = cal.get(Calendar.SECOND); int millis = cal.get(Calendar.MILLISECOND); sb.append(twoDigFmt.format(hour)).append(':'); sb.append(twoDigFmt.format(minutes)).append(':'); sb.append(twoDigFmt.format(seconds)).append('.'); sb.append(threeDigFmt.format(millis)).append(' '); // log level sb.append(record.getLevel().getLocalizedName()); sb.append(": "); // caller method int lineNumber = inferCaller(record); String loggerName = record.getLoggerName(); if (loggerName == null) loggerName = record.getSourceClassName(); if (loggerName.startsWith("com.xunlei.callcenter.")) { sb.append(loggerName.substring("com.xunlei.callcenter.".length())); } else sb.append(record.getLoggerName()); if (record.getSourceMethodName() != null) { sb.append('.'); sb.append(record.getSourceMethodName()); // include the line number if we have it. if (lineNumber != -1) sb.append("().").append(Integer.toString(lineNumber)); else sb.append("()"); } sb.append(' '); sb.append(record.getMessage()); sb.append(lineSeparator); if (record.getThrown() != null) { try { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); record.getThrown().printStackTrace(pw); pw.close(); sb.append(sw.toString()); } catch (Exception ex) { } } return sb.toString(); } /** * Try to extract the name of the class and method that called the current * log statement. * * @param record * the logrecord where class and method name should be stored. * * @return the line number that the call was made from in the caller. */ private int inferCaller(LogRecord record) { // Get the stack trace. StackTraceElement stack[] = (new Throwable()).getStackTrace(); // the line number that the caller made the call from int lineNumber = -1; // First, search back to a method in the XLCallCenter Logger class. int ix = 0; while (ix < stack.length) { StackTraceElement frame = stack[ix]; String cname = frame.getClassName(); if (cname.equals("com.xunlei.demo.util.Logger")) { break; } ix++; } // Now search for the first frame before the XLCallCenter Logger class. while (ix < stack.length) { StackTraceElement frame = stack[ix]; lineNumber = stack[ix].getLineNumber(); String cname = frame.getClassName(); if (!cname.equals("com.xunlei.demo.util.Logger")) { // We've found the relevant frame. record.setSourceClassName(cname); record.setSourceMethodName(frame.getMethodName()); break; } ix++; } return lineNumber; } }