004、从零开始写调用链监控-日志模块

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、项目扩展

项目结构:

004、从零开始写调用链监控-日志模块_第1张图片
1566808111134.png

日志模块UML:

004、从零开始写调用链监控-日志模块_第2张图片
1566808162811.png

关键代码:

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
004、从零开始写调用链监控-日志模块_第3张图片
1566529887800.png
执行TestSnifferAgent的main方法:
004、从零开始写调用链监控-日志模块_第4张图片
1566809018782.png

第三步:修改AgentConfig.java验std输出

// 输出模式
public static LoggerType TYPE = LoggerType.STDOUT;

重新打包:

mvn clean package

重新执行结果如下:


004、从零开始写调用链监控-日志模块_第5张图片
1566809185188.png

4、源码地址

https://github.com/gy4j/gy4j-monitor/tree/004-disruptor-logging

你可能感兴趣的:(004、从零开始写调用链监控-日志模块)