使用 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;
}
}