java sdk实现记录日志

背景:

客户需要在我们提供的sdk接口服务中增加日志,能够实现配置日志路径,日志级别等实现记录info日志,debug日志,error日志。

预研以及方案选型:

1、使用java自带的jul记录日志

优点:不集成任何日志框架,不存在jar包版本冲突,可支持外部传入日志路径,日志级别等动态修改配置

缺点:功能单一,简单,性能较差,且需要写很多复杂代码

代码示例

package cn.com.infosec.ucypher.log;
import cn.com.infosec.ucypher.agent.sdf.UCypherSDFDev;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.logging.*;

public class JulLogger implements AgentILogger {

    private static Logger logger;
    private static FileHandler fileHandler;
    private static UCypherSDFDev dev;

    public JulLogger(Class<?> clazz) {
        logger = Logger.getLogger(clazz.getName());
//        if(dev != null){
//            configureLogger(dev);
//        }
    }

    public void configureLogger(UCypherSDFDev sdfDev) {
        dev = sdfDev;
        try {
            if (fileHandler != null) {
                logger.removeHandler(fileHandler);
                fileHandler.close();
            }
            fileHandler = new FileHandler(dev.getProperty(UCypherSDFDev.LOG_PATH), Integer.parseInt(dev.getProperty(UCypherSDFDev.LOG_MAX_SIZE)), Integer.parseInt(dev.getProperty(UCypherSDFDev.LOG_COUNT)), true); // append = true
            fileHandler.setFormatter(new MyFormatter());

            Level level = null;
            switch (dev.getProperty(UCypherSDFDev.LOG_LEVEL)) {
                case "1":
                    level = Level.INFO;
                    break;
                case "2":
                    level = Level.FINE;
                    break;
                case "3":
                    level = Level.SEVERE;
                    break;
            }
            fileHandler.setLevel(level);
            // 设置日志级别
            logger.setLevel(level);
            // 添加 Handler
            logger.addHandler(fileHandler);
            // 禁用父 Logger 的 Handler,避免重复输出
            logger.setUseParentHandlers(false);
        } catch (IOException e) {
            System.err.println("Failed to configure FileHandler: " + e.getMessage());
        }
    }

    @Override
    public void debug(String message) {
        logger.log(Level.FINE, message);
    }


    @Override
    public void info(String message) {
        logger.log(Level.INFO, message);
    }

    @Override
    public void error(String message) {
        logger.log(Level.SEVERE, message);
    }

    @Override
    public void error(String message, Throwable t) {
        // 将堆栈信息转换为字符串
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);
        t.printStackTrace(pw);
        String stackTrace = sw.toString();
        logger.log(Level.SEVERE, String.format("Exception occurred: %s%n%s", message, stackTrace));

    }
}
2、集成logback与slf4j jar

优点:不需要写复杂的代码,现有框架已经成熟,可以直接调用,性能高,并且支持外部传入日志路径,日志级别等动态修改配置

代码示例:

package cn.com.infosec.ucypher.log;

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
import ch.qos.logback.classic.filter.LevelFilter;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.rolling.RollingFileAppender;
import ch.qos.logback.core.rolling.TimeBasedRollingPolicy;
import ch.qos.logback.core.spi.FilterReply;
import cn.com.infosec.ucypher.agent.sdf.UCypherSDFDev;

public class LogbackLogger implements AgentILogger{
    public static org.slf4j.Logger logger;
    private static UCypherSDFDev dev;

    public LogbackLogger(Class<?> clazz) {
        logger = org.slf4j.LoggerFactory.getLogger(clazz.getName());
//        if(dev != null){
//            configureLogger(dev);
//        }
    }


