Appender/Logger/Root 关系怎么样 🏛️
希望有一个人,知道我不开心的时候来陪着我,哪怕不说话
log4j2 配置中, 我始终不明白 Appender 跟 Logger 的关系是什么, 他们究竟有什么用
在Log4j2中,Appender和Logger是两个核心组件,它们在日志记录过程中扮演着不同的角色。
- Appender(附加器):
Appender负责将日志输出到特定的目标。
例如,将日志记录输出到控制台、文件、数据库等。在Log4j2中,有许多预定义的Appender,如ConsoleAppender(控制台输出)、FileAppender(文件输出)、RollingFileAppender(滚动文件输出)、SocketAppender(网络输出)等。你可以根据需求选择相应的Appender,或者自定义实现Appender。
Appender的配置通常包括输出格式(如PatternLayout)、滚动策略、触发策略等。例如,在Log4j2的XML配置文件中,一个简单的FileAppender配置如下:
<Appenders>
<File name="File" fileName="logs/app.log">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</File>
</Appenders>
- Logger(记录器):
Logger负责捕捉日志事件,并将它们传递给适当的Appender。
每个Logger都有一个日志级别(如DEBUG、INFO、WARN、ERROR和CRITICAL),只有当日志事件的级别大于或等于Logger的级别时,才会将事件传递给Appender。这样,我们可以控制不同级别的日志事件是否被记录。
Logger通常有一个名字,表示它与特定的类或包相关联。在Java代码中,我们通常根据当前类创建一个Logger实例,如:
private static final Logger logger = LogManager.getLogger(MyClass.class);
在Log4j2的配置文件中,我们可以为不同的Logger设置日志级别,以及关联的Appender。例如:
<Loggers>
<Logger name="com.example" level="info">
<AppenderRef ref="File"/>
</Logger>
<Root level="error">
<AppenderRef ref="File"/>
</Root>
</Loggers>
在这个例子中,我们为名为com.example
的Logger设置了INFO级别,并关联了名为File
的Appender。同时,我们还设置了Root Logger(所有Logger的父类)的级别为ERROR,以确保只有错误级别的日志会被记录。
总结一下,Logger负责捕捉和筛选日志事件,而Appender负责将日志事件输出到特定目标。在Log4j2的配置中,我们需要定义Appender,然后将Appender关联到相应的Logger,从而实现灵活的日志记录策略。
普通logger 与 root的关系
在Log4j2中,Logger之间存在一种层次结构。普通Logger(也称为非Root Logger或自定义Logger)都是Root Logger的子Logger。Root Logger是Logger层次结构的顶层记录器,它是所有Logger的父类。
普通Logger根据名称和包路径进行组织。例如,一个名为com.example.MyClass
的普通Logger与名为com.example
的包相关联。这种层次结构允许我们为不同的包或类设置不同的日志级别和Appender。
Root Logger的主要作用是提供默认的日志级别和Appender设置。当一个普通Logger没有显式地设置级别或Appender时,它会继承Root Logger的设置。
这是一个简化的Logger层次结构示意图:
Root Logger
├── com.example (package)
│ ├── com.example.MyClass1 (class)
│ ├── com.example.MyClass2 (class)
│ └── ...
├── com.example.subpackage (package)
│ ├── com.example.subpackage.MyClass3 (class)
│ └── ...
└── ...
以下是一个简单的Log4j2配置示例,展示了普通Logger和Root Logger的关系:
<Configuration>
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Logger name="com.example" level="info">
<AppenderRef ref="Console"/>
</Logger>
<Root level="warn">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
在这个示例中,我们为名为com.example
的普通Logger设置了INFO级别,并关联了名为Console
的Appender。同时,我们设置了Root Logger的级别为WARN。这意味着:
- 对于
com.example
包及其子包的类,只有INFO级别及以上的日志事件会被记录。 - 对于其他没有显式设置Logger的包和类,只有WARN级别及以上的日志事件会被记录,因为它们会继承Root Logger的设置。
通过这种层次结构,我们可以灵活地为不同的包和类设置日志策略,以满足不同场景的需求。
<Logger name="com.example" level="info">
<AppenderRef ref="Console"/>
</Logger>
<Root level="warn">
<AppenderRef ref="Console"/>
</Root>
如果我现在把 com.example 这个logger改成 error级别, 那一个名为com.example.MyClass的普通Logger现在用error记录一行日志, 内容为hello, 那么这样会记录两次吧, 因为命中了这个 logger, 然后又交给了父logger root记录器, 我记得有一个参数可以禁止root输出,叫做 addition = false 是这样的吗?
no。在Log4j2中,如果一个普通Logger有一个显式设置的日志级别,它将不会继承Root Logger的设置。因此,当你将com.example
这个Logger的级别改为ERROR时,它将不会同时触发Root Logger的输出。
在这个情况下,com.example.MyClass
这个Logger会使用它的最近祖先com.example
的设置,即ERROR级别。因此,当你使用ERROR级别记录一条消息时,它将只会被记录一次,而不会额外触发Root Logger的输出。
关于你提到的additivity
属性(注意是additivity而不是addition),它的确可以控制是否向父Logger传递日志事件。默认情况下,additivity设置为true
,表示日志事件会被传递给祖先Logger。当将additivity设置为false
时,日志事件将不会被传递给祖先Logger,仅在当前Logger中处理。
以下是一个包含additivity属性的配置示例:
<Configuration>
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Logger name="com.example" level="error" additivity="false">
<AppenderRef ref="Console"/>
</Logger>
<Root level="warn">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
在这个配置中,我们将com.example
的additivity属性设置为false
。这意味着,当com.example
或它的子Logger(如com.example.MyClass
)产生的日志事件被处理时,它们将不会被传递给Root Logger。
我们平常在程序中使用的log.info, 我们使用的这个log, log4j2 怎么知道用哪个Logger进行记录呢? log4j2是怎么进行匹配的呢? 根据我们提供的logger的name还是根据类路径?
在程序中,当我们使用log.info()
或其他日志方法时,Log4j2会根据当前类路径来查找匹配的Logger。具体来说,Log4j2首先会检查当前类路径是否与配置文件中定义的某个Logger的名称完全匹配。如果没有找到完全匹配的Logger,Log4j2会沿着类路径逐级向上查找,直到找到一个匹配的Logger。如果没有找到任何匹配的Logger,Log4j2会使用Root Logger的设置。
举个例子,假设我们有以下配置:
<Loggers>
<Logger name="com.example" level="info">
<AppenderRef ref="Console"/>
</Logger>
<Logger name="com.example.subpackage" level="debug">
<AppenderRef ref="Console"/>
</Logger>
<Root level="warn">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
现在,我们在名为com.example.subpackage.MyClass
的类中使用log.info()
:
private static final Logger log = LogManager.getLogger(com.example.subpackage.MyClass.class);
...
log.info("This is an info message");
在这种情况下,Log4j2会按照以下顺序查找匹配的Logger:
- 首先,查找名称为
com.example.subpackage.MyClass
的Logger。在这个例子中,没有找到匹配的Logger。 - 然后,查找名称为
com.example.subpackage
的Logger。在这个例子中,找到了一个匹配的Logger,级别为DEBUG。 - 由于已经找到了匹配的Logger,Log4j2会使用该Logger的设置进行日志记录。
注意:如果在步骤2中没有找到匹配的Logger,Log4j2会继续沿着类路径向上查找,直到找到一个匹配的Logger,或者使用Root Logger。
所以,总结一下,Log4j2是通过类路径(包路径)来查找和匹配Logger的。这样可以让我们针对不同的包和类设置不同的日志策略。