使用 slf4j
实际上我对日志服务的要求不高,大多数情况下能够打印信息就可以了,例如 ActionScript 的 trace(); 这么的基本的函数我就觉得足够了,包括在网页调试中 alert() 大法更是屡试不爽。好了,来到 Java 世界,仍沿用这一思想,所以 sysout 快捷键下生成的 System.out/err.println("xxx"); 也没觉得有什么障碍。但长久以来 sysout 总感觉不妥,尤其运维的兄弟看到你这一堆打印的东东,而他又不是十分明白那是啥就有意见了,可想而知更严重的是,对你的程序的质量也会从心里觉得不太靠谱。于是综上所述,搞一个妥妥的日志系统还是有必要的,可以让随时关掉输出在生产服务器上,或者保存到文件。最开始我的初衷是 java.util.Logger 已经够用,后来渐渐接触一些开源使用了 slf4j 和 apache common logging,而不说 logback 的那些了,开源世界就是丰富多彩啊,单纯一个日志系统都可以搞那么多。再看看 slf4j 及其文档资料,好一个详细,被分解得如此精巧细致。
使用 slf4j,简单的 Hello world:
import com.ajaxjs.framework.model.BaseModel; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class Catalog extends BaseModel { public static void main(String[] args) { Logger logger = LoggerFactory.getLogger(Catalog.class); logger.info("Hello World"); } }
调用 logger.debug("Processing trade with id: {} and symbol : {} ", id, symbol); 等于调用 if (logger.isDebugEnabled()) { logger.debug("Processing trade with id: " + id + " symbol: " + symbol); },非常方便。
对于配搭 Java.util.logger,slf4j 还需要一个 JAR 包:slf4j-jdk14-1.7.17.jar。
配置 Java.util.logger
看完 slf4j 之后,我想,能不能自己实现一个简单的?嗯这就是造轮子,——我手痒了。
首先,Java 自带的日志系统结构是这样的,我们先了解一下。
我扩展的 LogHelper 主要提供 log() 和 warning() 这两个方法,而且还重载了以方便实现 info("Processing trade with id: {} and symbol : {} ", id, symbol) 多个参数的传入。
完整的代码如下:
package com.ajaxjs.util; import java.io.IOException; import java.util.HashMap; import java.util.Locale; import java.util.Map; import java.util.logging.FileHandler; import java.util.logging.Filter; import java.util.logging.Formatter; import java.util.logging.Handler; import java.util.logging.Level; import java.util.logging.LogRecord; import java.util.logging.Logger; /** * 自定义日志工具类 * @author frank * */ public class LogHelper { /** * * @param clazz */ public LogHelper(Class<?> clazz) { packageName = clazz.getPackage().getName(); logger = Logger.getLogger(packageName); logger.addHandler(initFileHandler()); logger.setFilter(filter); } // 所在的包名 private String packageName; // 包装这个 logger private Logger logger; // 缓存 private static Map<String, LogHelper> cache = new HashMap<>(); /** * 打印一个日志 * * @param msg * 日志信息 */ public void log(String msg) { logger.logp(Level.INFO, packageName, getMethodName(), msg); } /** * 打印一个日志 * * @param tpl * 信息语句之模板 * @param params * 信息参数 */ public void log(String tpl, Object... params) { logger.logp(Level.INFO, packageName, getMethodName(), tpl, params); } /** * 打印一个日志(警告级别) * * @param msg * 警告信息 */ public void warning(String msg) { logger.logp(Level.WARNING, packageName, getMethodName(), msg); } /** * 打印一个日志(警告级别) * * @param tpl * 信息语句之模板 * @param params * 信息参数 */ public void warning(String tpl, Object... params) { logger.logp(Level.WARNING, packageName, getMethodName(), tpl, params); } /** * 记录一个异常 * * @param ex */ public void warning(Throwable ex) { logger.logp(Level.WARNING, packageName, getMethodName(), ex.getMessage(), ex); } /** * 获取所在的方法,调用时候 * * @return */ private String getMethodName() { StackTraceElement[] st = Thread.currentThread().getStackTrace(); StackTraceElement frame = st[2]; String method = String.format((Locale) null, "%s(%s:%s)", frame.getMethodName(), frame.getFileName(), frame.getLineNumber()); return method; } /** * 获取自定义的 logger * * @param clazz * @return */ public static LogHelper getLog(Class<?> clazz) { String key = clazz == null ? "root" : clazz.getPackage().getName(); if (cache.containsKey(key)) { return cache.get(key); } else { LogHelper logger = new LogHelper(clazz); cache.put(key, logger); return logger; } } private static int max_size = 1; private static int max_number = 10; private static String logFilePath = "c:\\temp\\log%g.log"; /** * 日志格式 */ private final static Formatter myFormatter = new Formatter() { private static final String tpl = "\n%s %s : %s.%s %s"; @Override public String format(LogRecord record) { return String.format(tpl, DateTools.now(), record.getLevel(), record.getLoggerName(), record.getSourceMethodName(), record.getMessage()); } }; // 过滤器,是否要日志服务 private final static Filter filter = new Filter() { @Override public boolean isLoggable(LogRecord record) { return record.getMessage().contains("no log") ? false : true; } }; /** * 初始化保存到磁盤的處理器 */ private Handler initFileHandler() { FileHandler logHandler = null; try { logHandler = new FileHandler(logFilePath, 1024 * max_size, max_number, true); } catch (IOException e) { logger.severe("日志文件路径" + logFilePath + "错误,请检查!"); e.printStackTrace(); } logHandler.setLevel(Level.WARNING); logHandler.setFormatter(myFormatter); return logHandler; } /** * 控制台支持字符串 format * * @param msg * 插入的内容 */ public static void sysConsole(String msg) { System.console().format("%S", msg); } }
实际上 Java 自带的日志系统不算弱的了,首先提供了若干 Level,用于描述日志信息的级别;其次, handler 是把日志信息如果保存和显示的对象,包括持久化;如果要保存的话那是什么格式的呢?这便是 Formatter 考虑的问题;再则,如果我想某些日志不保存,可不可以呢?可以呀,那就是 Filter 涉及的问题。老外的这篇文章说得不错:http://examples.javacodegeeks.com/core-java/util/logging/java-util-logging-example/。
调用方法:
public class Node { private static final com.ajaxjs.util.LogHelper LOGGER = com.ajaxjs.util.LogHelper.getLog(Node.class); …… }
这种是不用导入包名。
封装这个类的时候,遇到一个难点就是重写 warning()/info() 方法之后不能调出上下文堆栈,不能报告具体哪个方法调用。后来查阅相关文档,才知道是调出
StackTraceElement[] st = Thread.currentThread().getStackTrace();
线程的 StackTace 即可。
修正 console 超链接不能点击的问题,少了一个 .,修改后如下
/** * 获取所在的方法,调用时候 * * @return */ private String getMethodName() { StackTraceElement frame = null; // get thread by class name for (StackTraceElement ste : Thread.currentThread().getStackTrace()) { if (ste.getClassName().equals(packageName)) { // 会有两个? frame = ste; break; } } if(frame != null) {// 超链接,跳到源码所在行数 return String.format(".%s(%s:%s)%n", frame.getMethodName(), frame.getFileName(), frame.getLineNumber()); }else{ return null; } }