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下有三种日志框架的配置文件,如图
自适应日志框架
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包,代码中可以看出优先级为
- logback
- log4j2
- log4j
- commons-log
- 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);
}
-
加载配置文件
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
log4jLOG_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
-
设置默认日志级别
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); } }
-
构建
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对象时调用的。