文章目录 背景一、\使用1.1、使用示例1.1、属性配置说明 & 演示1.1.1、name1.1.2、level1.1.3、additivity1.1.3.1、效果演示:additivity=
排查一个项目的问题,发现打印了一堆重复日志…,为避免日志爆盘被投诉,写一下logback的使用和解析。 因为重复日志问题引发的,所以本文优先
版本:logback-1.2.3
如果需要定制指定模块或者类的日志信息,可以通过
标签来实现,通过其name
属性指定包或者类
全路径即可
关系的
是有父子
是一个特殊的
,其 name="ROOT"
,是所有的祖先节点,先了解,下面会讲。
父子关系是根据name
属性的层级来确定的
如
,其父子关系如下(先了解结论,下面分析中会讲):
└─
│ └─
│ │ └─
│ │ │ └─
<logger name="com.qbhj" level="INFO" additivity="true"> <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:
可以配置
0-N
个
2、由
属性
和子元素
2个部分构成,日志输出是 所引用的
来执行的
3、本身是一个
链表
,有一个父节点和N个子节点,具体在1.3、解析
中详细说明
属性名 | 属性值 | 是否必填 | 说明 |
---|---|---|---|
name | 包或者类路径 | 是 | 如: com.qbhj 或 com.qbhj.Test.class |
level | OFF、ERROR、WARN、INFO、DEBUG、TRACE、ALL | 否 | 日志级别 ,默认继承父 |
additivity | true | false | 否 | 是否叠加,默认true ,建议配置为false,见日志规范 |
指定包或者类
全路径,则该
日志级别
Tips:
日志级别
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
若父级都没设置,则使用节点的,
不配置则其
默认级别level为DEBUG
是否允许父级
1)、打印日志代码
package com.qbhj.logback.controller;// import .....@Slf4j@RestControllerpublic 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......"); } }}
1)、日志配置,只配置root
,无
<root level="INFO"> <appender-ref ref="CONSOLE"/> root>
2)、结果,日志只打印了一遍
###################################### num: 120:36:06.170 INFO --- [ Http-NIO-8080-exec-1] c.qbhj.logback.controller.LogController : /additivity......
3)、说明
所有Logger都没有配置
appender
,所以无日志输出
root配置了appender
=CONSOLE
,所以控制台会打印日志
Logger | appender | additivity(默认true ) | 打印日志 | 是否被调用 |
---|---|---|---|---|
| CONSOLE | true | 是 | 是 |
└─
| 无 | true | 否 | 是 |
│ └─
| 无 | true | 否 | 是 |
│ │ └─
| 无 | true | 否 | 是 |
│ │ │ └─
| 无 | true | 否 | 是 |
1)、增加
配置name=com.qbhj.logback.controller
,
、
中都有配置控制台(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>
2)、结果,日志打了2遍,是
和
各自打一遍
###################################### num: 120: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)、说明
配置了
appender
=CONSOLE
,控制台打印日志
root配置了appender
=CONSOLE
,所以控制台会打印日志
Logger | appender | additivity(默认true ) | 打印日志 | 是否被调用 |
---|---|---|---|---|
| CONSOLE | true | 是 | 是 |
└─
| 无 | true | 否 | 是 |
│ └─
| 无 | true | 否 | 是 |
│ │ └─
| 无 | true | 否 | 是 |
│ │ │ └─
| CONSOLE | true | 是 | 是 |
1)、再增加
配置name=com.qbhj.logback
logback包肯定包含 LogController类的,即
、
中都有配置控制台(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>
2)、结果,日志打了3遍,2个
和
各自打一遍
###################################### num: 120: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......
3)、说明
2个
配置了
appender
=CONSOLE
,控制台打印日志2遍
root配置了appender
=CONSOLE
,控制台会打印日志
Logger | appender | additivity(默认true ) | 打印日志 | 是否被调用 |
---|---|---|---|---|
| CONSOLE | true | 是 | 是 |
└─
| 无 | true | 否 | 是 |
│ └─
| 无 | true | 否 | 是 |
│ │ └─
| CONSOLE | true | 是 | 是 |
│ │ │ └─
| CONSOLE | true | 是 | 是 |
1)、同样的配置,
全部改为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>
2)、结果:日志只打印了一遍
###################################### num: 121:00:24.598 INFO --- [ http-nio-8080-exec-1] c.qbhj.logback.controller.LogController : /additivity......
3)、说明
,配置了
appender
=CONSOLE
所以打印日志。其additivity=“false”
,不调用父级,结束
Logger | appender | additivity(默认true ) | 打印日志 | 是否被调用 |
---|---|---|---|---|
| CONSOLE | true | 否 | 否 |
└─
| 无 | true | 否 | 否 |
│ └─
| 无 | true | 否 | 否 |
│ │ └─
| CONSOLE | false | 否 | 否 |
│ │ │ └─
| CONSOLE | false | 是 | 是 |
示例
<logger name="com.qbhj" level="INFO" additivity="false"> <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>
每个logger可以配置任意(含
0
)个
2、语法:ref=appender[name]
,声明的
name
属性值
属性名 | 属性值 | 是否必填 | 说明 |
---|---|---|---|
level | OFF、ERROR、WARN、INFO、DEBUG、TRACE、ALL | 否 | 日志级别,默认DEBUG |
的本质是一个名为ROOT的特殊logger,即
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>
是一个链表,有父节点和子节点
2、除父节点为null外,所有logger一定有父节点,
为所有logger的根节点
ch.qos.logback.classic.Logger
package ch.qos.logback.classic;//..........public final class Logger implements org.slf4j.Logger, LocationAwareLogger, AppenderAttachable<ILoggingEvent>, Serializable { // .......... 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的祖先(根)节点 transient private Logger parent; 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; } // .......... }
LoggerContext 初始化的时候就在其构造函数中创建了root了
root默认日志级别为DEBUG
ch.qos.logback.classic.LoggerContext
public class LoggerContext extends ContextBase implements ILoggerFactory, LifeCycle { 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);// 初始化 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>(); } // .......... }
会根据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; }
若自身level为空,则
继承
父级Logger的level值
ch.qos.logback.classic.Logger#setLevel
public synchronized void setLevel(Level newLevel) { if (level == newLevel) { // nothing to do; return; } // 的 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); }
调用自身的appender进行日志输出
2、若additive=false
,则跳出循环,否则调用父logger的appender进行日志输出
ch.qos.logback.classic.Logger#callAppenders
// aai 就是中指定的 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; } }
还是上述的例子
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)、结果:打印两遍
###################################### num: 121: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......
说明:
,且配置了 appender
=CONSOLE
所以打印日志。其additivity=“true”
,调用父级
,且配置了appender=CONSOLE,所以打印日志。其
additivity=“false”
,跳出循环,结束
Logger | appender | additivity(默认true ) | 打印日志 | 是否被调用 |
---|---|---|---|---|
| CONSOLE | true | 否 | 否 |
└─
| 无 | true | 否 | 否 |
│ └─
| 无 | true | 否 | 否 |
│ │ └─
| CONSOLE | false | 是 | 是 |
│ │ │ └─
| 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>
1)、结果:打印1遍
###################################### num: 121:04:10.603 INFO --- [ http-nio-8080-exec-1] c.qbhj.logback.controller.LogController : /additivity......
说明:
,且配置了 appender
=CONSOLE
所以打印日志。其additivity=“false”
,跳出循环,结束
Logger | appender | additivity(默认true ) | 打印日志 | 是否被调用 |
---|---|---|---|---|
| CONSOLE | true | 是 | 否 |
└─
| 无 | true | 否 | 否 |
│ └─
| 无 | true | 否 | 否 |
│ │ └─
| CONSOLE | true | 否 | 否 |
│ │ │ └─
| CONSOLE | false | 是 | 是 |
阿里开发手册(黄山版)中约定如下:
二、异常日志
(三) 日志规约
【强制】
避免重复打印日志,浪费磁盘空间,务必在日志配置文件中设置 additivity=false
正例:
阿里开发手册(黄山版).pdf
https://logback.qos.ch/manual/configuration.html
来源地址:https://blog.csdn.net/weixin_43582081/article/details/129780600
--结束END--
本文标题: 【Logback】<logger>、<root>标签详解
本文链接: https://lsjlt.com/news/417101.html(转载时请注明来源链接)
有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341
2024-04-01
2024-04-03
2024-04-03
2024-01-21
2024-01-21
2024-01-21
2024-01-21
2023-12-23
回答
回答
回答
回答
回答
回答
回答
回答
回答
回答
0