编码技巧——全局日志开关

开发过程中,业务日志是非常重要且有必要的,往往能帮我们排查问题,如异常原因、入参出参、是否有脏数据、执行到了哪一步等;但是日志也不是越多越好,对于大流量接口,打日志需要慎重,大量的日志会导致频繁的IO、影响机器负载;

" 根据经验,4核8G的机器,1个小时日志量不要超过20W "

——经历过暴涨日志导致机器负载达到80的某模块SE给出的经验

此外,在中间件出异常时,如ZK连接不上、MQ链接异常等情况发生时,重启机器时也会产生疯长的日志量,可能导致服务依旧无法重启成功;这时候,如果有个全局的日志开关,能屏蔽某些指定Class打印的日志,就可以临时解决日志暴涨问题;

这里分别给出Log4j和Logback日志框架下的工具类实现,原理是拿到指定类的Logger对象,设置其日志等级为屏蔽,这一属性是在打印日志时实时判断的,因此无须重启机器生效;触发器是配置中心变更时间消息,可以是ZK、Apollo等;

1. Log4J日志开关

package com.AA.service.helper.logger;

import com.alibaba.fastjson.JSON;
import com.google.common.collect.Maps;
import com.AA.client.ConfigManager;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Level;
import org.apache.log4j.LogManager;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author AA
 * @description 全局日志开关工具
 * @date 2022/3/25
 */
@Slf4j
@Component
public class LoggerSwitchHelper implements InitializingBean {

    /**
     * 仅对指定类屏蔽日志
     */
    private static final String LOGGER_BLACKLIST = "logger.switch.blacklist";

    /**
     * 临时保存上一次关闭的日志对象开关,用于修改配置后恢复原有类下的日志
     */
    private static Map<String, Level> LAST_LOGGER_BLACKLIST_MAP = new ConcurrentHashMap<>();

    @Override
    public void afterPropertiesSet() throws Exception {
        String conf = ConfigManager.getString(LOGGER_BLACKLIST, "");
        refreshLoggerSwitch(conf);

        ConfigManager.addListener((item, type) -> {
            if (StringUtils.equalsIgnoreCase(item.getName(), LOGGER_BLACKLIST)) {
                refreshLoggerSwitch(item.getValue());
            }
        });
    }

    /**
     * 刷新配置
     */
    private synchronized void refreshLoggerSwitch(String conf) {
        try {
            log.info("refreshLoggerSwitch. [conf={}]", conf);
            // 临时记录当前的黑名单
            Map<String, Level> current_logger_blacklist = Maps.newConcurrentMap();
            if (StringUtils.isNotEmpty(conf)) {
                List<String> loggerBlackList = Arrays.asList(StringUtils.split(conf, ","));
                if (CollectionUtils.isNotEmpty(loggerBlackList)) {
                    for (String loggerName : loggerBlackList) {
                        org.apache.log4j.Logger logger = LogManager.getLogger(loggerName);
                        if (null != logger) {
                            if (null != logger.getLevel()) {
                                current_logger_blacklist.put(loggerName, logger.getLevel());
                            }
                            // 当前日志对象的等级为空 且有父日志 则将其的当前等级初始化成父日志对象的等级
                            else if (logger.getAdditivity()) {
                                current_logger_blacklist.put(loggerName, logger.getParent().getLevel());
                            }
                            logger.setLevel(Level.OFF);
                            log.info("logger_turn_off. [loggerName={}]", loggerName);
                        } else {
                            log.info("logger_not_found. [loggerName={}]", loggerName);
                        }
                    }
                }
            }

            // 针对上次关闭的类,这次打开后的日志需要进行恢复
            if (MapUtils.isNotEmpty(LAST_LOGGER_BLACKLIST_MAP)) {
                for (Map.Entry<String, Level> entry : LAST_LOGGER_BLACKLIST_MAP.entrySet()) {
                    String loggerName = entry.getKey();
                    if (!current_logger_blacklist.containsKey(loggerName)) {
                        // 从快照移除
                        LAST_LOGGER_BLACKLIST_MAP.remove(entry.getKey());
                        org.apache.log4j.Logger logger = LogManager.getLogger(loggerName);
                        if (null != logger) {
                            // 根据快照 设置成关闭前的日志等级
                            logger.setLevel(entry.getValue());
                            log.info("logger_remove_fr_blackList_reopen. [loggerName={} level={}]", loggerName, entry.getValue());
                        }
                    }
                }
            }

            // 保存快照
            current_logger_blacklist.forEach((loggerName, level) -> {
                LAST_LOGGER_BLACKLIST_MAP.putIfAbsent(loggerName, level);
            });
            log.info("save_last_loggerCfg. [last_loggerCfg={}]", JSON.toJSONString(LAST_LOGGER_BLACKLIST_MAP));

            log.info("refreshLoggerSwitch_suc.");
        } catch (Exception e) {
            log.error("refreshLoggerSwitch_error. [conf={}]", conf);
        }
    }

}