    public void configureLogger(UCypherSDFDev sdfDev) {
        dev = sdfDev;

        Level logLevel = null;
        String logLevelString = dev.getProperty(UCypherSDFDev.LOG_LEVEL);
        if (logLevelString != null) {
            switch (logLevelString) {
                case "1":
                    logLevel = Level.INFO;
                    break;
                case "2":
                    logLevel = Level.DEBUG;
                    break;
                case "3":
                    logLevel = Level.ERROR;
                    break;
                default:
                    logLevel = Level.INFO;
                    System.err.println("Invalid log level in UCypherSDFDev.LOG_LEVEL.  Using INFO.");
                    break;
            }
        } else {
            logLevel = Level.INFO;
            System.err.println("UCypherSDFDev.LOG_LEVEL property not found.  Using INFO.");
        }

        LoggerContext loggerContext = (LoggerContext) org.slf4j.LoggerFactory.getILoggerFactory();
        loggerContext.reset();

        // 创建根Logger并设置全局日志级别(如DEBUG)
        Logger rootLogger = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME);
        rootLogger.setLevel(logLevel);

        // 动态创建不同级别的Appender
        createAppenderForLevel(loggerContext, Level.DEBUG, "debug.log");
        createAppenderForLevel(loggerContext, Level.INFO, "info.log");
        createAppenderForLevel(loggerContext, Level.ERROR, "error.log");
    }

    // 工具方法:创建指定级别的Appender
    private static void createAppenderForLevel(LoggerContext context, Level level, String fileName) {
        String logPath = dev.getProperty(UCypherSDFDev.LOG_PATH);
        if (logPath == null || logPath.isEmpty()) {
            System.err.println("UCypherSDFDev.LOG_PATH property not found or empty.  Using current directory.");
            logPath = "."; // Use current directory as a fallback
        }

        // 1. 配置编码器
        PatternLayoutEncoder encoder = new PatternLayoutEncoder();
        encoder.setContext(context);
        encoder.setPattern("%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n");
        encoder.start();

        // 2. 创建 RollingFileAppender
        RollingFileAppender<ILoggingEvent> rollingFileAppender = new RollingFileAppender<>();
        rollingFileAppender.setContext(context);
        rollingFileAppender.setFile(logPath + "/" + fileName);

        // 3. 配置 Rolling Policy
        TimeBasedRollingPolicy<ILoggingEvent> rollingPolicy = new TimeBasedRollingPolicy<>();
        rollingPolicy.setContext(context);
        rollingPolicy.setFileNamePattern(logPath + "/" + fileName + ".%d{yyyy-MM-dd}.log");
        rollingPolicy.setMaxHistory(30);
        rollingPolicy.setParent(rollingFileAppender);
        rollingPolicy.start();

        rollingFileAppender.setRollingPolicy(rollingPolicy);
        rollingFileAppender.setEncoder(encoder);
        rollingFileAppender.setAppend(true);

        // 4. 添加级别过滤器
        LevelFilter filter = new LevelFilter();
        filter.setLevel(level);
        filter.setOnMatch(FilterReply.ACCEPT);  // 接受匹配级别的日志
        filter.setOnMismatch(FilterReply.DENY); // 拒绝其他级别的日志
        filter.start();
        rollingFileAppender.addFilter(filter);

        rollingFileAppender.start();

        // 5. 绑定到根Logger
        context.getLogger(Logger.ROOT_LOGGER_NAME).addAppender(rollingFileAppender);
    }

    @Override
    public void trace(String message) {

    }

    @Override
    public void trace(String message, Throwable t) {

    }

    @Override
    public void debug(String message) {
        logger.debug(message);

    }



    @Override
    public void info(String message) {
        logger.info(message);

    }

    @Override
    public void error(String message) {
        
    }


    @Override
    public void error(String message, Throwable t) {
        logger.error(message,t);

    }
}

缺点:需要引入logback-classic.jar logback-core.jar slf4j-api.jar,存在jar包版本冲突

3、只集成slf4j-api jar

优点:无需改动代码,只需引入统一日志规范进行记录日志的操作,但不支持代码动态更改日志级别,日志路径等参数

