最近开发的Dodo工具箱中需要一个日志功能,当然现在已经有很成熟很好用的Log4j,但是我觉得作为一个菜鸟,有必要自己开发一个简单的日志处理模块。下面简介该模块的设计与输出重定向这个难点。
用例设计:
类设计:
实现难点——输出重定向:
我是这样设想的,日志来源于三个方面 1其他模块使用Logger记录的日志信息 2 系统执行过程中需要输出的信息 3 系统在运行过程中碰到的错误信息。日志输出的目的也有三个地方,第一:控制台 第二:日志文件 第三:gui即时信息窗口。
如果只是单目的地的重定向十分简单,比如说想把System.err的信息重定向到文件out,只需在系统启动时调用如下两行即可:
PrintStream out = new PrintStream(new BufferedOutputStream(new FileOutputStream("out")));
System.setErr(out);
现在的难点在于需要将错误信息输出到多个目的地(文件,控制台,gui窗口),没有一个适合的PrintStream的子类来完成这个任务,那么我们就需要继承PrintStream,并在这个子类中重新print方法来完成我们的输出逻辑,我定义的该类的名称为IORedirect,该类的源码如下:
package toolBox.core.utility; import java.io.FileNotFoundException; import java.io.PrintStream; import java.util.Date; import java.util.Vector; import com.ibm.icu.text.SimpleDateFormat; /** * The class pipelines print/println's to several PrintStream. Useful for * directing system.out and system.err to external files etc. * * @author jiangkai * */ public class IORedirect extends PrintStream { protected static SimpleDateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); /**The default target output stream*/ protected PrintStream default_target=null; /**The different target output streams*/ protected Vector<PrintStream> targets=null; /** */ protected Vector<Boolean> appendHeader = null; /** */ protected String prefix = null; /** * initializes the object, with a default printstream. */ public IORedirect(PrintStream s) { this(s,"defaultoutput"); } public IORedirect(PrintStream s,String p) { super(s); default_target = s; prefix = p; targets = new Vector<PrintStream>(); appendHeader = new Vector<Boolean>(); clear(); } /** * removes all streams and places the default printstream, if any, again in * the list. * * @see #getDefault() */ public void clear() { targets.clear(); appendHeader.clear(); if (getDefault() != null) { targets.add(getDefault()); appendHeader.add(true); } } /** * returns the default printstrean, can be NULL. * * @return the default printstream * @see #m_Default */ public PrintStream getDefault() { return default_target; } public void defaultPrint(String x) { if(default_target!=null) default_target.print(x); } public void defaultPrintln(String x) { if(default_target!=null) default_target.println(x); } /** * adds the given PrintStream to the list of streams, with NO timestamp and * NO prefix. * * @param p * the printstream to add */ public void addPrintStream(PrintStream p) { addPrintStream(p,false); } public void addPrintStream(PrintStream p,boolean ah) { if (!targets.contains(p)) { targets.add(p); appendHeader.add(ah); } } public void removePrintStream(PrintStream p) { if (targets.contains(p)) { int index = targets.indexOf(p); targets.remove(index); appendHeader.remove(index); } } public boolean containsPrintStream(PrintStream p) { return targets.contains(p); } public int size() { return targets.size(); } public void flush() { for (PrintStream element : targets) element.flush(); } protected void printHeader() { for(PrintStream element:targets) if(appendHeader.get(targets.indexOf(element))) element.print(dateformat.format(new Date())+" "+prefix+"\n\t"); } @Override public void print(int x) { printHeader(); for (PrintStream element : targets) element.print(x); flush(); } @Override public void print(long x) { printHeader(); for (PrintStream element : targets) element.print(x); flush(); } @Override public void print(float x) { printHeader(); for (PrintStream element : targets) element.print(x); flush(); } @Override public void print(double x) { printHeader(); for (PrintStream element : targets) element.print(x); flush(); } @Override public void print(boolean x) { printHeader(); for (PrintStream element : targets) element.print(x); flush(); } @Override public void print(char x) { printHeader(); for (PrintStream element : targets) element.print(x); flush(); } @Override public void print(char[] x) { printHeader(); for (PrintStream element : targets) element.print(x); flush(); } @Override public void print(String x) { printHeader(); for (PrintStream element : targets) element.print(x); flush(); } @Override public void print(Object x) { printHeader(); for (PrintStream element : targets) element.print(x); flush(); } @Override public void println(int x) { printHeader(); for (PrintStream element : targets) element.println(x); flush(); } @Override public void println(long x) { printHeader(); for (PrintStream element : targets) element.println(x); flush(); } @Override public void println(float x) { printHeader(); for (PrintStream element : targets) element.println(x); flush(); } @Override public void println(double x) { printHeader(); for (PrintStream element : targets) element.println(x); flush(); } @Override public void println(boolean x) { printHeader(); for (PrintStream element : targets) element.println(x); flush(); } @Override public void println(char x) { printHeader(); for (PrintStream element : targets) element.println(x); flush(); } @Override public void println(char[] x) { printHeader(); for (PrintStream element : targets) element.println(x); flush(); } @Override public void println(String x) { printHeader(); for (PrintStream element : targets) element.println(x); flush(); } @Override public void println(Object x) { printHeader(); for (PrintStream element : targets) element.println(x); flush(); } /** * Writes <code>len</code> bytes from the specified byte array starting at * offset <code>off</code> to this stream. If automatic flushing is enabled * then the <code>flush</code> method will be invoked. * * <p> * Note that the bytes will be written as given; to write characters that * will be translated according to the platform's default character * encoding, use the <code>print(char)</code> or <code>println(char)</code> * methods. * * @param buf * A byte array * @param off * Offset from which to start taking bytes * @param len * Number of bytes to write */ public void write(byte buf[], int off, int len) { for (PrintStream element : targets) element.write(buf, off, len); flush(); } /** * Writes the specified byte to this stream. If the byte is a newline and * automatic flushing is enabled then the <code>flush</code> method will be * invoked. * * <p> * Note that the byte is written as given; to write a character that will be * translated according to the platform's default character encoding, use * the <code>print(char)</code> or <code>println(char)</code> methods. * * @param b * The byte to be written * @see #print(char) * @see #println(char) */ public void write(int b) { for (PrintStream element : targets) element.write(b); flush(); } public static void main(String[] args) throws FileNotFoundException { IORedirect stdRedirect = new IORedirect(System.err,"stderr"); // stdRedirect.addPrintStream(System.err); System.setErr(stdRedirect); System.err.println("error"); //stdRedirect.addPrintStream(System.out); // System.setOut(stdRedirect); // System.out.println("no"); } }现在一目了然了,我们的输出逻辑在于重写PrintStream类中的Print函数,在该函数中我们自己定义需要向那写输出流输出日志信息,我在这里的写法是
@Override public void println(boolean x) { printHeader(); for (PrintStream element : targets) element.println(x); flush(); }对于targets中的每一个输出流输出信息,当然,你可以在这边直接写文件输出的逻辑,窗口输出的逻辑,但是当前这种写法使得IORedirect这个类更加通用,不管你是什么输出流,只要注册到该IORediect类即可。
下面贴出该类的使用办法:
IORedirect stdoutRedirect = new IORedirect(System.out, "stdout"); System.setOut(stdoutRedirect); stdoutRedirect.addPrintStream(m_windowStream, true); stdoutRedirect.addPrintStream(m_FileStream,true);
这样,即将m_windowStream和m_FileStream注册到了该重定向对象中,这样系统的输出会输出到m_windowStream和m_FileStream这两个输出流中去,当然了,m_windowStream和m_FileStream也必须是PrintStream的子类,关于写法很简单,这里不再啰嗦。