2. Logback日志开关

再补充下基于logback的实现;

package com.AA.service.config;

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.LoggerContext;
import com.alibaba.fastjson.JSON;
import com.google.common.collect.Maps;
import com.AA.ConfigManager;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author AA
 * @description logback日志开关工具
 * @date 2022/1/19
 */
@Slf4j
@Component
public class LoggerSwitchHelper implements InitializingBean {

    /**
     * 仅对指定类屏蔽日志
     */
    private static final String LOGGER_BLACKLIST = "logger.switch.blacklist";

    /**
     * 临时保存上一次关闭的日志对象开关,用于修改配置后恢复原有类下的日志
     */
    private static Map<String, Level> LAST_LOGGER_BLACKLIST_MAP = new ConcurrentHashMap<>();

    @Override
    public void afterPropertiesSet() throws Exception {
        String conf = ConfigManager.getString(LOGGER_BLACKLIST, "");
        refreshLoggerSwitch(conf);

        ConfigManager.addListener((item, type) -> {
            if (StringUtils.equalsIgnoreCase(item.getName(), LOGGER_BLACKLIST)) {
                refreshLoggerSwitch(item.getValue());
            }
        });
    }

    /**
     * 刷新配置
     */
    private synchronized void refreshLoggerSwitch(String conf) {
        try {
            log.info("refreshLoggerSwitch. [conf={}]", conf);
            // 临时记录当前的黑名单
            Map<String, ch.qos.logback.classic.Level> current_logger_blacklist = Maps.newConcurrentMap();
            // 日志配置上下文
            LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
            if (StringUtils.isNotEmpty(conf)) {
                List<String> loggerBlackList = Arrays.asList(StringUtils.split(conf, ","));
                if (CollectionUtils.isNotEmpty(loggerBlackList)) {

                    for (String loggerName : loggerBlackList) {
                        ch.qos.logback.classic.Logger logger = lc.getLogger(loggerName);
                        if (null != logger) {
                            if (null != logger.getLevel()) {
                                current_logger_blacklist.put(loggerName, logger.getLevel());
                            }
                            // 等级为空取父类等级 异常时取INFO等级
                            else if (logger.isAdditive()) {
                                try {
                                    Field field = logger.getClass().getDeclaredField("parent");
                                    field.setAccessible(true);
                                    final Logger parentLogger = (Logger) field.get(logger);
                                    current_logger_blacklist.put(loggerName, parentLogger.getLevel());
                                } catch (Exception e) {
                                    current_logger_blacklist.put(loggerName, ch.qos.logback.classic.Level.INFO);
                                }
                            }
                            logger.setLevel(Level.OFF);
                            log.info("logger_turn_off. [loggerName={}]", loggerName);
                        } else {
                            log.info("logger_not_found. [loggerName={}]", loggerName);
                        }
                    }
                }
            }

            // 针对上次关闭的类,这次打开后的日志需要进行恢复
            if (MapUtils.isNotEmpty(LAST_LOGGER_BLACKLIST_MAP)) {
                for (Map.Entry<String, ch.qos.logback.classic.Level> entry : LAST_LOGGER_BLACKLIST_MAP.entrySet()) {
                    String loggerName = entry.getKey();
                    if (!current_logger_blacklist.containsKey(loggerName)) {
                        // 从快照移除
                        LAST_LOGGER_BLACKLIST_MAP.remove(entry.getKey());
                        ch.qos.logback.classic.Logger logger = lc.getLogger(loggerName);
                        if (null != logger) {
                            // 根据快照 设置成关闭前的日志等级
                            logger.setLevel(entry.getValue());
                            log.info("logger_remove_fr_blackList_reopen. [loggerName={} level={}]", loggerName, entry.getValue());
                        }
                    }
                }
            }

            // 保存快照
            current_logger_blacklist.forEach((loggerName, level) -> {
                LAST_LOGGER_BLACKLIST_MAP.putIfAbsent(loggerName, level);
            });
            log.info("save_last_loggerCfg. [last_loggerCfg={}]", JSON.toJSONString(LAST_LOGGER_BLACKLIST_MAP));

            log.info("refreshLoggerSwitch_suc.");
        } catch (Exception e) {
            log.error("refreshLoggerSwitch_error. [conf={}]", conf);
        }
    }

}

主要区别于Logger对象的获取;也可以使用策略模式同时兼容两种日志框架;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值