打点日志的一个实现

所谓打点日志,是指用于数据统计的日志,一般前端会有打点的方法,而后端也会有类似需求。按理说,后端的一般的日志也可以做到打点的效果,但是因为一般的日志离散,风格不统一,得到的日志格式不好,不容易切分统计,所以就会想要一个独立的打点日志来实现打点。

实现打点日志的方法

  1. 通过日志模版,增加一种类型的打点日志,则替换模版中的占位符,重新生成一遍日志的配置文件,之后热更新日志配置。
  2. 使用日志框架API,不借助配置文件,直接通过编码的方式,生成logger

这两种各有好处,第一种打点日志实现和具体日志框架不绑定,且系统重启之后,配置不丢失,第二种实现简单,但是要使用具体日志框架的特定事先才行。

下面是一个使用日志框架API 打点日志的实现,代码如下,实际使用的时候,配合一个log.properties,从外部控制日志的一些配置。

下面打点日志,可以实现:

  1. 对于不同业务,增加一个业务枚举类,实现接口,不需要配置日志,即可将日志区分开,打到特定的目录下面
  2. 同一业务枚举下区分子类型,可以用来做数据统计、分类汇总等
  3. 日志使用统一的分隔符,格式统一,方便切分查询
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.spi.ILoggingEvent;
import ch.qos.logback.core.Appender;
import ch.qos.logback.core.Context;
import ch.qos.logback.core.FileAppender;
import ch.qos.logback.core.encoder.Encoder;
import ch.qos.logback.core.rolling.RollingFileAppender;
import ch.qos.logback.core.rolling.RollingPolicy;
import ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy;
import ch.qos.logback.core.util.FileSize;
import org.slf4j.LoggerFactory;

import java.nio.charset.Charset;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 打点日志基础实现的LoggerFactory
 *
 **/
public class BaseLoggerFactory {

    private static final Properties LOG_PROPERTES = new Properties();

    private static String APP_NAME;

    private static String LOG_ROOT;

    /**
     * 日志内容格式
     */
    private static String LOG_PATTERN;
    /**
     * 日志文件名格式
     */
    private static String FILE_PATTERN;
    /**
     * 归档日志文件名格式
     */
    private static String ROLLING_FILE_PATTERN;

    private static int MAX_HISTORY;
    private static String MAX_FILE_SIZE;
    private static LoggerContext CONTEXT = (LoggerContext) LoggerFactory.getILoggerFactory();
    private static Map<String, Logger> cacheLoggers = new ConcurrentHashMap<>();

    static {
        try {
            LOG_PROPERTES.load(BaseLoggerFactory.class.getClassLoader().getResourceAsStream("log.properties"));
        } catch (Exception e) {
            //ignore
        }

        APP_NAME = LOG_PROPERTES.getProperty("appName", "TestApplication");

        LOG_ROOT = System.getProperty("user.home") + "/" + APP_NAME + "/logs";

        LOG_PATTERN = LOG_PROPERTES.getProperty("logPattern",
                "%d{yyyy-MM-dd HH:mm:ss.SSS}|%t|%-5level|%m%n");
        FILE_PATTERN = LOG_PROPERTES.getProperty("filePattern",
                LOG_ROOT + "/statistic/${logBizType}/${logBizType}.log");

        ROLLING_FILE_PATTERN = LOG_PROPERTES.getProperty("rollingFilePattern",
                LOG_ROOT + "/statistic/${logBizType}/${logBizType}.log.%d{yyyyMMdd}%i.gz");

        MAX_HISTORY = Integer.parseInt(LOG_PROPERTES.getProperty("maxHistory", "15"));
        MAX_FILE_SIZE = LOG_PROPERTES.getProperty("maxFileSize", "256MB");
    }

    static Logger getLogger(String name) {
        // 使用双重检查锁,因为ConcurrentHashMap提供了读写操作的happens-before承诺,不会有双重检查锁的弊端
        Logger logger = cacheLoggers.get(name);
        if (logger != null) {
            return logger;
        }
        synchronized (BaseLoggerFactory.class) {
            logger = cacheLoggers.get(name);
            if (logger != null) {
                return logger;
            }
            logger = CONTEXT.getLogger(name);
            logger.addAppender(generateAppender(CONTEXT, name));
            logger.setLevel(Level.INFO);
            logger.setAdditive(false);
            logger = cacheLoggers.putIfAbsent(name, logger);
            return logger;
        }

    }