缺点:需要上层调用者去管控jar版本,如果存在不用slf4j的客户就无法使用

代码示例:

logger = org.slf4j.LoggerFactory.getLogger(clazz.getName());

直接使用slf4j的LoggerFactory.getLogger方法获取到logger对象即可 

4、更改logback源码,更改包名,集成

优点:不需要自己手写记录日志代码,性能高 

缺点:需要维护logback和slf4j的源码,非常非常麻烦!!!我就是整了好久才跑起来项目,然后改包名,集成调用

5、使用反射机制

使用反射机制,反射到上层应用使用的日志框架,得到哪个就用哪个

优点:sdk无需关注日志框架选型,并且不需要手写记录日志,完全交由上层调用者管控

缺点:存在加载顺序问题,如果上层存在多个日志框架,可能存在先取到哪个就用哪个,需要在上层调用者日志框架中配置我们自定义的日志

代码示例:

package cn.com.infosec.ucypher.log;

import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.logging.*;

/**
 * @author Hoary (hoary.huang@infosec.com.cn)
 * @org �����Ű����ͿƼ��ɷ����޹�˾
 * @date 2024/7/8 17:18
 * @since NetSignServer5.7.2.2_build20240426
 */
public class NsLogger {

    private static boolean on;

    private Object logger;

    private Method logMethod;
    private Method debugMethod;
    private Method infoMethod;
    private Method errorMethod;

    private static final String INFO = "info";
    private static final String DEBUG = "debug";
    private static final String ERROR = "error";

    private boolean isInfo;

    private boolean isError;

    private Class clazz;

    private NsLogger() {
        String level = System.getProperty("log.level");
        if(level != null){
            if(level.trim().equalsIgnoreCase("info")){
                isInfo = true;
            } else if (level.trim().equalsIgnoreCase("error")) {
                isError = true;
            }
        }
    }

    public static NsLogger getLogger(Class clazz){
        NsLogger log = new NsLogger();
        log.clazz = clazz;
        log.initLogger();
        return log;
    }

    private void initLogger(){
        if(initSlf4j() || initCommonLog() || initLogBack()|| initLog4j2() || initLog4j() || initJavaLog()){
            logMethod = debugMethod;
            if(isInfo){
                logMethod = infoMethod;
            } else if (isError) {
                logMethod = errorMethod;
            }
        }
    }

    private boolean initSlf4j(){
        try {
            Class clazzFactory = Class.forName("org.slf4j.LoggerFactory");
            Method mGetLogger = clazzFactory.getMethod("getLogger", Class.class);
            logger = mGetLogger.invoke(null, clazz);
            Class clazzLogger = Class.forName("org.slf4j.Logger");
            debugMethod = clazzLogger.getMethod(DEBUG, String.class);
            infoMethod = clazzLogger.getMethod(INFO, String.class);
            errorMethod = clazzLogger.getMethod(ERROR, String.class);
            return true;
        }catch (Exception e){ }
        return false;
    }

    private boolean initCommonLog(){
        try{
            Class clazzFactory = Class.forName("org.apache.commons.logging.LogFactory");
            Method mGetLogger = clazzFactory.getMethod("getLog", Class.class);
            logger = mGetLogger.invoke(null, clazz);
            Class clazzLogger = Class.forName("org.apache.commons.logging.Log");
            debugMethod = clazzLogger.getMethod(DEBUG, Object.class);
            infoMethod = clazzLogger.getMethod(INFO, Object.class);
            errorMethod = clazzLogger.getMethod(ERROR, String.class);
            return true;
        }catch (Exception e){ }
        return false;
    }

