1、自定义日志模块
前面搭建好的框架里面,都采用System.out.print来进行了日志的输出,作为一个和业务系统不耦合的javaagent框架,日志输出是必要的,显然不可能采用System.out.print进行日志的输出,那么即需要开发一个日志模块进行日志的输出。
日志模块实现目标:
1、支持文件输出和std输出:STDOUT, FILE
2、支持和项目的sl4j输出适配(暂时不考虑)
3、日志输出级别:DEBUG, INFO, WARN, ERROR, OFF
4、日志异步(采用disruptor)输出,提升性能
5、日志文件大小限制,大于200m进行备份
disruptor介绍:
Disruptor是一个高性能的异步处理框架,或者可以认为是线程间通信的高效低延时的内存消息组件,它最大特点是高性能,其LMAX架构可以获得每秒6百万订单,用1微秒的延迟获得吞吐量为100K+。关于其详细介绍,百度google随便找。
推荐文章:
https://www.jianshu.com/p/d24b2eb4a881
2、项目扩展
项目结构:
日志模块UML:
关键代码:
gy4j-monitor-sniffer的pom.xml:
引入disruptor的依赖:
com.lmax
disruptor
${disruptor.version}
3.3.6
TestByteBuddyPrintInterceptor.java:
public class TestByteBuddyPrintInterceptor {
private static final ILogger logger = LoggerFactory.getLogger(TestByteBuddyPrintInterceptor.class);
@RuntimeType
public Object intercept(@This Object obj,
@AllArguments Object[] allArguments,
@SuperCall Callable> zuper,
@Origin Method method) throws Throwable {
try {
// 原方法执行前
logger.info(" before method:" + method.getName());
} catch (Throwable t) {
t.printStackTrace();
logger.info("class[" + obj.getClass() + "] before method[" + method.getName() + "] intercept failure");
}
Object ret = null;
try {
// 原方法调用
ret = zuper.call();
} catch (Throwable t) {
try {
// 原方法调用异常
logger.info("exception method:" + method.getName());
} catch (Throwable t2) {
logger.info("class[" + obj.getClass() + "] handle method[" + method.getName() + "] exception failure");
}
throw t;
} finally {
try {
// 原方法执行后
logger.info("after method:" + method.getName());
} catch (Throwable t) {
logger.info("class[" + obj.getClass() + "] after method[" + method.getName() + "] intercept failure");
}
}
return ret;
}
}
AgentConfig.java:
public class AgentConfig {
public static class Logging {
// 日志文件大小,超过该大小自动重命名
public static long MAX_FILE_SIZE = 1024 * 1024 * 200;
// 日志级别
public static LoggerLevel LEVEL = LoggerLevel.DEBUG;
// 输出模式
public static LoggerType TYPE = LoggerType.FILE;
// 日志文件存放目录
public static String PATH = "";
// 日志文件名
public static String FILE_NAME = "sniffer-agent.log";
}
}
Constants.java:
public class Constants {
/**
* 换行符
*/
public static final String LINE_SEPARATOR = System.getProperty("line.separator", "\n");
}
ILogger.java:
public interface ILogger {
void debug(String message);
void debug(String format, Object... args);
void info(String message);
void info(String format, Object... args);
void warn(String message);
void warn(String format, Object... args);
void warn(Throwable throwable, String format);
void warn(Throwable throwable, String format, Object... args);
void error(String message);
void error(String format, Object... args);
void error(Throwable throwable, String message);
void error(Throwable throwable, String format, Object... args);
boolean isDebugEnable();
boolean isInfoEnable();
boolean isWarnEnable();
boolean isErrorEnable();
}
IWriter.java:
public interface IWriter {
/**
* 输出日志.
*
* @param message
*/
void write(String message);
}
FileWriter.java:
public class FileWriter implements IWriter, EventHandler {
/**
* 单例实例对象.
*/
private static FileWriter INSTANCE;
/**
* 单例创建的锁对象.
*/
private static final Object CREATE_LOCK = new Object();
/**
* 文件输出流.
*/
private FileOutputStream fileOutputStream;
/**
* Disruptor对象:Disruptor是一个高性能的异步处理框架.
*/
private Disruptor disruptor;
/**
* Disruptor对象的环形缓存区对象.
*/
private RingBuffer buffer;
/**
* 是否启动.
*/
private volatile boolean started = false;
/**
* 行数.
*/
private volatile long lineNum;
/**
* 文件大小.
*/
private volatile long fileSize;
/**
* 构造函数:初始化Disruptor相关.
*/
private FileWriter() {
disruptor = new Disruptor(new EventFactory() {
@Override
public LoggerEvent newInstance() {
return new LoggerEvent();
}
}, 1024, DaemonThreadFactory.INSTANCE);
disruptor.handleEventsWith(this);
buffer = disruptor.getRingBuffer();
lineNum = 0;
disruptor.start();
}
/**
* 单例获取方法.
*
* @return
*/
public static IWriter getInstance() {
if (INSTANCE == null) {
synchronized (CREATE_LOCK) {
if (INSTANCE == null) {
INSTANCE = new FileWriter();
}
}
}
return INSTANCE;
}
/**
* 日志输出:写入buffer,异步输出日志.
*
* @param message 日志信息
*/
@Override
public void write(String message) {
// 拿到buffer里面的下一个可用序号.
long seq = buffer.next();
try {
// 通过序号拿到buffer里面的对象.
LoggerEvent logEvent = buffer.get(seq);
// set日志信息.
logEvent.setMessage(message);
} finally {
// 提交.
buffer.publish(seq);
}
}
/**
* 日志事件处理.
*
* @param logEvent 日志事件对象
* @param sequence 序号
* @param endOfBatch 是否批量结束
*/
@Override
public void onEvent(LoggerEvent logEvent, long sequence, boolean endOfBatch) {
if (hasWriterStream()) {
try {
lineNum++;
write(logEvent.getMessage() + Constants.LINE_SEPARATOR, endOfBatch);
} finally {
logEvent.setMessage(null);
}
}
}
/**
* 通过文件流输出日志.
*
* @param message 日志消息
* @param forceFlush 是否flush
*/
private void write(String message, boolean forceFlush) {
try {
fileOutputStream.write(message.getBytes());
fileSize += message.getBytes().length;
if (forceFlush || lineNum % 20 == 0) {
fileOutputStream.flush();
}
} catch (IOException ex) {
ex.printStackTrace();
} finally {
checkFile();
}
}
/**
* 检查日志文件:如果文件大小超过限制,重命名日志文件.
*/
private void checkFile() {
if (fileSize > AgentConfig.Logging.MAX_FILE_SIZE) {
// flush文件流
forceExecute(new Callable() {
@Override
public Object call() throws Exception {
fileOutputStream.flush();
return null;
}
});
// 关闭文件流
forceExecute(new Callable() {
@Override
public Object call() throws Exception {
fileOutputStream.close();
return null;
}
});
// 重命名文件
forceExecute(new Callable() {
@Override
public Object call() throws Exception {
new File(AgentConfig.Logging.PATH, AgentConfig.Logging.FILE_NAME)
.renameTo(new File(AgentConfig.Logging.PATH,
AgentConfig.Logging.FILE_NAME + DateUtil.getFullFormat(new Date())));
return null;
}
});
// 重置文件流对象和启动状态
forceExecute(new Callable() {
@Override
public Object call() throws Exception {
fileOutputStream = null;
started = false;
return null;
}
});
}
}
/**
* 强制执行,忽略异常.
*
* @param callable Callable实例
*/
private void forceExecute(Callable callable) {
try {
callable.call();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 是否存在文件输出流.
*
* @return
*/
private boolean hasWriterStream() {
if (fileOutputStream != null) {
return true;
}
if (!started) {
// 检查日志目录是否存在
File logFilePath = new File(AgentConfig.Logging.PATH);
if (!logFilePath.exists()) {
logFilePath.exists();
} else if (!logFilePath.isDirectory()) {
System.err.println("Log dir(" + AgentConfig.Logging.PATH + ") is not a directory.");
}
try {
// 获取日志文件流和文件大小
File logFile = new File(AgentConfig.Logging.PATH + AgentConfig.Logging.FILE_NAME);
fileOutputStream = new FileOutputStream(logFile, true);
fileSize = logFile.length();
} catch (FileNotFoundException ex) {
ex.printStackTrace();
}
started = true;
}
return fileOutputStream != null;
}
/**
* 日志事件对象.
*/
public static class LoggerEvent {
/**
* 日志消息.
*/
private String message;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
}
NoopLogger.java:
public enum NoopLogger implements ILogger {
INSTANCE;
@Override
public void debug(String message) {
}
@Override
public void debug(String format, Object... args) {
}
@Override
public void info(String message) {
}
@Override
public void info(String format, Object... args) {
}
@Override
public void warn(String message) {
}
@Override
public void warn(String format, Object... args) {
}
@Override
public void warn(Throwable throwable, String format) {
}
@Override
public void warn(Throwable throwable, String format, Object... args) {
}
@Override
public void error(String message) {
}
@Override
public void error(String format, Object... args) {
}
@Override
public void error(Throwable throwable, String message) {
}
@Override
public void error(Throwable throwable, String format, Object... args) {
}
@Override
public boolean isDebugEnable() {
return false;
}
@Override
public boolean isInfoEnable() {
return false;
}
@Override
public boolean isWarnEnable() {
return false;
}
@Override
public boolean isErrorEnable() {
return false;
}
}
SimpleLogger.java:
public class SimpleLogger implements ILogger {
private Class> targetClass;
/**
* 构造函数.
*
* @param targetClass 日志来源类
*/
public SimpleLogger(Class> targetClass) {
this.targetClass = targetClass;
}
@Override
public void debug(String message) {
if (isDebugEnable()) {
logger(LoggerLevel.DEBUG, message, null);
}
}
@Override
public void debug(String format, Object... args) {
if (isDebugEnable()) {
logger(LoggerLevel.DEBUG, replaceParam(format, args), null);
}
}
@Override
public void info(String message) {
if (isInfoEnable()) {
logger(LoggerLevel.INFO, message, null);
}
}
@Override
public void info(String format, Object... args) {
if (isInfoEnable()) {
logger(LoggerLevel.INFO, replaceParam(format, args), null);
}
}
@Override
public void warn(String message) {
if (isWarnEnable()) {
logger(LoggerLevel.WARN, message, null);
}
}
@Override
public void warn(String format, Object... args) {
if (isWarnEnable()) {
logger(LoggerLevel.WARN, replaceParam(format, args), null);
}
}
@Override
public void warn(Throwable t, String message) {
if (isWarnEnable()) {
logger(LoggerLevel.WARN, message, t);
}
}
@Override
public void warn(Throwable t, String format, Object... args) {
if (isWarnEnable()) {
logger(LoggerLevel.WARN, replaceParam(format, args), t);
}
}
@Override
public void error(String message) {
if (isErrorEnable()) {
logger(LoggerLevel.ERROR, message, null);
}
}
@Override
public void error(String format, Object... args) {
if (isErrorEnable()) {
logger(LoggerLevel.ERROR, replaceParam(format, args), null);
}
}
@Override
public void error(Throwable t, String message) {
if (isErrorEnable()) {
logger(LoggerLevel.ERROR, message, t);
}
}
@Override
public void error(Throwable t, String format, Object... args) {
if (isErrorEnable()) {
logger(LoggerLevel.ERROR, replaceParam(format, args), t);
}
}
@Override
public boolean isDebugEnable() {
return LoggerLevel.DEBUG.compareTo(AgentConfig.Logging.LEVEL) >= 0;
}
@Override
public boolean isInfoEnable() {
return LoggerLevel.INFO.compareTo(AgentConfig.Logging.LEVEL) >= 0;
}
@Override
public boolean isWarnEnable() {
return LoggerLevel.WARN.compareTo(AgentConfig.Logging.LEVEL) >= 0;
}
@Override
public boolean isErrorEnable() {
return LoggerLevel.ERROR.compareTo(AgentConfig.Logging.LEVEL) >= 0;
}
/**
* 日志输出公共方法.
*
* @param loggerLevel 日志级别
* @param message 日志消息
* @param throwable 异常
*/
private void logger(LoggerLevel loggerLevel, String message, Throwable throwable) {
WriterFactory.getWriter().write(format(loggerLevel, message, throwable));
}
/**
* 日志消息格式化.
*
* @param loggerLevel 日志级别
* @param message 日志消息
* @param throwable 异常
* @return
*/
private String format(LoggerLevel loggerLevel, String message, Throwable throwable) {
return StringUtil.join(' ', loggerLevel.name(), DateUtil.getFullFormat(new Date())
, Thread.currentThread().getName(), targetClass.getSimpleName()
, ": ", message, throwable == null ? "" : ExceptionUtil.format(throwable));
}
/**
* 日志格式化转换:按{}的顺序用args列表替换.
*
* @param format 日志格式字符串
* @param args 参数列表
* @return
*/
private String replaceParam(String format, Object... args) {
int startSize = 0;
int paramIndex = 0;
int index;
String tmpMessage = format;
while ((index = format.indexOf("{}", startSize)) != -1) {
if (paramIndex >= args.length) {
break;
}
/**
* @Fix Matcher.quoteReplacement:the Illegal group reference issue.
* exp:"{}".replaceFirst("\\{\\}", "x$")
*/
tmpMessage = tmpMessage.replaceFirst("\\{\\}", Matcher.quoteReplacement(String.valueOf(args[paramIndex++])));
startSize = index + 2;
}
return tmpMessage;
}
}
SystemOutWriter.java:
public enum SystemOutWriter implements IWriter {
INSTANCE;
@Override
public void write(String message) {
System.out.println(message);
}
}
LoggerFactory.java:
public class LoggerFactory {
/**
* 获取日志对象.
*
* @param clazz 来源类
* @return
*/
public static ILogger getLogger(Class> clazz) {
if (AgentConfig.Logging.LEVEL == LoggerLevel.OFF) {
return NoopLogger.INSTANCE;
} else {
return new SimpleLogger(clazz);
}
}
}
LoggerLevel.java:
public enum LoggerLevel {
DEBUG, INFO, WARN, ERROR, OFF
}
LoggerType.java:
public enum LoggerType {
STDOUT, FILE
}
WriterFactory.java:
public class WriterFactory {
/**
* 获取日志输出对象.
*
* @return 日志输出对象
*/
public static IWriter getWriter() {
if (AgentConfig.Logging.TYPE == LoggerType.STDOUT) {
return SystemOutWriter.INSTANCE;
} else {
return FileWriter.getInstance();
}
}
}
3、测试验证
第一步:编译打包
mvn clean package
获取路径:G:\gy4j\git_gy4j\gy4j-monitor\gy4j-monitor-sniffer\target\sniffer-agent.jar
第二步:测试验证
加入jvm参数(使用前面copy的jar路径):
-javaagent:G:\gy4j\git_gy4j\gy4j-monitor\gy4j-monitor-sniffer\target\sniffer-agent.jar
执行TestSnifferAgent的main方法:
第三步:修改AgentConfig.java验std输出
// 输出模式
public static LoggerType TYPE = LoggerType.STDOUT;
重新打包:
mvn clean package
重新执行结果如下:
4、源码地址
https://github.com/gy4j/gy4j-monitor/tree/004-disruptor-logging