    private static Appender<ILoggingEvent> generateAppender(Context context, String logBizType) {
        RollingFileAppender<ILoggingEvent> appender = new RollingFileAppender<>();
        // 当前日志的文件命名格式
        String filePatternInstance = FILE_PATTERN.replaceAll("\\$\\{logBizType}", logBizType);
        appender.setFile(filePatternInstance);
        appender.setContext(context);
        appender.setEncoder(generateEncoder(context));
        appender.setRollingPolicy(generateRollingPolicy(context, appender, logBizType));
        //sizeAndTimeBasedRollingPolicy实现了TriggeringPolicy,不需要setTriggeringPolicy()

        appender.start();
        return appender;
    }

    private static RollingPolicy generateRollingPolicy(Context context, FileAppender<ILoggingEvent> fileAppender, String logBizType) {
        SizeAndTimeBasedRollingPolicy<ILoggingEvent> rollingPolicy = new SizeAndTimeBasedRollingPolicy<>();

        rollingPolicy.setContext(context);
        rollingPolicy.setParent(fileAppender);
        // 归档文件的命名格式
        String rollingFilePatternInstance = ROLLING_FILE_PATTERN.replaceAll("\\$\\{logBizType}", logBizType);
        rollingPolicy.setFileNamePattern(rollingFilePatternInstance);
        rollingPolicy.setMaxHistory(MAX_HISTORY);
        rollingPolicy.setMaxFileSize(FileSize.valueOf(MAX_FILE_SIZE));

        rollingPolicy.start();
        return rollingPolicy;
    }

    private static Encoder<ILoggingEvent> generateEncoder(Context context) {
        PatternLayoutEncoder encoder = new PatternLayoutEncoder();
        encoder.setCharset(Charset.forName("UTF-8"));
        encoder.setContext(context);
        // 日志内容的格式
        encoder.setPattern(LOG_PATTERN);

        encoder.start();
        return encoder;
    }
}

import ch.qos.logback.classic.Logger;

/**
 * 打点日志
 * 通过枚举类,可以统计实现不同枚举业务日志分文件统计打点数据
 *
 **/
public class StatisticLogger {

    private static final String DELIMITER = "|";

    public static void statisticLog(ILogBizType logBizType, ISubLogBizType statisticType, Object... args) {
        Logger logger = BaseLoggerFactory.getLogger(logBizType.getCode());
        // 打点日志从需求上说不需要堆栈信息,所以没有涉及堆栈的获取
        StringBuilder msg = new StringBuilder()
                .append(statisticType.getSubType());
        // 为了处理可变参数内容是数组的情况
        for (int i = 0; i < args.length; i++) {
            msg.append(DELIMITER).append("{}");
        }
        logger.info(msg.toString(), args);
    }
}

/**
 * 打点日志使用的业务类型
 * 可以继承此接口实现自己的枚举类来枚举不同业务
 *
 **/
public interface ILogBizType {

    /**
     * 返回一个表示业务类型的code,打点日志的目录名和日志名均依据此命名
     * @return
     */
    String getCode();

}
/**
 * logBizType的细分类型
 *
 * 通过扩展该类实现枚举类,实现区分LogBizType下的不同场景
 **/
@FunctionalInterface
public interface ISubLogBizType {


    /**
     * 子类型,用于区分一个LogBizType下的不同场景使用
     * @return
     */
    int getSubType();

}
/**
 * 打点日志的枚举类
 * 可以继承它拓展自己的业务枚举类
 **/
public enum LogBizType implements ILogBizType{

    /**
     * 错误日志类型
     */
    ERROR("error"),
    /**
     * 默认日志类型
     */
    DEFAULT("default"),
    /**
     * 系统日志类型
     */
    SYS("system");

    LogBizType(String code){
        this.code = code;
    }

    private String code;

    @Override
    public String getCode() {
        return code;
    }
}

  • 0
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值