    private boolean initJavaLog(){
        // ���û�� slf4j Ҳû�� commons.logging, ����JDK�Դ���
        logger = Logger.getLogger(clazz.getName());
        Logger logger1 = (Logger) logger;
        Handler consoleHandler = new ConsoleHandler();
        consoleHandler.setFormatter(new DefaultFormatter());
        logger1.addHandler(consoleHandler);
        logger1.setUseParentHandlers(false);
        try {
            debugMethod = Logger.class.getMethod("config", String.class);
            infoMethod = Logger.class.getMethod(INFO, String.class);
            errorMethod = Logger.class.getMethod(ERROR, String.class);
            return true;
        } catch (Exception ex) { }
        return false;
    }

    private boolean initLog4j(){
        try{
            Class clazzLogger = Class.forName("org.apache.log4j.Logger");
            Method mGetLogger = clazzLogger.getMethod("getLogger", Class.class);
            logger = mGetLogger.invoke(null, clazz);

            debugMethod = clazzLogger.getMethod(DEBUG, String.class);
            infoMethod = clazzLogger.getMethod(INFO, String.class);
            errorMethod = clazzLogger.getMethod(ERROR, String.class);
            return true;
        }catch (Exception e){ }
        return false;
    }

    private boolean initLog4j2(){
        try{
            Class clazzFactory = Class.forName("org.apache.logging.log4j.LogManager");
            Method mGetLogger = clazzFactory.getMethod("getLogger", Class.class);
            logger = mGetLogger.invoke(null, clazz);
            Class clazzLogger = Class.forName("org.apache.logging.log4j.Logger");
            debugMethod = clazzLogger.getMethod(DEBUG, String.class);
            infoMethod = clazzLogger.getMethod(INFO, String.class);
            errorMethod = clazzLogger.getMethod(ERROR, String.class);
            return true;
        }catch (Exception e){ }
        return false;
    }

    private boolean initLogBack(){
        try{
            Class clazzFactory = Class.forName("ch.qos.logback.classic.LoggerContext");
            Class clazzLogger = Class.forName("ch.qos.logback.classic.Logger");
            Method mGetLogger = clazzFactory.getMethod("getLogger", Class.class);
            logger = mGetLogger.invoke(clazzFactory.getConstructor().newInstance(), clazz);
            debugMethod = clazzLogger.getMethod(DEBUG, String.class);
            infoMethod = clazzLogger.getMethod(INFO, String.class);
            errorMethod = clazzLogger.getMethod(ERROR, String.class);
            return true;
        }catch (Exception e){
        }
        return false;

    }

    class DefaultFormatter extends Formatter{
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
        @Override
        public String format(LogRecord record) {
            String msg = record.getMessage();
            StringBuilder builder = new StringBuilder(100 + msg.length());
            builder.append("[");
            builder.append(dateFormat.format(new Date(record.getMillis())));
            builder.append("][");
            builder.append(Thread.currentThread().getName()).append("][");
            builder.append(record.getLevel().getName());
            builder.append("] ").append(msg).append("\n");
            return builder.toString();
        }
    }

    public void log(String msg){
        if(!on){
            return;
        }
        try {
            logMethod.invoke(logger, msg);
        } catch (Exception e) {
        }
    }

    public void info(String msg){
//        if(!on){
//            return;
//        }
        try {
            infoMethod.invoke(logger, msg);
        } catch (Exception e) {
        }
    }

    public void debug(String msg){
//        if(!on){
//            return;
//        }
        try {
            debugMethod.invoke(logger, msg);
        } catch (Exception e) {
        }
    }

    public void error(String msg,Throwable t){
//        if(!on){
//            return;
//        }
        try {
            errorMethod.invoke(logger, msg,t);
        } catch (Exception e) {
        }
    }

    public static void openLog(boolean on) {
        NsLogger.on = on;
    }
}

logback.xm配置如下:

 <!--  从 System Property 中读取 log.level,默认值为 INFO -->
    <property name="log.level" value="${log.level:-DEBUG}" />

    <logger level="${log.level}" name="cn.com.infosec.ucypher" additivity="false">
        <appender-ref ref="app-log" />
    </logger>

每种方案都有优缺点, 结合自己项目实际情况选择,我们最终选择使用反射完成,

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值