首页 > 基础资料 博客日记
【Logback】<logger>、<root>标签详解
2023-12-14 21:28:22基础资料围观228次
背景
排查一个项目的问题,发现打印了一堆重复日志…,为避免日志爆盘被投诉,写一下logback的使用和解析。 因为重复日志问题引发的,所以本文优先<logger>、<root>标签,其余的后续在写…
版本:logback-1.2.3
一、<logger>使用
如果需要定制指定模块或者类的日志信息,可以通过<logger>
标签来实现,通过其name
属性指定包或者类
全路径即可
<logger>是有父子
关系的
<root>
是一个特殊的<logger> ,其name="ROOT"
,是所有<logger>的祖先节点,先了解,下面会讲。
父子关系是根据name
属性的层级来确定的
如<logger name="com.qbhj.logback.controller">
,其父子关系如下(先了解结论,下面分析中会讲):
<logger name="ROOT">
└─<logger name="com">
│ └─<logger name="com.qbhj">
│ │ └─<logger name="com.qbhj.logback">
│ │ │ └─<logger name="com.qbhj.logback.controller">
1.1、使用示例
<logger name="com.qbhj" level="INFO" additivity="true">
<!-- 可配置 N 个appender-ref -->
<appender-ref ref="CONSOLE"/>
<appender-ref ref="appender1"/>
<appender-ref ref="appender1"/>
</logger>
<logger name="com.qbhj.logback" additivity="true">
<appender-ref ref="CONSOLE"/>
</logger>
<logger name="com.qbhj.logback.empty" additivity="true">
</logger>
Tips:
1、
<logger>
可以配置0-N
个
2、<logger>
由属性
和子元素
<appender-ref>2个部分构成,日志输出是<appender-ref>
所引用的<appender>
来执行的
3、<logger>
本身是一个链表
,有一个父节点和N个子节点,具体在1.3、解析
中详细说明
1.1、属性配置说明 & 演示
属性名 | 属性值 | 是否必填 | 说明 |
---|---|---|---|
name | 包或者类路径 | 是 | 如: com.qbhj 或 com.qbhj.Test.class |
level | OFF、ERROR、WARN、INFO、DEBUG、TRACE、ALL | 否 | 日志级别 ,默认继承父<logger>的日志级别 |
additivity | true | false | 否 | 是否叠加,默认true ,建议配置为false,见日志规范 |
1.1.1、name
指定包或者类
全路径,则该<logger>的配置就对其生效,默认使用<root>的日志配置
1.1.2、level
日志级别
Tips:
<logger>
日志级别level
属性是有继承关系,其优先级如下(此表摘自logback官网):
Logger name | Assigned Level | Effective Level |
---|---|---|
root | DEBUG | DEBUG |
chapters.configuration | INFO | INFO |
chapters.configuration.MyApp3 | null | INFO |
chapters.configuration.Foo | DEBUG | DEBUG |
总结:
若自身没设置 level 则使用其父Logger的level
若父级都没设置,则使用<root>
节点的,<root>
不配置则其默认级别level为DEBUG
1.1.3、additivity
是否允许父级<logger>打印自身name指定范围内的日志。若允许,则会重复打印日志。
1.1.3.1、效果演示:additivity=true
1)、打印日志代码
package com.qbhj.logback.controller;
// import .....
@Slf4j
@RestController
public class LogController {
@GetMapping("/additivity")
public void additivity(@RequestParam(defaultValue = "1") Integer num) {
System.out.println("###################################### num: " + num);
for (int i = 0; i < num; i++) {
log.info("/additivity......");
}
}
}
2.1)、日志配置,只配置root
,无<logger>
<root level="INFO">
<!-- 控制台 -->
<appender-ref ref="CONSOLE"/>
</root>
2.2)、结果,日志只打印了一遍
###################################### num: 1
20:36:06.170 INFO --- [ http-nio-8080-exec-1] c.qbhj.logback.controller.LogController : /additivity......
2.3)、说明
所有Logger都没有配置
appender
,所以无日志输出
root配置了appender
=CONSOLE
,所以控制台会打印日志
Logger | appender | additivity(默认true ) | 打印日志 | 是否被调用 |
---|---|---|---|---|
<logger name="ROOT"> | CONSOLE | true | 是 | 是 |
└─<logger name="com"> | 无 | true | 否 | 是 |
│ └─<logger name="com.qbhj"> | 无 | true | 否 | 是 |
│ │ └─<logger name="com.qbhj.logback"> | 无 | true | 否 | 是 |
│ │ │ └─<logger name="com.qbhj.logback.controller"> | 无 | true | 否 | 是 |
3.1)、增加<logger>
配置name=com.qbhj.logback.controller
,<root>
、<logger>
中都有配置控制台(CONSOLE)输出,共配置了2遍
<logger name="com.qbhj.logback.controller" level="INFO" additivity="true">
<!-- 控制台 -->
<appender-ref ref="CONSOLE"/>
</logger>
<root level="INFO">
<!-- 控制台 -->
<appender-ref ref="CONSOLE"/>
</root>
3.2)、结果,日志打了2遍,是<logger>
和<root>
各自打一遍
###################################### num: 1
20:54:51.165 INFO --- [ http-nio-8080-exec-2] c.qbhj.logback.controller.LogController : /additivity......
20:54:51.165 INFO --- [ http-nio-8080-exec-2] c.qbhj.logback.controller.LogController : /additivity......
3.3)、说明
<logger name="com.qbhj.logback.controller">
配置了appender
=CONSOLE
,控制台打印日志
root配置了appender
=CONSOLE
,所以控制台会打印日志
Logger | appender | additivity(默认true ) | 打印日志 | 是否被调用 |
---|---|---|---|---|
<logger name="ROOT"> | CONSOLE | true | 是 | 是 |
└─<logger name="com"> | 无 | true | 否 | 是 |
│ └─<logger name="com.qbhj"> | 无 | true | 否 | 是 |
│ │ └─<logger name="com.qbhj.logback"> | 无 | true | 否 | 是 |
│ │ │ └─<logger name="com.qbhj.logback.controller"> | CONSOLE | true | 是 | 是 |
4.1)、再增加<logger>
配置name=com.qbhj.logback
logback包肯定包含 LogController类的,即<root>
、<logger>
中都有配置控制台(CONSOLE)输出,共配置了3遍
<logger name="com.qbhj.logback.controller" level="INFO">
<!-- 控制台 -->
<appender-ref ref="CONSOLE"/>
</logger>
<logger name="com.qbhj.logback" level="INFO">
<!-- 控制台 -->
<appender-ref ref="CONSOLE"/>
</logger>
<root level="INFO">
<!-- 控制台 -->
<appender-ref ref="CONSOLE"/>
</root>
4.2)、结果,日志打了3遍,2个<logger>
和<root>
各自打一遍
###################################### num: 1
20:58:23.914 INFO --- [ http-nio-8080-exec-1] c.qbhj.logback.controller.LogController : /additivity......
20:58:23.914 INFO --- [ http-nio-8080-exec-1] c.qbhj.logback.controller.LogController : /additivity......
20:58:23.914 INFO --- [ http-nio-8080-exec-1] c.qbhj.logback.controller.LogController : /additivity......
4.3)、说明
2个
<logger >
配置了appender
=CONSOLE
,控制台打印日志2遍
root配置了appender
=CONSOLE
,控制台会打印日志
Logger | appender | additivity(默认true ) | 打印日志 | 是否被调用 |
---|---|---|---|---|
<logger name="ROOT"> | CONSOLE | true | 是 | 是 |
└─<logger name="com"> | 无 | true | 否 | 是 |
│ └─<logger name="com.qbhj"> | 无 | true | 否 | 是 |
│ │ └─<logger name="com.qbhj.logback"> | CONSOLE | true | 是 | 是 |
│ │ │ └─<logger name="com.qbhj.logback.controller"> | CONSOLE | true | 是 | 是 |
1.1.3.1、效果演示:additivity=“false”
1.1)、同样的配置,<logger>
全部改为additivity=“false”
<logger name="com.qbhj.logback.controller" level="INFO" additivity="false">
<!-- 控制台 -->
<appender-ref ref="CONSOLE"/>
</logger>
<logger name="com.qbhj.logback" level="INFO" additivity="false">
<!-- 控制台 -->
<appender-ref ref="CONSOLE"/>
</logger>
<root level="INFO">
<!-- 控制台 -->
<appender-ref ref="CONSOLE"/>
</root>
1.2)、结果:日志只打印了一遍
###################################### num: 1
21:00:24.598 INFO --- [ http-nio-8080-exec-1] c.qbhj.logback.controller.LogController : /additivity......
1.3)、说明
<logger name="com.qbhj.logback.controller" level="INFO" additivity="false">
,配置了appender
=CONSOLE
所以打印日志。其additivity=“false”
,不调用父级,结束
Logger | appender | additivity(默认true ) | 打印日志 | 是否被调用 |
---|---|---|---|---|
<logger name="ROOT"> | CONSOLE | true | 否 | 否 |
└─<logger name="com"> | 无 | true | 否 | 否 |
│ └─<logger name="com.qbhj"> | 无 | true | 否 | 否 |
│ │ └─<logger name="com.qbhj.logback"> | CONSOLE | false | 否 | 否 |
│ │ │ └─<logger name="com.qbhj.logback.controller"> | CONSOLE | false | 是 | 是 |
1.2 appender-ref
示例
<!-- 建议配置为false,避免日志重复输出 -->
<logger name="com.qbhj" level="INFO" additivity="false">
<!-- 可配置 N 个appender-ref -->
<appender-ref ref="CONSOLE"/>
<appender-ref ref="appender1"/>
<appender-ref ref="appender1"/>
</logger>
<logger name="com.qbhj.logback" additivity="false">
<appender-ref ref="CONSOLE"/>
</logger>
<logger name="com.qbhj.logback.empty" additivity="false">
</logger>
1、每个logger可以配置任意(含
0
)个<appender-ref>
2、语法:ref=appender[name]
,声明<appender>
的name
属性值
二、<root>使用
2.1、属性
属性名 | 属性值 | 是否必填 | 说明 |
---|---|---|---|
level | OFF、ERROR、WARN、INFO、DEBUG、TRACE、ALL | 否 | 日志级别,默认DEBUG |
<root>
的本质是一个名为ROOT的特殊logger,即<logger name="ROOT">
root是所有logger的根节点
<root level="DEBUG">
<!-- 控制台 -->
<appender-ref ref="CONSOLE"/>
<!-- 文件 -->
<appender-ref ref="FILE_ALL"/>
<appender-ref ref="FILE_WARN"/>
<appender-ref ref="FILE_ERROR"/>
</root>
三、解析
3.1、<logger>链表
1、<logger>是一个链表,有父节点和子节点
2、除<root>父节点为null外,所有logger一定有父节点,<root>为所有logger的根节点
ch.qos.logback.classic.Logger
package ch.qos.logback.classic;
//..........
public final class Logger implements org.slf4j.Logger, LocationAwareLogger, AppenderAttachable<ILoggingEvent>, Serializable {
// ..........
/**
* The name of this logger
*/
private String name;
// The assigned levelInt of this logger. Can be null.
transient private Level level;
// The effective levelInt is the assigned levelInt and if null, a levelInt is
// inherited form a parent.
transient private int effectiveLevelInt;
// root 为所有logger的祖先(根)节点
/**
* The parent of this category. All categories have at least one ancestor
* which is the root category.
*/
transient private Logger parent;
/**
* The children of this logger. A logger may have zero or more children.
*/
transient private List<Logger> childrenList;
// additive 默认 true
transient private boolean additive = true;
final transient LoggerContext loggerContext;
// ..........
private boolean isRootLogger() {
// only the root logger has a null parent
// 只有root的parent为null
return parent == null;
}
// ..........
}
3.2、root是一个名为 ROOT 的特殊logger,其 parent 为 null
LoggerContext 初始化的时候就在其构造函数中创建了root了
root默认日志级别为DEBUG
ch.qos.logback.classic.LoggerContext
public class LoggerContext extends ContextBase implements ILoggerFactory, LifeCycle {
/** Default setting of packaging data in stack traces */
public static final boolean DEFAULT_PACKAGING_DATA = false;
final Logger root;
private int size;
private int noAppenderWarning = 0;
final private List<LoggerContextListener> loggerContextListenerList = new ArrayList<LoggerContextListener>();
private Map<String, Logger> loggerCache;
// .............
public LoggerContext() {
super();
this.loggerCache = new ConcurrentHashMap<String, Logger>();
this.loggerContextRemoteView = new LoggerContextVO(this);
// 初始化 <root>
/**
package org.slf4j;
public interface Logger {
String ROOT_LOGGER_NAME = "ROOT";
*/
this.root = new Logger(Logger.ROOT_LOGGER_NAME, null, this);
// ROOT的默认日志级别 DEBUG
this.root.setLevel(Level.DEBUG);
loggerCache.put(Logger.ROOT_LOGGER_NAME, root);
initEvaluatorMap();
size = 1;
this.frameworkPackages = new ArrayList<String>();
}
// ..........
}
3.3、 name属性和<logger>继承关系
1、会根据name属性的值,逐级创建子logger,如
com.qbhj.Test.class
中 log.info(“print log…”),一共会创建3个logger1)、
com
2)、com.qbhj
3)、com.qbhj.Test
ch.qos.logback.classic.LoggerContext#getLogger(java.lang.Class<?>)
public final Logger getLogger(final Class<?> clazz) {
return getLogger(clazz.getName());
}
@Override
public final Logger getLogger(final String name) {
if (name == null) {
throw new IllegalArgumentException("name argument cannot be null");
}
// root直接返回
// if we are asking for the root logger, then let us return it without
// wasting time
if (Logger.ROOT_LOGGER_NAME.equalsIgnoreCase(name)) {
return root;
}
// 非root,先拿到root,然后逐级创建其子logger
int i = 0;
Logger logger = root;
// check if the desired logger exists, if it does, return it
// without further ado.
Logger childLogger = (Logger) loggerCache.get(name);
// if we have the child, then let us return it without wasting time
if (childLogger != null) {
return childLogger;
}
// if the desired logger does not exist, them create all the loggers
// in between as well (if they don't already exist)
String childName;
while (true) {
int h = LoggerNameUtil.getSeparatorIndexOf(name, i);
if (h == -1) {
childName = name;
} else {
childName = name.substring(0, h);
}
// move i left of the last point
i = h + 1;
synchronized (logger) { // 此logger为 root
childLogger = logger.getChildByName(childName);
if (childLogger == null) {
// 调用Logger创建子节点
childLogger = logger.createChildByName(childName);
loggerCache.put(childName, childLogger);
incSize();
}
}
logger = childLogger;
if (h == -1) {
return childLogger;
}
}
}
// ch.qos.logback.classic.Logger#createChildByName
Logger createChildByName(final String childName) {
int i_index = LoggerNameUtil.getSeparatorIndexOf(childName, this.name.length() + 1);
if (i_index != -1) {
throw new IllegalArgumentException("For logger [" + this.name + "] child name [" + childName
+ " passed as parameter, may not include '.' after index" + (this.name.length() + 1));
}
if (childrenList == null) {
childrenList = new CopyOnWriteArrayList<Logger>();
}
Logger childLogger;
childLogger = new Logger(childName, this, this.loggerContext);
childrenList.add(childLogger);
childLogger.effectiveLevelInt = this.effectiveLevelInt;
return childLogger;
}
3.3、level属性继承 和 优先级
1、若自身level为空,则
继承
父级Logger的level值
ch.qos.logback.classic.Logger#setLevel
public synchronized void setLevel(Level newLevel) {
if (level == newLevel) {
// nothing to do;
return;
}
// <root> 的 level 必填
if (newLevel == null && isRootLogger()) {
throw new IllegalArgumentException("The level of the root logger cannot be set to null");
}
level = newLevel;
// 若level为空,则使用其父logger的有效level
if (newLevel == null) {
effectiveLevelInt = parent.effectiveLevelInt;
newLevel = parent.getEffectiveLevel();
} else {
effectiveLevelInt = newLevel.levelInt;
}
if (childrenList != null) {
int len = childrenList.size();
for (int i = 0; i < len; i++) {
Logger child = (Logger) childrenList.get(i);
// tell child to handle parent levelInt change
child.handleParentLevelChange(effectiveLevelInt);
}
}
// inform listeners
loggerContext.fireOnLevelChange(this, newLevel);
}
3.4、additivity属性
3.4.1、源码分析
1、调用自身的appender进行日志输出
2、若additive=false
,则跳出循环,否则调用父logger的appender进行日志输出
ch.qos.logback.classic.Logger#callAppenders
// aai 就是<appender-ref>中指定的<appender> AppenderAttachableImpl
transient private AppenderAttachableImpl<ILoggingEvent> aai;
// additive 默认为 true
transient private boolean additive = true;
// ............
public void callAppenders(ILoggingEvent event) {
int writes = 0;
// 1、遍历当前logger的父logger
for (Logger l = this; l != null; l = l.parent) {
// 2、若当前logger配置了append,则遍历append输出日志信息
writes += l.appendLoopOnAppenders(event);
// 3、若当前logger的属性additive=false 则跳出循环,不再调用父级
if (!l.additive) {
break;
}
}
// No appenders in hierarchy
if (writes == 0) {
loggerContext.noAppenderDefinedWarning(this);
}
}
private int appendLoopOnAppenders(ILoggingEvent event) {
// 若当前logger配置了append,则调用其进行日志输出
if (aai != null) {
return aai.appendLoopOnAppenders(event);
} else {
return 0;
}
}
3.4.2、演示
还是上述的例子
1)、只把name=“com.qbhj.logback.controller” 的logger,additivity属性改为"true"
<logger name="com.qbhj.logback.controller" level="INFO" additivity="true">
<!-- 控制台 -->
<appender-ref ref="CONSOLE"/>
</logger>
<logger name="com.qbhj.logback" level="INFO" additivity="false">
<appender-ref ref="CONSOLE"/>
</logger>
<root level="INFO">
<!-- 控制台 -->
<appender-ref ref="CONSOLE"/>
</root>
1.1)、结果:打印两遍
###################################### num: 1
21:02:30.800 INFO --- [ http-nio-8080-exec-2] c.qbhj.logback.controller.LogController : /additivity......
21:02:30.800 INFO --- [ http-nio-8080-exec-2] c.qbhj.logback.controller.LogController : /additivity......
说明:
<logger name=“com.qbhj.logback.controller” level=“INFO” additivity=“true”> ,且配置了
appender
=CONSOLE
所以打印日志。其additivity=“true”
,调用父级
<logger name="com.qbhj.logback" level="INFO" additivity="false">
,且配置了appender=CONSOLE,所以打印日志。其additivity=“false”
,跳出循环,结束
Logger | appender | additivity(默认true ) | 打印日志 | 是否被调用 |
---|---|---|---|---|
<logger name="ROOT"> | CONSOLE | true | 否 | 否 |
└─<logger name="com"> | 无 | true | 否 | 否 |
│ └─<logger name="com.qbhj"> | 无 | true | 否 | 否 |
│ │ └─<logger name="com.qbhj.logback"> | CONSOLE | false | 是 | 是 |
│ │ │ └─<logger name="com.qbhj.logback.controller"> | CONSOLE | true | 是 | 是 |
2)、只把name=“com.qbhj.logback.controller” 的logger,additivity属性改为"false", name="com.qbhj.logback"的logger改为false
<logger name="com.qbhj.logback.controller" level="INFO" additivity="false">
<!-- 控制台 -->
<appender-ref ref="CONSOLE"/>
</logger>
<logger name="com.qbhj.logback" level="INFO" additivity="true">
<appender-ref ref="CONSOLE"/>
</logger>
<root level="INFO">
<!-- 控制台 -->
<appender-ref ref="CONSOLE"/>
</root>
2.1)、结果:打印1遍
###################################### num: 1
21:04:10.603 INFO --- [ http-nio-8080-exec-1] c.qbhj.logback.controller.LogController : /additivity......
说明:
<logger name=“com.qbhj.logback.controller” level=“INFO” additivity=“false”> ,且配置了
appender
=CONSOLE
所以打印日志。其additivity=“false”
,跳出循环,结束
Logger | appender | additivity(默认true ) | 打印日志 | 是否被调用 |
---|---|---|---|---|
<logger name="ROOT"> | CONSOLE | true | 是 | 否 |
└─<logger name="com"> | 无 | true | 否 | 否 |
│ └─<logger name="com.qbhj"> | 无 | true | 否 | 否 |
│ │ └─<logger name="com.qbhj.logback"> | CONSOLE | true | 否 | 否 |
│ │ │ └─<logger name="com.qbhj.logback.controller"> | CONSOLE | false | 是 | 是 |
四、日志规范
阿里开发手册(黄山版)中约定如下:
二、异常日志
(三) 日志规约
7.
【强制】
避免重复打印日志,浪费磁盘空间,务必在日志配置文件中设置 additivity=false
正例:<logger name=“com.taobao.dubbo.config” additivity=“false”>
五、参考资料
阿里开发手册(黄山版).pdf
https://logback.qos.ch/manual/configuration.html#rootElement
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:jacktools123@163.com进行投诉反馈,一经查实,立即删除!
标签: