sofa-bolt源码阅读(5)-日志

6.1 日志类型

bolt的日志文件默认位于{user.home}/logs/bolt目录下

文件名 说明
common-default.log 普通日志
common-error.log 错误级别日志
connection-event.log 连接相关的日志
remoting-rpc.log rpc协议相关的日志

6.2 日志实现

bolt只依赖SLF4j门面框架,可以支持三种类型的日志实现:log4j, log4j2, 和 logback。运行时根据具体项目引入的日志框架,实现日志的自适应打印。

日志隔离的功能是工具包sofa-common-tools实现的。

日志的入口类是BoltLoggerFactory

private static final Logger logger = BoltLoggerFactory.getLogger("CommonDefault");

获取名称为CommonDefault的日志,这个名称是在日志配置文件中配置的。

<AsyncLogger name="CommonDefault" level="${LOG_LEVEL}" additivity="false">
    <appender-ref ref="CommonDefaultAppender"/>
    <appender-ref ref="ERROR-APPENDER"/>
</AsyncLogger>

日志空间 space

public static Logger getLogger(String name) {
    if (name == null || name.isEmpty()) {
        return null;
    }
    return LoggerSpaceManager.getLoggerBySpace(name, BOLT_LOG_SPACE);
}

spaceName参数BOLT_LOG_SPACE的值是com.alipay.remoting,表示的是日志空间的名称。

不同的space使用不同的配置文件。例如space名称为com.alipay.remoting时,bolt会尝试从classpath下的com.alipay.remoting.log目录下寻找配置文件。resouces下有三种日志框架的配置文件,如图

sofa-bolt源码阅读(5)-日志

自适应日志框架

com.alipay.sofa.common.log.LoggerSpaceManager#getLoggerBySpace

public static Logger getLoggerBySpace(String name, String spaceName) {
    return getLoggerBySpace(name, new SpaceId(spaceName),
    Collections.<String, String> emptyMap());
}

public static Logger getLoggerBySpace(String name, SpaceId spaceId,
                                      Map<String, String> properties) {
    //init first
    init(spaceId, properties);
    return MultiAppLoggerSpaceManager.getLoggerBySpace(name, spaceId);
}

MultiAppLoggerSpaceManager中实现了自适应逻辑

public static Logger getLoggerBySpace(String name, SpaceId spaceId, ClassLoader spaceClassloader) {
    //创建日志工厂类
    AbstractLoggerSpaceFactory abstractLoggerSpaceFactory = getILoggerFactoryBySpaceName(
        spaceId, spaceClassloader);
    return abstractLoggerSpaceFactory.getLogger(name);
}

创建日志工厂方法已经返回的是具体的日志工厂实现类(由构造器LoggerSpaceFactoryBuilder的实现类构建)

  • log4j -> LoggerSpaceFactory4Log4jBuilder
  • log4j2 -> LoggerSpaceFactory4Log4j2Builder
  • logback -> LoggerSpaceFactory4LogbackBuilder
  • commons-log -> LoggerSpaceFactory4CommonsLoggingBuilder

从工厂类中获取日志对象Logger并返回

创建日志工厂

private static AbstractLoggerSpaceFactory createILoggerFactory(SpaceId spaceId,
                                           ClassLoader spaceClassloader) {
    if (System.getProperty(SOFA_MIDDLEWARE_LOG_DISABLE_PROP_KEY) != null
        && Boolean.TRUE.toString().equalsIgnoreCase(
            System.getProperty(SOFA_MIDDLEWARE_LOG_DISABLE_PROP_KEY))) {
        ReportUtil.reportWarn("Sofa-Middleware-Log is disabled!  -D"
                              + SOFA_MIDDLEWARE_LOG_DISABLE_PROP_KEY + "=true");
        return NOP_LOGGER_FACTORY;
    }
    // 设置日志的配置文件
    LogEnvUtils.processGlobalSystemLogProperties();

    try {
        if (LogEnvUtils.isLogbackUsable(spaceClassloader)) {
            String isLogbackDisable = System
                .getProperty(LOGBACK_MIDDLEWARE_LOG_DISABLE_PROP_KEY);
            if (isLogbackDisable != null
                && Boolean.TRUE.toString().equalsIgnoreCase(isLogbackDisable)) {
                ReportUtil.reportWarn("Logback-Sofa-Middleware-Log is disabled!  -D"
                                      + LOGBACK_MIDDLEWARE_LOG_DISABLE_PROP_KEY + "=true");
            } else {
                ReportUtil.report("Actual binding is of type [ " + spaceId.toString()
                                  + " Logback ]");
                LoggerSpaceFactoryBuilder loggerSpaceFactory4LogbackBuilder = new LoggerSpaceFactory4LogbackBuilder(
                    spacesMap.get(spaceId));

                return loggerSpaceFactory4LogbackBuilder.build(spaceId.getSpaceName(),
                    spaceClassloader);
            }
        }

        if (LogEnvUtils.isLog4j2Usable(spaceClassloader)) {
            String isLog4j2Disable = System.getProperty(LOG4J2_MIDDLEWARE_LOG_DISABLE_PROP_KEY);
            if (isLog4j2Disable != null
                && Boolean.TRUE.toString().equalsIgnoreCase(isLog4j2Disable)) {
                ReportUtil.reportWarn("Log4j2-Sofa-Middleware-Log is disabled!  -D"
                                      + LOG4J2_MIDDLEWARE_LOG_DISABLE_PROP_KEY + "=true");
            } else {
                ReportUtil.report("Actual binding is of type [ " + spaceId.toString()
                                  + " Log4j2 ]");
                LoggerSpaceFactoryBuilder loggerSpaceFactory4Log4j2Builder = new LoggerSpaceFactory4Log4j2Builder(
                    spacesMap.get(spaceId));

                return loggerSpaceFactory4Log4j2Builder.build(spaceId.getSpaceName(),
                    spaceClassloader);
            }
        }

        if (LogEnvUtils.isLog4jUsable(spaceClassloader)) {
            String isLog4jDisable = System.getProperty(LOG4J_MIDDLEWARE_LOG_DISABLE_PROP_KEY);
            if (isLog4jDisable != null
                && Boolean.TRUE.toString().equalsIgnoreCase(isLog4jDisable)) {
                ReportUtil.reportWarn("Log4j-Sofa-Middleware-Log is disabled!  -D"
                                      + LOG4J_MIDDLEWARE_LOG_DISABLE_PROP_KEY + "=true");
            } else {
                ReportUtil.report("Actual binding is of type [ " + spaceId.toString()
                                  + " Log4j ]");
                LoggerSpaceFactoryBuilder loggerSpaceFactory4Log4jBuilder = new LoggerSpaceFactory4Log4jBuilder(
                    spacesMap.get(spaceId));

                return loggerSpaceFactory4Log4jBuilder.build(spaceId.getSpaceName(),
                    spaceClassloader);
            }
        }

        if (LogEnvUtils.isCommonsLoggingUsable(spaceClassloader)) {
            //此种情形:commons-logging 桥接到 log4j 实现,默认日志实现仍然是 log4j
            String isLog4jDisable = System
                .getProperty(LOG4J_COMMONS_LOGGING_MIDDLEWARE_LOG_DISABLE_PROP_KEY);
            if (isLog4jDisable != null
                && Boolean.TRUE.toString().equalsIgnoreCase(isLog4jDisable)) {
                ReportUtil
                    .reportWarn("Log4j-Sofa-Middleware-Log(But adapter commons-logging to slf4j) is disabled!  -D"
                                + LOG4J_COMMONS_LOGGING_MIDDLEWARE_LOG_DISABLE_PROP_KEY
                                + "=true");
            } else {
                ReportUtil.report("Actual binding is of type [ " + spaceId.toString()
                                  + " Log4j (Adapter commons-logging to slf4j)]");

                LoggerSpaceFactoryBuilder loggerSpaceFactory4Log4jBuilder = new LoggerSpaceFactory4CommonsLoggingBuilder(
                    spacesMap.get(spaceId));

                return loggerSpaceFactory4Log4jBuilder.build(spaceId.getSpaceName(),
                    spaceClassloader);
            }
        }

        ReportUtil.reportWarn("No log util is usable, Default app logger will be used.");
    } catch (Throwable e) {
        ReportUtil.reportError("Build ILoggerFactory error! Default app logger will be used.",
            e);
    }

    return NOP_LOGGER_FACTORY;
}

LogEnvUtils用来判断是否存在相应的类包,例如判断类路径下是否存在log4j2的jar包

public static boolean isLog4j2Usable(ClassLoader spaceClassloader) {
    AssertUtil.notNull(spaceClassloader);
    try {
        return (spaceClassloader.loadClass("org.apache.logging.slf4j.Log4jLoggerFactory") != null);
    } catch (ClassNotFoundException e) {
        return false;
    }
}

如果存在多个日志jar包,代码中可以看出优先级为

  1. logback
  2. log4j2
  3. log4j
  4. commons-log
  5. default

