使用 slf4j + Java.util.logger

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 使用 slf4j实际上我对日志服务的要求不高,大多数情况下能够打印信息就可以了,例如 ActionScript 的 trace(); 这么的基本的函数我就觉得足够了,包括在网页调试中 alert() 大法更是屡试不爽。

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


相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
目录
相关文章
|
4月前
|
存储 监控 Java
Java日志通关(三) - Slf4j 介绍
作者日常在与其他同学合作时,经常发现不合理的日志配置以及五花八门的日志记录方式,后续作者打算在团队内做一次Java日志的分享,本文是整理出的系列文章第三篇。
|
4月前
|
存储 监控 Java
Java日志通关(三) - Slf4j 介绍
作者日常在与其他同学合作时,经常发现不合理的日志配置以及五花八门的日志记录方式,后续作者打算在团队内做一次Java日志的分享,本文是整理出的系列文章第三篇。
|
4月前
|
JavaScript Java API
Java日志通关(二) - Slf4j+Logback 整合及排包
作者日常在与其他同学合作时,经常发现不合理的日志配置以及五花八门的日志记录方式,后续作者打算在团队内做一次Java日志的分享,本文是整理出的系列文章第二篇。
|
5月前
|
Java Maven
Class path contains multiple SLF4J bindings,后来找到的解决思路是idea2019.3必须用java11和idea2019.3版本,必须用applicatio
Class path contains multiple SLF4J bindings,后来找到的解决思路是idea2019.3必须用java11和idea2019.3版本,必须用applicatio
|
5月前
|
运维 Java Apache
Java中的日志框架:Log4j与SLF4J详解
Java中的日志框架:Log4j与SLF4J详解
最通俗易懂的 JAVA slf4j,log4j,log4j2,logback 关系与区别以及完整集成案例
最通俗易懂的 JAVA slf4j,log4j,log4j2,logback 关系与区别以及完整集成案例
最通俗易懂的 JAVA slf4j,log4j,log4j2,logback 关系与区别以及完整集成案例
|
Java 数据处理 API
java日志框架详解-slf4j
java日志框架详解-slf4j
162 0
|
SQL Java 数据库连接
java使用slf4j+log4j进行日志记录并将ERROR级别信息入库
%d{yyyy-MM-dd HH:mm:ss}表示日志信息产生的时间,{yyyy-MM-dd HH:mm:ss}表示一种时间格式,你也可以直接写成%d;
454 0
java使用slf4j+log4j进行日志记录并将ERROR级别信息入库
Java:日志输出JDK Logging、commons-logging、log4j、SLF4J、Logback
Java:日志输出JDK Logging、commons-logging、log4j、SLF4J、Logback
157 0
|
Java
Java:日志输出JDK Logging、commons-logging、log4j、SLF4J、Logback
Java:日志输出JDK Logging、commons-logging、log4j、SLF4J、Logback
180 0