前面的文章主要介绍了logback将logback.xml中的节点路径解析成一个个的model, 最后在DefaultProcessor中使用modelHandler对model进行处理, 本节将介绍几个常用标签节点的modelHandler解析的详细过程
configuration节点对应的是ConfigurationModel, 使用ConfigurationModelHandlerFull解析, ConfigurationModelHandlerFull继承了ConfigurationModelHandler类, 我们先看一下ConfigurationModelHandler
public class ConfigurationModelHandler extends ModelHandlerBase {
ConfigurationModel configurationModel = (ConfigurationModel) model;
// See LOGBACK-527 (the system property is looked up first). Thus, it overrides
// the equivalent property in the config file. This reversal of scope priority
// is justified by the use case: the admin trying to chase rogue config file
// 1.debug属性系统; 属性中获取 logback.debug
String debugAttrib = OptionHelper.getSystemProperty(DEBUG_SYSTEM_PROPERTY_KEY, null);
if (debugAttrib == null) {
// configuration的debug属性
debugAttrib = mic.subst(configurationModel.getDebugStr());
}
// 1.logback.debug不是空 2并且不是非false 3并且不是null字符串
if (!(OptionHelper.isNullOrEmptyOrAllSpaces(debugAttrib) || debugAttrib.equalsIgnoreCase(FALSE.toString())
|| debugAttrib.equalsIgnoreCase(NULL_STR))) {
// 添加OnConsoleStatusListener监听器, 并打印状态
StatusListenerConfigHelper.addOnConsoleListenerInstance(context, new OnConsoleStatusListener());
}
// 查询scan属性, 非false的话打印日志
processScanAttrib(mic, configurationModel);
LoggerContext lc = (LoggerContext) context;
// packagingData属性, 用于打印打包信息, 默认是false
boolean packagingData = OptionHelper.toBoolean(mic.subst(configurationModel.getPackagingDataStr()),
LoggerContext.DEFAULT_PACKAGING_DATA);
lc.setPackagingDataEnabled(packagingData);
ContextUtil contextUtil = new ContextUtil(context);
contextUtil.addGroovyPackages(lc.getFrameworkPackages());
}
方法小结
debug
属性和scan
属性和packagingData
属性-Dlogback.debug
设置系统级参数来控制, 它的优先级比configuration标签的debug属性还要高ConfigurationModelHandlerFull
public class ConfigurationModelHandlerFull extends ConfigurationModelHandler {
protected void postProcessScanAttrib(ModelInterpretationContext mic, ConfigurationModel configurationModel) {
// scan属性
String scanStr = mic.subst(configurationModel.getScanStr());
// scanPeriod属性
String scanPeriodStr = mic.subst(configurationModel.getScanPeriodStr());
detachedPostProcess(scanStr, scanPeriodStr);
}
public void detachedPostProcess(String scanStr, String scanPeriodStr) {
// scan 非false, 一般可以设置scan为true
if (!OptionHelper.isNullOrEmptyOrAllSpaces(scanStr) && !"false".equalsIgnoreCase(scanStr)) {
// 默认是ScheduledThreadPoolExecutor类, 核心线程是4个
ScheduledExecutorService scheduledExecutorService = context.getScheduledExecutorService();
// 1.校验热加载文件;动态监听的配置文件, 默认是监听logback.xml文件, 所以默认是true
// 可以使用configuration/propertiesConfigurator节点来指定热加载的文件
boolean watchPredicateFulfilled = ConfigurationWatchListUtil.watchPredicateFulfilled(context);
if (!watchPredicateFulfilled) {
addWarn(FAILED_WATCH_PREDICATE_MESSAGE_1);
addWarn(FAILED_WATCH_PREDICATE_MESSAGE_2);
return;
}
// 2.创建周期任务, 可以用来动态修改日志等级
ReconfigureOnChangeTask rocTask = new ReconfigureOnChangeTask();
rocTask.setContext(context);
addInfo("Registering a new ReconfigureOnChangeTask " + rocTask);
// 3.发布热加载对象注册事件
context.fireConfigurationEvent(ConfigurationEvent.newConfigurationChangeDetectorRegisteredEvent(rocTask));
// 扫描间隔 scanPeriod 默认是1min; 格式 '数字 单位'
Duration duration = getDurationOfScanPeriodAttribute(scanPeriodStr, SCAN_PERIOD_DEFAULT);
addInfo("Will scan for changes in [" + ConfigurationWatchListUtil.getConfigurationWatchList(context) + "] ");
addInfo("Setting ReconfigureOnChangeTask scanning period to " + duration);
// 4.执行周期任务
ScheduledFuture<?> scheduledFuture = scheduledExecutorService.scheduleAtFixedRate(rocTask, duration.getMilliseconds(), duration.getMilliseconds(),
TimeUnit.MILLISECONDS);
rocTask.setScheduredFuture(scheduledFuture);
context.addScheduledFuture(scheduledFuture);
}
}
}
方法小结
configuration/propertiesConfigurator
标签添加热加载的文件, 只支持添加properties后缀和网络地址的文件scanPeriod="5 minute"
, 单位可选为milli(或millisecond)、second(或seconde)、minute、hour、dayReconfigureOnChangeTask热加载配置文件
public class ReconfigureOnChangeTask extends ContextAwareBase implements Runnable {
public void run() {
// 1.发布热加载运行事件
context.fireConfigurationEvent(newConfigurationChangeDetectorRunningEvent(this));
// 2.获取热加载需要扫描的文件们
ConfigurationWatchList configurationWatchList = ConfigurationWatchListUtil.getConfigurationWatchList(context);
if (configurationWatchList == null) {
addWarn("Empty ConfigurationWatchList in context");
return;
}
if (configurationWatchList.emptyWatchLists()) {
addInfo("Both watch lists are empty. Disabling ");
return;
}
// 3.获取热加载的本地文件
File changedFile = configurationWatchList.changeDetectedInFile();
// 4.获取热加载的网络文件
URL changedURL = configurationWatchList.changeDetectedInURL();
if (changedFile == null && changedURL == null) {
return;
}
// 5.发布文件变更事件
context.fireConfigurationEvent(ConfigurationEvent.newConfigurationChangeDetectedEvent(this));
addInfo(DETECTED_CHANGE_IN_CONFIGURATION_FILES);
// 6.处理本地文件变更
if (changedFile != null) {
changeInFile(changedFile, configurationWatchList);
}
// 7.处理网络文件变更
if (changedURL != null) {
changeInURL(changedURL);
}
}
}
方法小结
这里只关注本地文件的变更
private void changeInFile(File changedFile, ConfigurationWatchList configurationWatchList) {
// properties结尾的文件
if (changedFile.getName().endsWith(PROPERTIES_FILE_EXTENSION)) {
runPropertiesConfigurator(changedFile);
return;
}
// 下面的配置一般就是logback.xml修改了
// ======== fuller processing below
addInfo(CoreConstants.RESET_MSG_PREFIX + "named [" + context.getName() + "]");
// 取消正在进行的任务
cancelFutureInvocationsOfThisTaskInstance();
// 获取主配置文件, 一般是logback.xml
URL mainConfigurationURL = configurationWatchList.getMainURL();
LoggerContext lc = (LoggerContext) context;
// 主配置文件是xml文件才处理
if (mainConfigurationURL.toString().endsWith("xml")) {
performXMLConfiguration(lc, mainConfigurationURL);
} else if (mainConfigurationURL.toString().endsWith("groovy")) {
addError("Groovy configuration disabled due to Java 9 compilation issues.");
}
}
// properties结尾的文件
private void runPropertiesConfigurator(Object changedObject) {
addInfo("Will run PropertyConfigurator on "+changedObject);
PropertiesConfigurator propertiesConfigurator = new PropertiesConfigurator();
propertiesConfigurator.setContext(context);
try {
// 磁盘文件
if (changedObject instanceof File) {
File changedFile = (File) changedObject;
propertiesConfigurator.doConfigure(changedFile);
}
// 网络文件
else if(changedObject instanceof URL) {
URL changedURL = (URL) changedObject;
propertiesConfigurator.doConfigure(changedURL);
}
// 发布修改成功事件
context.fireConfigurationEvent(newPartialConfigurationEndedSuccessfullyEvent(this));
} catch (JoranException e) {
addError("Failed to reload "+ changedObject);
}
}
这里将本地文件分为.properties和.xml为后缀两种; .properties的文件使用PropertiesConfigurator来处理, 只支持修改日志级别, .xml文件就支持整个日志文件的变更了
这里关注下.properties文件的变更, 下面是PropertiesConfigurator中的核心代码
PropertiesConfigurator
void doConfigure(Properties properties) {
// 非logback开头的属性们
Map<String, String> variablesMap = extractVariablesMap(properties);
// logback开头的属性们
Map<String, String> instructionMap = extractLogbackInstructionMap(properties);
this.variableSubstitutionsHelper = new VariableSubstitutionsHelper(context, variablesMap);
// 设置日志等级
configureLoggers(instructionMap);
// root日志等级
configureRootLogger(instructionMap);
}
void configureLoggers(Map<String, String> instructionMap) {
for (String key : instructionMap.keySet()) {
// logback.logger. 格式开头的属性
if (key.startsWith(LOGBACK_LOGGER_PREFIX)) {
// 取logback.logger.后面的部分
String loggerName = key.substring(LOGBACK_LOGGER_PREFIX_LENGTH);
// 取值
String value = subst(instructionMap.get(key));
// 设置日志等级
setLevel(loggerName, value);
}
}
}
void configureRootLogger(Map<String, String> instructionMap) {
// logback.root开头的属性
String val = subst(instructionMap.get(LOGBACK_ROOT_LOGGER_PREFIX));
if (val != null) {
// root的日志级别
setLevel(org.slf4j.Logger.ROOT_LOGGER_NAME, val);
}
}
private void setLevel(String loggerName, String levelStr) {
Logger logger = getLoggerContext().getLogger(loggerName);
// 等级levelStr为INHERITED 或者 null字符串
if (JoranConstants.INHERITED.equalsIgnoreCase(levelStr) || NULL.equalsIgnoreCase(levelStr)) {
// root
if (Logger.ROOT_LOGGER_NAME.equalsIgnoreCase(loggerName)) {
// 报错
addError(ErrorCodes.ROOT_LEVEL_CANNOT_BE_SET_TO_NULL);
} else {
addInfo("Setting level of logger [" + loggerName + "] to null, i.e. INHERITED");
logger.setLevel(null);
}
} else {
// 修改日志的级别; 默认是debug; 可选有ALL,TRACE,DEBUG,INFO,WARN,ERROR,OFF
Level level = Level.toLevel(levelStr);
logger.setLevel(level);
}
}
方法小结
logback.root.
开头, 其它类的日志以logback.logger.
开头ALL,TRACE,DEBUG,INFO,WARN,ERROR,OFF
关于configuration/propertiesConfigurator
标签的解析, 我们简单看一下
PropertiesConfiguratorModelHandler
public void detachedHandle(ContextAwarePropertyContainer capc, Model model) throws ModelHandlerException {
PropertiesConfiguratorModel propertyConfiguratorModel = (PropertiesConfiguratorModel) model;
this.optional = OptionHelper.toBoolean(propertyConfiguratorModel.getOptional(), false);
// resource、url、file三选一
if (!checkAttributes(propertyConfiguratorModel)) {
inError = true;
return;
}
InputStream in = getInputStream(capc, propertyConfiguratorModel);
if (in == null) {
inError = true;
return;
}
addInfo("Reading configuration from [" + getAttribureInUse() + "]");
PropertiesConfigurator propertiesConfigurator = new PropertiesConfigurator();
propertiesConfigurator.setContext(capc.getContext());
try {
// 根据配置的文件修改日志级别
propertiesConfigurator.doConfigure(in);
} catch (JoranException e) {
addError("Could not configure from " + getAttribureInUse());
throw new ModelHandlerException(e);
}
}
protected InputStream getInputStream(ContextAwarePropertyContainer capc, ResourceModel resourceModel) {
URL inputURL = getInputURL(capc, resourceModel);
if (inputURL == null)
return null;
// 添加到动态监听列表
ConfigurationWatchListUtil.addToWatchList(context, inputURL, CREATE_CWL_IF_NOT_ALREADY_CREATED);
return openURL(inputURL);
}
方法小结
resource、url、file三选一
指定热加载文件
, 使用classpath下的application-log.properties
来动态设置日志级别一般的关于热加载的可以配置如下
<configuration scan="true" scanPeriod="5 minute">
<propertiesConfigurator resource="reConfiglog.properties"/>
configuration>
// reConfigLog的内容可以如下
# 指定per.qiao.App这个类的日志级别(也可以per.qiao指定到包)
logback.logger.per.qiao.App=DEBUG
# 指定root日志的日志级别
logback.root=DEBUG
property标签对应的是PropertyAction->PropertyModel->PropertyModelHandler
variable标签和property标签是等同的
PropertyModelHandler类解析
public class PropertyModelHandler extends ModelHandlerBase {
@Override
public void handle(ModelInterpretationContext mic, Model model) {
PropertyModel propertyModel = (PropertyModel) model;
PropertyModelHandlerHelper propertyModelHandlerHelper = new PropertyModelHandlerHelper(this);
propertyModelHandlerHelper.setContext(context);
// 核心方法
propertyModelHandlerHelper.handlePropertyModel(mic, propertyModel);
}
}
// PropertyModelHandlerHelper#handlePropertyModel
public void handlePropertyModel(ContextAwarePropertyContainer capc, PropertyModel propertyModel) {
// scope属性, 可选项: LOCAL, CONTEXT, SYSTEM, 默认是LOCAL
ActionUtil.Scope scope = ActionUtil.stringToScope(propertyModel.getScopeStr());
// 仅file不为空
if (checkFileAttributeSanity(propertyModel)) {
String file = propertyModel.getFile();
file = capc.subst(file);
try (FileInputStream istream = new FileInputStream(file)) {
// 从properties中获取数据
PropertyModelHandlerHelper.loadAndSetProperties(capc, istream, scope);
} catch (FileNotFoundException e) {
// ...
}
}
// 仅resource不为空
else if (checkResourceAttributeSanity(propertyModel)) {
String resource = propertyModel.getResource();
resource = capc.subst(resource);
URL resourceURL = Loader.getResourceBySelfClassLoader(resource);
if (resourceURL == null) {
addError("Could not find resource [" + resource + "].");
} else {
try (InputStream istream = resourceURL.openStream();) {
PropertyModelHandlerHelper.loadAndSetProperties(capc, istream, scope);
} catch (IOException e) {
addError("Could not read resource file [" + resource + "].", e);
}
}
}
// name和value不为空, 并且file和resource为空
else if (checkValueNameAttributesSanity(propertyModel)) {
String value = propertyModel.getValue();
value = value.trim();
value = capc.subst(value);
ActionUtil.setProperty(capc, propertyModel.getName(), value, scope);
} else {
addError(ModelConstants.INVALID_ATTRIBUTES);
}
}
方法小结
听说你想给我点颜色看看, show me some color see see!!!
好的, logback也支持在控制台打印带点颜色的日志
ConversionRuleAction->ConversionRuleModel->ConversionRuleModelHandler
public void handle(ModelInterpretationContext mic, Model model) throws ModelHandlerException {
ConversionRuleModel conversionRuleModel = (ConversionRuleModel) model;
String converterClass = conversionRuleModel.getClassName();
if (OptionHelper.isNullOrEmptyOrAllSpaces(converterClass)) {
addWarn("Missing className. This should have been caught earlier.");
inError = true;
return;
} else {
// 支持从import中获取
converterClass = mic.getImport(converterClass);
}
// 转换的那个词, 比如%d{yyyy-MM-dd HH:mm:ss.SSS}中的d, 请设置唯一哦, 否则就是覆盖了
String conversionWord = conversionRuleModel.getConversionWord();
try {
// 解析pattern 规则的Converter们
Map<String, Supplier<DynamicConverter>> ruleRegistry = (Map<String, Supplier<DynamicConverter>>) context
.getObject(CoreConstants.PATTERN_RULE_REGISTRY_FOR_SUPPLIERS);
if (ruleRegistry == null) {
ruleRegistry = new HashMap<>();
// 放到LoggerContext的objectMap中存起来
context.putObject(CoreConstants.PATTERN_RULE_REGISTRY_FOR_SUPPLIERS, ruleRegistry);
}
addInfo("registering conversion word " + conversionWord + " with class [" + converterClass + "]");
// 绑定conversionWord和converterClass
ConverterSupplierByClassName converterSupplierByClassName = new ConverterSupplierByClassName(conversionWord, converterClass);
converterSupplierByClassName.setContext(getContext());
// 放到map中
ruleRegistry.put(conversionWord, converterSupplierByClassName);
} catch (Exception oops) {
inError = true;
String errorMsg = "Could not add conversion rule to PatternLayout.";
addError(errorMsg);
}
}
举个栗子:
<conversionRule conversionWord="clr" converterClass="ch.qos.logback.core.pattern.color.RedCompositeConverter"/>
// 然后可以在<pattern>标签中这么用了 %clr(%d{yyyy-MM-dd HH:mm:ss.SSS})
方法小结
将conversionRule标签中的conversionWord和converterClass组成map中的key和value添加到LoggerContext上下文中; 其中map的key为CoreConstants.PATTERN_RULE_REGISTRY_FOR_SUPPLIERS
AppenderAction->AppenderModel->AppenderModelHandler
AppenderModelHandler
public void handle(ModelInterpretationContext mic, Model model) throws ModelHandlerException {
this.appender = null;
this.inError = false;
AppenderModel appenderModel = (AppenderModel) model;
String appenderName = mic.subst(appenderModel.getName());
// 判断当前appender是否被appender-ref引用, 没有被引用直接打个日志然后退出
if (!mic.hasDependers(appenderName)) {
addWarn("Appender named [" + appenderName + "] not referenced. Skipping further processing.");
skipped = true;
appenderModel.markAsSkipped();
return;
}
addInfo("Processing appender named [" + appenderName + "]");
String originalClassName = appenderModel.getClassName();
// 支持import标签引入
String className = mic.getImport(originalClassName);
try {
addInfo("About to instantiate appender of type [" + className + "]");
// 实例化
appender = (Appender<E>) OptionHelper.instantiateByClassName(className, ch.qos.logback.core.Appender.class,
context);
appender.setContext(context);
appender.setName(appenderName);
// 压入栈
mic.pushObject(appender);
} catch (Exception oops) {
inError = true;
addError("Could not create an Appender of type [" + className + "].", oops);
throw new ModelHandlerException(oops);
}
}
public void postHandle(ModelInterpretationContext mic, Model model) throws ModelHandlerException {
if (inError || skipped) {
return;
}
// 标记为运行
if (appender instanceof LifeCycle) {
((LifeCycle) appender).start();
}
// 标记当前appender被ref引用了
mic.markStartOfNamedDependee(appender.getName());
// 查询栈顶数据
Object o = mic.peekObject();
// 添加到model解析上下文中, key为JoranConstants.APPENDER_BAG
@SuppressWarnings("unchecked")
Map<String, Appender<E>> appenderBag = (Map<String, Appender<E>>) mic.getObjectMap()
.get(JoranConstants.APPENDER_BAG);
appenderBag.put(appender.getName(), appender);
if (o != appender) {
addWarn("The object at the of the stack is not the appender named [" + appender.getName()
+ "] pushed earlier.");
} else {
// 弹出栈顶
mic.popObject();
}
}
方法小结
RootLoggerAction->RootLoggerModel->RootLoggerModelHandler
LoggerModelHandler
public void handle(ModelInterpretationContext mic, Model model) throws ModelHandlerException {
inError = false;
RootLoggerModel rootLoggerModel = (RootLoggerModel) model;
LoggerContext loggerContext = (LoggerContext) this.context;
// root节点日志
root = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME);
// 等级, 可以是占位符
String levelStr = mic.subst(rootLoggerModel.getLevel());
if (!OptionHelper.isNullOrEmptyOrAllSpaces(levelStr)) {
Level level = Level.toLevel(levelStr);
addInfo("Setting level of ROOT logger to " + level);
root.setLevel(level);
}
mic.pushObject(root);
}
这个方法没什么, 就是设置了一下root日志的级别
说明一下, root日志的创建不是通过loggerContext.getLogger
创建的, 在logback框架被spi引入时会创建日志上下文LoggerContext, 在LoggerContext的构造器中创建了root的Logger对象
root日志的默认级别是debug, 并且不支持设置为NULL和INHERITED
root日志是所有其它包或者类日志的顶层, 当我们没有额外设置某个类或者包的logger对象时, 默认都使用的root日志
LoggerAction->LoggerModel->LoggerModelHandler
LoggerModelHandler
public void handle(ModelInterpretationContext mic, Model model) throws ModelHandlerException {
inError = false;
LoggerModel loggerModel = (LoggerModel) model;
// 解析名称
String finalLoggerName = mic.subst(loggerModel.getName());
LoggerContext loggerContext = (LoggerContext) this.context;
// 获取到对应的日志对象
logger = loggerContext.getLogger(finalLoggerName);
// 解析日志等级
String levelStr = mic.subst(loggerModel.getLevel());
// 不为空
if (!OptionHelper.isNullOrEmptyOrAllSpaces(levelStr)) {
// INHERITED或者null字符串
if (JoranConstants.INHERITED.equalsIgnoreCase(levelStr) || NULL.equalsIgnoreCase(levelStr)) {
// root等级不支持INHERITED和null字符串
if(Logger.ROOT_LOGGER_NAME.equalsIgnoreCase(finalLoggerName)) {
addError(ErrorCodes.ROOT_LEVEL_CANNOT_BE_SET_TO_NULL);
} else {
// 其它类的日志直接置为空
addInfo("Setting level of logger [" + finalLoggerName + "] to null, i.e. INHERITED");
logger.setLevel(null);
}
} else {
// 设置日志具体的等级
Level level = Level.toLevel(levelStr);
addInfo("Setting level of logger [" + finalLoggerName + "] to " + level);
logger.setLevel(level);
}
}
// additivity属性
String additivityStr = mic.subst(loggerModel.getAdditivity());
// 不为空
if (!OptionHelper.isNullOrEmptyOrAllSpaces(additivityStr)) {
// 转boolean, 默认是true
boolean additive = OptionHelper.toBoolean(additivityStr, true);
addInfo("Setting additivity of logger [" + finalLoggerName + "] to " + additive);
// 设置additive, 如果
logger.setAdditive(additive);
}
mic.pushObject(logger);
}
方法小结
<logger name="per.qiao.App" level="debug" additivity="ture">...logger>
这种不仅App这个类的日志级别是debug, per,per.qiao这两个级别的包的日志级别都是debug, 好奇的同学可能就问了, 那么我还有个per.qiao.App2
也设置了level=“info”, 那么per和per.qiao这两个包的日志级别是啥呢?? 它们还是debug, 只会设置一次
本节就介绍了几个核心日志节点的modelHandler, 下一节讲重点介绍下appender和logger(root)相关的标签
-Dlogback.debug
设置系统级参数来控制, 它的优先级比configuration标签的更高configuration/propertiesConfigurator
标签指定热更新的properties文件