按顺序一旦找到某一日志jar包,就调用相应构造器的build方法进行构建。

创建Log4j2工厂

@Override
public AbstractLoggerSpaceFactory build(String spaceName, ClassLoader spaceClassloader) {
    AssertUtil.hasText(spaceName);
    AssertUtil.notNull(spaceClassloader);

    //load config file
    URL configFileUrl = getSpaceLogConfigFileURL(spaceClassloader, spaceName);

    // set default logging.level
    specifySpaceLogConfigProperites(spaceName);

    return doBuild(spaceName, spaceClassloader, configFileUrl);
}
  1. 加载配置文件

    private URL getSpaceLogConfigFileURL(ClassLoader spaceClassloader, String spaceName) {
        String suffix = LogEnvUtils.getLogConfEnvSuffix(spaceName);
    
        //TODO avoid this pattern "log-conf.xml.console"
        String logConfigLocation = spaceName.replace('.', '/') + "/" + LOG_DIRECTORY + "/"
                                   + getLoggingToolName() + "/" + LOG_XML_CONFIG_FILE_NAME + suffix;
        URL configFileUrl = spaceClassloader.getResource(logConfigLocation);
    
        //recommend this pattern "log-conf-console.xml"
        if (configFileUrl == null && suffix != null && !suffix.isEmpty()) {
            //try again with another env profile file pattern;
            logConfigLocation = spaceName.replace('.', '/')
                                + "/"
                                + LOG_DIRECTORY
                                + "/"
                                + getLoggingToolName()
                                + "/"
                                + String.format(LOG_XML_CONFIG_FILE_ENV_PATTERN,
                                    suffix.substring(1));
            configFileUrl = spaceClassloader.getResource(logConfigLocation);
        }
        AssertUtil.state(configFileUrl != null, this + " build error: No " + getLoggingToolName()
                                                + " config file (" + configFileUrl + ") found!");
        return configFileUrl;
    }

    日志文件名后缀

    // 指定特定spaceName的环境配置,值格式: spaceName1:dev&spaceName2:test&spaceName3:product
    String LOG_ENV_SUFFIX                                        = "log.env.suffix";

    日志配置文件路径

    参数
    suffix 未配置,空字符串
    LOG_DIRECTORY log
    getLoggingToolName() logback
    log4j2
    log4j
    LOG_XML_CONFIG_FILE_NAME log-conf.xml

    日志配置文件可能的地址是

    • com/alipay/remoting/log/log4j2/log-conf.xml
    • com/alipay/remoting/log/log4j2/log-conf.xml.console
    • com/alipay/remoting/log/log4j2/log-conf-console.xml
  2. 设置默认日志级别

    private void specifySpaceLogConfigProperites(String spaceName) {
        //如果system.properties 与 properites 都含有某分配置,那么以 system.properties 为准,同时WARN警告,properties中重复定义会被抛弃;
        Iterator<Map.Entry<Object, Object>> iterator = spaceInfo.properties().entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<Object, Object> entry = iterator.next();
            if (System.getProperties().containsKey(entry.getKey())) {
                iterator.remove();
                logger.warn(
                    "Props key {}  is also already existed in System.getProps ({}:{}),so use it!",
                    entry.getKey(), entry.getKey(), System.getProperty((String) entry.getKey()));
            }
        }
    
        /**
         * == 1.space's logger path
         */
        String loggingPathKey = LOG_PATH_PREFIX + spaceName;
        if (System.getProperty(loggingPathKey) == null && System.getProperty(LOG_PATH) != null
            && spaceInfo.properties().getProperty(loggingPathKey) == null) {
            spaceInfo.properties().setProperty(loggingPathKey, System.getProperty(LOG_PATH));
        }
    
        /**
         * == 2.space's logger level
         */
        String loggingLevelKey = LOG_LEVEL_PREFIX + spaceName;
        if (System.getProperty(loggingLevelKey) == null
            && spaceInfo.properties().getProperty(loggingLevelKey) == null) {
            spaceInfo.properties().setProperty(loggingLevelKey,
                Constants.DEFAULT_MIDDLEWARE_SPACE_LOG_LEVEL);
        }
    
    }
  3. 构建

    com.alipay.sofa.common.log.factory.LoggerSpaceFactory4Log4j2Builder#doBuild
    @Override
    public AbstractLoggerSpaceFactory doBuild(String spaceName, ClassLoader spaceClassloader,
                                              URL url) {
    
        try {
            final LoggerContext context = new LoggerContext(spaceName, null, url.toURI());
            Configuration config = null;
            ConfigurationFactory configurationFactory = ConfigurationFactory.getInstance();
            try {
                //log4j-core 2.3 version
                Class[] parameterTypes = new Class[3];
                parameterTypes[0] = String.class;
                parameterTypes[1] = URI.class;
                parameterTypes[2] = ClassLoader.class;
                Method getConfigurationMethod = configurationFactory.getClass().getMethod(
                    "getConfiguration", parameterTypes);
                config = (Configuration) getConfigurationMethod.invoke(configurationFactory,
                    spaceName, url.toURI(), spaceClassloader);
            } catch (NoSuchMethodException noSuchMethodException) {
                //log4j-core 2.8 version
                Class[] parameterTypes = new Class[4];
                parameterTypes[0] = LoggerContext.class;
                parameterTypes[1] = String.class;
                parameterTypes[2] = URI.class;
                parameterTypes[3] = ClassLoader.class;
                Method getConfigurationMethod = configurationFactory.getClass().getMethod(
                    "getConfiguration", parameterTypes);
                config = (Configuration) getConfigurationMethod.invoke(configurationFactory,
                    context, spaceName, url.toURI(), spaceClassloader);
            }
            if (config == null) {
                throw new RuntimeException("No log4j2 configuration are found.");
            }
            for (Map.Entry entry : getProperties().entrySet()) {
                //from Map<String,String>
                config.getProperties().put((String) entry.getKey(), (String) entry.getValue());
            }
    
            for (Map.Entry entry : System.getProperties().entrySet()) {
                //from Map<String,String>
                config.getProperties().put((String) entry.getKey(), (String) entry.getValue());
            }
    
            context.start(config);
    
            return new AbstractLoggerSpaceFactory(getLoggingToolName()) {
    
                private ConcurrentMap<String, org.apache.logging.slf4j.Log4jLogger> loggerMap = new ConcurrentHashMap<String, org.apache.logging.slf4j.Log4jLogger>();
    
                @Override
                public Logger setLevel(String loggerName, AdapterLevel adapterLevel)
                                                                                    throws Exception {
                    org.apache.logging.slf4j.Log4jLogger log4jLoggerAdapter = (org.apache.logging.slf4j.Log4jLogger) this
                        .getLogger(loggerName);
                    final String key = Logger.ROOT_LOGGER_NAME.equals(loggerName) ? LogManager.ROOT_LOGGER_NAME
                        : loggerName;
                    org.apache.logging.log4j.core.Logger log4j2Logger = context.getLogger(key);
                    //level
                    org.apache.logging.log4j.Level log4j2Level = this.toLog4j2Level(adapterLevel);
                    log4j2Logger.setLevel(log4j2Level);
                    return log4jLoggerAdapter;
                }
    
                @Override
                public org.slf4j.Logger getLogger(String name) {
                    final String key = Logger.ROOT_LOGGER_NAME.equals(name) ? LogManager.ROOT_LOGGER_NAME
                        : name;
                    org.apache.logging.log4j.core.Logger log4jLogger = context.getLogger(key);
                    Log4jLogger oldInst = this.loggerMap.get(key);
                    if (oldInst != null) {
                        return oldInst;
                    }
                    Log4jLogger newInst = new Log4jLogger(log4jLogger, key);
                    oldInst = this.loggerMap.putIfAbsent(key, newInst);
                    return oldInst == null ? newInst : oldInst;
                }
    
                private org.apache.logging.log4j.Level toLog4j2Level(AdapterLevel adapterLevel) {
                    if (adapterLevel == null) {
                        throw new IllegalStateException(
                            "AdapterLevel is NULL when adapter to log4j2.");
                    }
                    switch (adapterLevel) {
                        case TRACE:
                            return Level.TRACE;
                        case DEBUG:
                            return Level.DEBUG;
                        case INFO:
                            return Level.INFO;
                        case WARN:
                            return Level.WARN;
                        case ERROR:
                            return Level.ERROR;
                        default:
                            throw new IllegalStateException(adapterLevel
                                                            + " is unknown when adapter to log4j2.");
                    }
                }
            };
        } catch (Throwable e) {
            throw new IllegalStateException("Log4j2 loggerSpaceFactory build error!", e);
        }
    }

    调用ConfigurationFactory解析log4j2的配置文件,然后返回匿名类。匿名类的getLogger方法就是前面生成工厂类后获取Logger对象时调用的。

sofa-bolt源码阅读(5)-日志

上一篇:【转】如何使用MAT分析内存泄漏


下一篇:plsql的一些设置记录