应用中日志的优雅使用(整合log4j2与SLF4J)
一些基础原则(来自阿里java开发手册)
-
应用中不可直接使用日志系统( Log4j、 Logback) 中的 API,而应依赖使用日志框架( SLF4J、 JCL–Jakarta Commons Logging) 中的 API,使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一。
日志框架( SLF4J、 JCL–Jakarta Commons Logging)的使用方式(推荐使用 SLF4J)
import org.slf4j.Logger; import org.slf4j.LoggerFactory; private static final Logger logger = LoggerFactory.getLogger(Test.class);
-
所有日志文件至少保存 15 天,因为有些异常具备以“周” 为频次发生的特点。 对于当天日志,以“应用名.log” 来保存,保存在
/home/用户名/software/应用名/logs/
目录下,过往日志格式为: {logname}.log.{保存日期}.gz
,日期格式:yyyy-MM-dd
。 -
在日志输出时,字符串变量之间的拼接使用占位符的方式 。
LOGGER.info("SparkApp state: {}.", handle.getState().toString());
-
对于
trace/debug
级别的日志输出,必须进行日志级别的开关判断。if (LOGGER.isDebugEnabled()) { LOGGER.debug("hadoopConfDir=[{}].", hadoopConfDir); LOGGER.debug("yarnConfDir=[{}].", hadoopConfDir); LOGGER.debug("javaHome=[{}].", javaHome); LOGGER.debug("sparkHome=[{}].", sparkHome); LOGGER.debug("appReSourcePath=[{}].", appReSourcePath); LOGGER.debug("appMainClass=[{}].", appMainClass); LOGGER.debug("hadoopUserName=[{}].", hadoopUserName); LOGGER.debug("retryMaxCount[{}].", retryMaxCount); }
-
禁止直接使用
System.out
或System.err
输出日志或使用e.printStackTrace()
打印异常堆栈。标准日志输出与标准错误输出文件每次 Jboss 重启时才滚动,如果大量输出送往这两个文件,容易造成文件大小超过操作系统大小限制。
-
异常信息应该包括两类信息:案发现场信息和异常堆栈信息。如果不处理,那么通过关键字 throws 往上抛出。
logger.error("inputParams:{} and errorMessage:{}", 各类参数或者对象 toString(), e.getMessage(), e);
-
除非是海外项目或者国外服务器,需要全部英文。国内使用中文进行输出和注释,避免词不达意或解释歧义的情况。
使用实例 整合log4j2与SLF4J
结合使用的原理:通过SLF4J的api调用log4j2的api,再由log4j2的api调用log4j2的core。
gradle引入依赖
// >>>>>>日志依赖>>>>>>
// log4j2的日志实现核心包,其依赖了log4j-api(log4j2的日志门面)
implementation group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.20.0'
// log4j2和slf4j的连接包,版本与log4j2核心包一致
implementation group: 'org.apache.logging.log4j', name: 'log4j-slf4j-impl', version: '2.20.0'
// slf4j的日志门面
implementation group: 'org.slf4j', name: 'slf4j-api', version: '1.7.25'
创建log4j2.xml
配置文件。
log4j2加载配置文件机制:
- Log4j将检查
"log4j2.configurationFile"
系统属性,如果设置了,将尝试使用与文件扩展名匹配的ConfigurationFactory
来加载配置。请注意,这并不局限于本地文件系统中的某个位置,可能包含一个URL。 - 如果没有设置系统属性,属性ConfigurationFactory将在classpath中寻找
log4j2-test.properties
。 - 如果没有找到这样的文件,YAML ConfigurationFactory将在classpath中寻找
log4j2-test.yaml
或log4j2-test.yml
。 - 如果没有找到这样的文件,JSON ConfigurationFactory将在classpath中寻找
log4j2-test.json
或log4j2-test.jsn
。 - 如果没有找到这样的文件,XML ConfigurationFactory将在classpath中寻找
log4j2-test.xml
。 - 如果不能找到测试文件,属性ConfigurationFactory将在classpath中寻找
log4j2.properties
。 - 如果不能找到属性文件,YAML ConfigurationFactory将在classpath上寻找
log4j2.yaml
或log4j2.yml
。 - 如果YAML文件无法定位,JSON ConfigurationFactory将在classpath上寻找
log4j2.json
或log4j2.jsn
。 - 如果不能找到JSON文件,XML ConfigurationFactory将尝试在classpath上找到
log4j2.xml
。 - 如果不能找到配置文件,将使用
DefaultConfiguration
。这将导致日志输出到控制台。
示例:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="INFO">
<!-- 变量配置 -->
<Properties>
<!-- 日志目录,prd环境取值改为/opt/logs/monitor -->
<Property name="log_path">/home/china-unicorn/logs/monitor</Property>
<Property name="logfile">china-unicorn</Property>
<Property name="layout_pattern">[%d][%p][%t][%c:%L] %m%n</Property>
<Property name="file_pattern">${log_path}/${logfile}.log-%d{yyyy-MM-dd}-%i.gz</Property>
</Properties>
<!-- appender配置 -->
<Appenders>
<!-- 控制台 -->
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="${layout_pattern}"/>
</Console>
<!-- 每天滚动文件 -->
<RollingFile name="DailyRollingFile" fileName="${log_path}/${logfile}.log"
filePattern="${file_pattern}">
<PatternLayout pattern="${layout_pattern}"/>
<policies>
<TimeBasedTriggeringPolicy interval="1"/>
<SizeBasedTriggeringPolicy size="20 MB"/>
</policies>
<!--i的滚动大小限制是100-->
<DefaultRolloverStrategy max="100">
<Delete basePath="${log_path}">
<IfFileName glob="${logfile}-*.log.gz">
<!--目标路径下满足文件名命名要求的文件,保留天数或者路径下文件总大小-->
<IfAny>
<IfLastModified age="7d"/>
<IfAccumulatedFileSize exceeds="500 MB"/>
</IfAny>
</IfFileName>
</Delete>
</DefaultRolloverStrategy>
</RollingFile>
</Appenders>
<Loggers>
<Root level="DEBUG">
<AppenderRef ref="Console"/>
<AppenderRef ref="DailyRollingFile"/>
</Root>
</Loggers>
</Configuration>
创建TestLog.java
package com.donny.bigdata.test;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.LoggerContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
/**
* @author 1792998761@qq.com
* @date 2023/4/3 19:47
*/
public class TestLog {
private static final Logger LOGGER = LoggerFactory.getLogger(TestLog.class);
static {
// 初始化log4j2日志-- 采用将自定义配置文件路径的方式
LoggerContext context = (org.apache.logging.log4j.core.LoggerContext) LogManager.getContext(false);
File file = new File("conf/log4j2.xml");
context.setConfigLocation(file.toURI());
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("log4j2初始化完毕");
}
}
public static void main(String[] args) {
LOGGER.info("中文");
}
}
结果
[2023-04-04 14:58:06,719][DEBUG][main][com.donny.bigdata.test.TestLog:23] log4j2初始化完毕
[2023-04-04 14:58:06,719][INFO][main][com.donny.bigdata.test.TestLog:28] 中文
Gradle下日志包的冲突
利用dependencies,打印出依赖树。
命令式gradle :${modulename}:dependencies
,${modulename}是个占位符,里面用模块名(project)替换。或者使用IDEA开发也可以通过执行项目Gradle面板中Tasks->help->dependencies
任务,也打印出该模块对应的依赖树。
依赖树中包含以下部分
- annotationProcessor
- compileClasspath
- compileOnly
- default
- implementation
- mainSourceElements
- runtimeClasspath
- runtimeElements
- runtimeOnly
- testAnnotationProcessor
- testCompileClasspath
- testCompileOnly
- testImplementation
- testResultsElementsForTest
- testRuntimeClasspath
- testRuntimeOnly
部分举例展示:
compileClasspath - Compile classpath for source set 'main'.
+--- org.apache.logging.log4j:log4j-core:2.20.0
| \--- org.apache.logging.log4j:log4j-api:2.20.0
+--- org.slf4j:slf4j-api:1.7.25
+--- org.apache.logging.log4j:log4j-slf4j-impl:2.20.0
| +--- org.apache.logging.log4j:log4j-api:2.20.0
| \--- org.slf4j:slf4j-api:1.7.25
+--- com.sun.mail:jakarta.mail:2.0.1
| \--- com.sun.activation:jakarta.activation:2.0.1
...
...
...
implementation - Implementation only dependencies for source set 'main'. (n)
+--- org.apache.logging.log4j:log4j-core:2.20.0 (n)
+--- org.slf4j:slf4j-api:1.7.25 (n)
+--- org.apache.logging.log4j:log4j-slf4j-impl:2.20.0 (n)
+--- com.sun.mail:jakarta.mail:2.0.1 (n)
+--- jakarta.mail:jakarta.mail-api:2.0.1 (n)
+--- io.netty:netty-all:4.1.90.Final (n)
+--- org.elasticsearch.client:elasticsearch-rest-high-level-client:6.7.2 (n)
+--- org.elasticsearch.client:transport:6.7.2 (n)
+--- org.elasticsearch.client:elasticsearch-rest-client:6.7.2 (n)
+--- org.elasticsearch.client:elasticsearch-rest-client-sniffer:6.7.2 (n)
+--- org.apache.hadoop:hadoop-client:2.7.3 (n)
+--- org.apache.spark:spark-core_2.11:2.3.3 (n)
+--- org.apache.kafka:kafka-clients:1.0.0 (n)
+--- org.apache.hbase:hbase-client:1.1.2 (n)
+--- org.apache.hive:hive-jdbc:1.2.1 (n)
+--- com.clickhouse:clickhouse-jdbc:0.4.0 (n)
\--- org.apache.zookeeper:zookeeper:3.4.6 (n)
...
...
...
runtimeClasspath - Runtime classpath of source set 'main'.
+--- org.apache.logging.log4j:log4j-core:2.20.0
| \--- org.apache.logging.log4j:log4j-api:2.20.0
+--- org.slf4j:slf4j-api:1.7.25
+--- org.apache.logging.log4j:log4j-slf4j-impl:2.20.0
| +--- org.apache.logging.log4j:log4j-api:2.20.0
| +--- org.slf4j:slf4j-api:1.7.25
| \--- org.apache.logging.log4j:log4j-core:2.20.0 (*)
+--- com.sun.mail:jakarta.mail:2.0.1
| \--- com.sun.activation:jakarta.activation:2.0.1
+--- jakarta.mail:jakarta.mail-api:2.0.1
...
...
...
在其中寻找冲突的包。
处理方式
-
优先排除冲突依赖文章来源:https://www.toymoban.com/news/detail-407908.html
implementation("org.apache.hadoop:hadoop-client:2.9.1") { exclude group: 'org.slf4j', module: 'slf4j-log4j12' }
-
强制使用某个依赖文章来源地址https://www.toymoban.com/news/detail-407908.html
configurations.all { resolutionStrategy { force 'org.slf4j:slf4j-api:1.7.25' } }
到了这里,关于应用中日志的优雅使用(整合log4j2与SLF4J)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!