OK,上面一步咱們已經知道了日誌框架的必要性,而後咱們也對比了直接不用日誌框架來記錄日誌的種種弊端。如今咱們開始就來一步一步的實現本身的日誌框架。html
大致的思路以下:java
1,實現多種日誌級別,經過設值不一樣的日誌級別來控制項目中日誌的輸出等級,因此這裏就要寫一個等級的枚舉,這個枚舉就定義LinkinLogLevel好了。app
2,開始寫本身的日誌核心類,定義LinkinLog4j類。而後這裏要初始化日誌對象,而後提供一系列的輸出日誌的方法,好比info(),debug()等。框架
3,要控制等級,好比調用info()方法,那麼就應該按照約定來輸出INFO級別以上的日誌,自動屏蔽掉INFO等級之下的輸出。eclipse
4,每一個輸出日誌的方法最終都要調用一個輸出日誌的方法,定義log()方法,該方法將一系列的日誌內容(類名,等級,日誌信息等)輸出到控制檯上。測試
OK,大體的思路有了,咱們如今開始寫代碼,如今咱們寫代碼。ui
日誌等級定義枚舉代碼以下:this
package test.junit4test; import java.util.HashMap; import java.util.Map; /** * @建立做者: LinkinPark * @建立時間: 2016年2月23日 * @功能描述: 日誌等級枚舉。 * <p> * Log4J中的全部的等級以下:all→trace→debug→info→warn→error→fatal→off * 這裏本身模擬的等級以下:all→debug→info→error→off * </p> */ public enum LinkinLogLevel { ALL(Integer.MIN_VALUE, "ALL"), DEBUG(10000, "DEBUG"), INFO(20000, "INFO"), ERROR(30000, "ERROR"), OFF(Integer.MAX_VALUE, "OFF"); private final int status; private final String desc; private LinkinLogLevel(int status, String desc) { this.status = status; this.desc = desc; } public int getStatus() { return status; } public String getDesc() { return desc; } public String toString() { return status + ""; } /** * @建立時間: 2016年2月24日 * @相關參數: @return * @功能描述: 將全部的枚舉封裝一個Map返回 */ public static Map<Integer, LinkinLogLevel> getLevelMap() { Map<Integer, LinkinLogLevel> levelMap = new HashMap<>(5, 1); LinkinLogLevel[] values = LinkinLogLevel.values(); for (LinkinLogLevel linkinLogLevel : values) { levelMap.put(linkinLogLevel.getStatus(), linkinLogLevel); } return levelMap; } }
package test.junit4test; import java.io.PrintWriter; import java.io.StringWriter; import java.util.Map; import java.util.Objects; public class LinkinLog4j { private static final Map<Integer, LinkinLogLevel> levelMap; static { levelMap = LinkinLogLevel.getLevelMap(); } // 定義2個屬性,一個是每一個日誌文件的名稱,一個是每一個日誌文件的等級 private final String name; private final int level; /***************** 定義一系列構造器 ***********************************/ public LinkinLog4j(Class<?> klass, LinkinLogLevel level) { this(klass.getName(), level.getStatus()); } public LinkinLog4j(Class<?> klass) { this(klass.getName(), LinkinLogLevel.ALL.getStatus()); } public LinkinLog4j(String name, int level) { this.name = name; this.level = level; } /***************** 定義一系列輸出日誌的方法 ***********************************/ public void info(String message) { info(message, null); } public void info(String message, Throwable cause) { log(LinkinLogLevel.INFO.getStatus(), message, cause); } public void debug(String message) { debug(message, null); } public void debug(Throwable cause) { debug(null, cause); } public void debug(String message, Throwable cause) { log(LinkinLogLevel.DEBUG.getStatus(), message, cause); } public void error(String message) { error(message, null); } public void error(String message, Throwable cause) { log(LinkinLogLevel.ERROR.getStatus(), message, cause); } /** * @建立時間: 2016年2月24日 * @相關參數: @param level * @相關參數: @param message * @相關參數: @param cause * @功能描述: 核心日誌方法,輸出日誌內容到控制檯 * <p> * 判斷日誌類定義的日誌級別,控制一些日誌方法的執行和不執行 * 依次將日誌的信息一步一步的添加到StringBuilder中而後輸出 * TODO * 1,這裏最好使用責任鏈默認,上一步操做保持對下一步操做對象的封裝,實現解耦 * 2,重定向輸出,如今默認是控制檯,未來重寫writeLog方法,實現將日誌輸出到某一個文件下 * </p> */ private void log(int level, String message, Throwable cause) { if (isLevelEnabled(level)) { return; } StringBuilder builder = new StringBuilder(32); appendLogName2Log(builder, name); appendLevel2Log(builder, level); appendMessqge2Log(builder, message); appendCauseInfo2Log(builder, cause); writeLog(builder); } /** * @建立時間: 2016年2月24日 * @相關參數: @param level 日誌類中調用的各類輸出日誌方法的等級 * @相關參數: @return true:忽略該輸出日誌方法,false:執行該輸出日誌方法 * @功能描述: 控制一些日誌的輸出仍是忽略 * <p> * 日誌類本身配置的日誌等級VS日誌類中調用的各類輸出日誌方法的等級 * </p> */ private boolean isLevelEnabled(int level) { if (level < this.level) { return true; } return false; } /** * @建立時間: 2016年2月24日 * @相關參數: @param builder * @相關參數: @param level * @功能描述: 追加日誌等級 */ private void appendLevel2Log(StringBuilder builder, int level) { builder.append("[").append(levelMap.get(level).getDesc()).append("]").append(" "); } /** * @建立時間: 2016年2月24日 * @相關參數: @param builder * @相關參數: @param name * @功能描述: 追加日誌名字 */ private void appendLogName2Log(StringBuilder builder, String name) { builder.append("[").append(name).append("]-"); } /** * @建立時間: 2016年2月24日 * @相關參數: @param builder * @相關參數: @param message * @功能描述: 追加日誌內容信息 */ private void appendMessqge2Log(StringBuilder builder, String message) { builder.append(message); } /** * @建立時間: 2016年2月24日 * @相關參數: @param builder * @相關參數: @param cause * @功能描述: 追加日誌異常 */ private void appendCauseInfo2Log(StringBuilder builder, Throwable cause) { if (Objects.nonNull(cause)) { builder.append("<"); builder.append(cause.getMessage()); builder.append(">"); builder.append(System.getProperty("line.separator")); StringWriter writer = new StringWriter(); PrintWriter printer = new PrintWriter(writer); cause.printStackTrace(printer); printer.close(); builder.append(writer.toString()); } } /** * @建立時間: 2016年2月24日 * @相關參數: @param str 全部的日誌輸出的內容 * @功能描述: 控制檯輸出日誌 */ public void writeLog(StringBuilder str) { System.out.println(str.toString()); } }
1,如今咱們測試正常輸出日誌狀況,LinkinLog4jTest代碼以下:spa
package test.junit4test; import org.junit.Test; public class LinkinLog4jTest { LinkinLog4j log = new LinkinLog4j(LinkinLog4jTest.class, LinkinLogLevel.INFO); @Test public void testLog() { log.debug("debug()。。。"); log.info("info()。。。"); log.error("error()。。。"); } }看下junit控制檯輸出:
看下eclipse控制檯輸出:debug
[test.junit4test.LinkinLog4jTest]-[INFO] info()。。。 [test.junit4test.LinkinLog4jTest]-[ERROR] error()。。。
2,如今咱們測試代碼異常而後輸出日誌的狀況。LinkinLog4jTest1代碼以下:
package test.junit4test; import org.junit.Test; public class LinkinLog4jTest1 { LinkinLog4j log = new LinkinLog4j(LinkinLog4jTest1.class); @Test public void testLog() { try { throw new NullPointerException("測試異常日誌空指針了呢"); } catch (Exception e) { log.debug("testLog()方法拋出異常:" + e.getMessage()); } log.debug("debug()。。。"); log.info("info()。。。"); log.error("error()。。。"); } }junit綠條,而後控制檯輸出以下:
[test.junit4test.LinkinLog4jTest1]-[DEBUG] testLog()方法拋出異常:測試異常日誌空指針了呢 [test.junit4test.LinkinLog4jTest1]-[DEBUG] debug()。。。 [test.junit4test.LinkinLog4jTest1]-[INFO] info()。。。 [test.junit4test.LinkinLog4jTest1]-[ERROR] error()。。。
OK,大體的日誌框架咱們都已經寫完了,也實現了基本的功能,可是仍是有好多的問題的。
1,這裏咱們初始化咱們的每一個日誌類的時候就都要輸出日誌級別,若是不輸出的話就默認最低,如何才能將初始化日誌類的等級配置和代碼解耦放到一個統一的地方來配置呢?使用XML統一配置就OK,也就是新增咱們的日誌配置文件來統一來配置和初始化咱們的日誌框架,給用戶提供默認配置。
2,如今的框架咱們都是打印日誌到控制檯,暫時還不支持打印日誌到文件。
3,打印日誌的一系列方法,最終調用同一個輸出日誌的方法,沒有實現人爲控制日誌輸出的功能。好比用戶在打印日誌的過程當中,有些日誌要輸出這種樣式,有些日誌要輸出那種樣式,咱們如今的框架暫時還不支持。
爲了實現接口的簡單性,其中一種方法就是使用配置文件記錄LinkinLog4j的配置信息,LinkinLog4j則根據配置信息初始化每個LinkinLog4j實例。這些配置信息包括是否顯示日誌名稱、時間信息;若是顯示日誌打印時間,其格式如何;默認的日誌級別是什麼;支持單獨配置一些日誌名稱的日誌級別;若是將日誌打印到日誌文件,則日誌文件的名稱和目錄在哪裏等信息。下一篇博客我將實現這裏說的這些功能。