一、日志的概念
日志文件是用于记录系统操作事件的文件集合,可分为事件日志和消息日志。具有处理历史数据、诊断问题的追踪以及理解系统的活动等重要作用。
在计算机中,日志文件是记录在操作系统或其他软件运行中发生的事件或在通信软件的不同用户之间的消息的文件。记录是保持日志的行为。在最简单的情况下,消息被写入单个日志文件。
许多操作系统,软件框架和程序包括日志系统。广泛使用的日志记录标准是在因特网工程任务组(IETF)RFC5424中定义的syslog。 syslog标准使专用的标准化子系统能够生成,过滤,记录和分析日志消息。
1.1 调试日志
软件开发中,我们经常需要去调试程序,做一些信息,状态的输出便于我们查询程序的运行状况。为了让我们能够更加灵活和方便的控制这些调试的信息,所有我们需要专业的日志技术。java中寻找bug会需要重现。调试也就是debug 可以在程序运行中暂停程序运行,可以查看程序在运行中的情况。日志主要是为了更方便的去重现问题。
1.2 系统日志
系统日志是记录系统中硬件、软件和系统问题的信息,同时还可以监视系统中发生的事件。用户可以通过它来检查错误发生的原因,或者寻找受到攻击时攻击者留下的痕迹。系统日志包括系统日志、应用程序日志和安全日志。
系统日志的价值
系统日志策略可以在故障刚刚发生时就向你发送警告信息,系统日志帮助你在最短的时间内发现问题。
系统日志是一种非常关键的组件,因为系统日志可以让你充分了解自己的环境。这种系统日志信息对于决定故障的根本原因或者缩小系统攻击范围来说是非常关键的,因为系统日志可以让你了解故障或者袭击发生之前的所有事件。为虚拟化环境制定一套良好的系统日志策略也是至关重要的,因为系统日志需要和许多不同的外部组件进行关联。良好的系统日志可以防止你从错误的角度分析问题,避免浪费宝贵的排错时间。另外一种原因是借助于系统日志,管理员很有可能会发现一些之前从未意识到的问题,在几乎所有刚刚部署系统日志的环境当中。
二、JAVA日志框架
问题:
- 控制日志输出的内容和格式
- 控制日志输出的位置
- 日志优化:异步日志,日志文件的归档和压缩
- 日志系统的维护
- 面向接口开发 – 日志的门面
2.1 为什么要用日志框架
因为软件系统发展到今天已经很复杂了,特别是服务器端软件,涉及到的知识,内容,问题太多。在某些方面使用别人成熟的框架,就相当于让别人帮你完成一些基础工作,你只需要集中精力完成系统的业务逻辑设计。而且框架一般是成熟,稳健的,他可以处理系统很多细节问题,比如,事务处理,安全性,数据流控制等问题。还有框架一般都经过很多人使用,所以结构很好,所以扩展性也很好,而且它是不断升级的,你可以直接享受别人升级代码带来的好处。
2.2 现有的日志框架
JUL(java util logging)、logback、log4j、log4j2
JCL(Jakarta Commons Logging)、slf4j( Simple Logging Facade for Java)
日志门面
JCL、slf4j
日志实现
JUL、logback、log4j、log4j2
三、JUL 学习
JUL全称Java util Logging是java原生的日志框架,使用时不需要另外引用第三方类库,相对其他日志框架使用方便,学习简单,能够在小型应用中灵活使用。
3.1 JUL入门
3.1.1 架构介绍
- Loggers:被称为记录器,应用程序通过获取Logger对象,调用其API来来发布日志信息。Logger通常时应用程序访问日志系统的入口程序。
- Appenders:也被称为Handlers,每个Logger都会关联一组Handlers,Logger会将日志交给关联
- Handlers处理,由Handlers负责将日志做记录。Handlers在此是一个抽象,其具体的实现决定了日志记录的位置可以是控制台、文件、网络上的其他日志服务或操作系统日志等。
- Layouts:也被称为Formatters,它负责对日志事件中的数据进行转换和格式化。Layouts决定了数据在一条日志记录中的最终形式。
- Level:每条日志消息都有一个关联的日志级别。该级别粗略指导了日志消息的重要性和紧迫,我可以将Level和Loggers,Appenders做关联以便于我们过滤消息。
- Filters:过滤器,根据需要定制哪些信息会被记录,哪些信息会被放过。
总结一下就是:
用户使用Logger来进行日志记录,Logger持有若干个Handler,日志的输出操作是由Handler完成的。
在Handler在输出日志前,会经过Filter的过滤,判断哪些日志级别过滤放行哪些拦截,Handler会将日志内容输出到指定位置(日志文件、控制台等)。Handler在输出日志时会使用Layout,将输出内容进行排版。
3.1.2 入门案例
package top.onefine;
import org.junit.Test;
import java.util.logging.Level;
import java.util.logging.Logger;
public class JULTest {
// Java获取当前类名的两种方法
// - 适用于非静态方法:this.getClass().getName()
// - 适用于静态方法:Thread.currentThread().getStackTrace()[1].getClassName()
@Test
public void testQuick() {
// 1. 获取日志记录器对象,参数需要一个唯一标识
Logger logger = Logger.getLogger(this.getClass().getName());
// 2. 日志记录输出
logger.info("hello jul"); // 日志级别:info
// 使用通用方法进行日志记录
logger.log(Level.INFO, "info msg");
// 通过占位符的方式输出变量的值
String name = "one fine";
int age = 18;
logger.log(Level.INFO, "用户信息:{0}, {1}", new Object[] {name, age});
}
}
3.2 日志的级别
jul中定义的日志级别
* java.util.logging.Level中定义了日志的级别:
SEVERE(最高值) # 严重
WARNING # 警告
INFO (默认级别) # 信息
CONFIG # 配置
FINE # 详细
FINER # 较详细
FINEST(最低值) # 非常详细
* 还有两个特殊的级别:
OFF,可用来关闭日志记录。
ALL,启用所有消息的日志记录。
虽然我们测试了7个日志级别但是默认只实现info以上的级别
package top.onefine;
import org.junit.Test;
import java.util.logging.Logger;
public class JULTest {
@Test
public void testLogLevel() {
// 1. 获取日志记录器对象,参数需要一个唯一标识
Logger logger = Logger.getLogger(this.getClass().getName());
// 2. 日志记录输出
logger.severe("severe");
logger.warning("warning");
logger.info("info");
// 默认情况下后面将不会输出到控制台(被过滤)
logger.config("config");
logger.fine("fine");
logger.finer("finer");
logger.finest("finest");
}
}
自定义日志级别配置
package top.onefine;
import org.junit.Test;
import java.io.IOException;
import java.util.logging.*;
public class JULTest {
// 自定义日志级别
@Test
public void testLogConfig() throws IOException {
// 1. 获取日志记录器对象,参数需要一个唯一标识
Logger logger = Logger.getLogger(this.getClass().getName());
// 关闭系统默认配置
logger.setUseParentHandlers(false);
/* 自定义配置日志级别
*/
// 创建ConsoleHandler 控制台输出
ConsoleHandler consoleHandler = new ConsoleHandler();
// 创建简单格式formatter转换对象
SimpleFormatter simpleFormatter = new SimpleFormatter();
// 进行关联
consoleHandler.setFormatter(simpleFormatter);
logger.addHandler(consoleHandler);
// 配置日志具体级别
logger.setLevel(Level.ALL);
consoleHandler.setLevel(Level.ALL);
/* 输出日志到文件
*/
// 创建FileHandler 文件输出
FileHandler fileHandler = new FileHandler("d:/logs/jul.log");// 参数为日志文件放置位置,注意处理异常
// 进行关联
fileHandler.setFormatter(simpleFormatter);
logger.addHandler(fileHandler);
// 配置日志具体级别
logger.setLevel(Level.ALL);
consoleHandler.setLevel(Level.ALL);
// 2. 日志记录输出
logger.severe("severe");
logger.warning("warning");
logger.info("info");
logger.config("config");
logger.fine("fine");
logger.finer("finer");
logger.finest("finest");
}
}
3.3 Logger之间的父子关系
JUL中Logger之间存在父子关系,这种父子关系通过树状结构存储,JUL在初始化时会创建一个顶层RootLogger作为所有Logger父Logger,存储上作为树状结构的根节点。并父子关系通过路径来关联。
package top.onefine;
import org.junit.Test;
import java.util.logging.*;
public class JULTest {
// Logger对象父子关系
@Test
public void testLogParent() {
Logger logger1 = Logger.getLogger("top.onefine");
Logger logger2 = Logger.getLogger("top");
System.out.println("logger2" + ((logger1.getParent() == logger2) ? "是" : "不是") + "logger1的父亲"); // true
System.out.println("logger2 parent: " + logger2.getParent() + ", " +
"name: " + logger2.getParent().getName());
/* 输出:
logger2是logger1的父亲
logger2 parent: java.util.logging.LogManager$RootLogger@79bc6541, name:
*/
// LogManager$RootLogger是所有日志记录器的顶级父元素,name为空串""
/* 配置logger2的日志级别
*/
// 关闭logger2默认配置
logger2.setUseParentHandlers(false);
// 创建ConsoleHandler 控制台输出
ConsoleHandler consoleHandler = new ConsoleHandler();
// 创建简单格式formatter转换对象
SimpleFormatter simpleFormatter = new SimpleFormatter();
// 进行关联
consoleHandler.setFormatter(simpleFormatter);
logger2.addHandler(consoleHandler);
// 配置日志具体级别
logger2.setLevel(Level.ALL);
consoleHandler.setLevel(Level.ALL);
logger1.severe("severe");
logger1.warning("warning");
logger1.info("info");
logger1.config("config");
logger1.fine("fine");
logger1.finer("finer");
logger1.finest("finest");
}
}
结果:
logger2是logger1的父亲
logger2 parent: java.util.logging.LogManager$RootLogger@79bc6541, name:
五月 16, 2020 11:17:37 下午 top.onefine.JULTest testLogParent
严重: severe
五月 16, 2020 11:17:37 下午 top.onefine.JULTest testLogParent
严重: severe
五月 16, 2020 11:17:37 下午 top.onefine.JULTest testLogParent
警告: warning
五月 16, 2020 11:17:37 下午 top.onefine.JULTest testLogParent
警告: warning
五月 16, 2020 11:17:37 下午 top.onefine.JULTest testLogParent
信息: info
五月 16, 2020 11:17:37 下午 top.onefine.JULTest testLogParent
信息: info
五月 16, 2020 11:17:37 下午 top.onefine.JULTest testLogParent
配置: config
五月 16, 2020 11:17:37 下午 top.onefine.JULTest testLogParent
详细: fine
五月 16, 2020 11:17:37 下午 top.onefine.JULTest testLogParent
较详细: finer
五月 16, 2020 11:17:37 下午 top.onefine.JULTest testLogParent
非常详细: finest
3.4 日志的配置文件
默认配置文件路径$JAVAHOME\jre\lib\logging.properties:
// C:\Program Files\Java\jre1.8.0_191\lib\logging.properties
############################################################
# Default Logging Configuration File
#
# You can use a different file by specifying a filename
# with the java.util.logging.config.file system property.
# For example java -Djava.util.logging.config.file=myfile
############################################################
############################################################
# Global properties
############################################################
# "handlers" specifies a comma separated list of log Handler
# classes. These handlers will be installed during VM startup.
# Note that these classes must be on the system classpath.
# By default we only configure a ConsoleHandler, which will only
# show messages at the INFO and above levels.
handlers= java.util.logging.ConsoleHandler
# To also add the FileHandler, use the following line instead.
#handlers= java.util.logging.FileHandler, java.util.logging.ConsoleHandler
# Default global logging level.
# This specifies which kinds of events are logged across
# all loggers. For any given facility this global level
# can be overriden by a facility specific level
# Note that the ConsoleHandler also has a separate level
# setting to limit messages printed to the console.
.level= INFO
############################################################
# Handler specific properties.
# Describes specific configuration info for Handlers.
############################################################
# default file output is in user's home directory.
java.util.logging.FileHandler.pattern = %h/java%u.log
java.util.logging.FileHandler.limit = 50000
java.util.logging.FileHandler.count = 1
java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter
# Limit the message that are printed on the console to INFO and above.
java.util.logging.ConsoleHandler.level = INFO
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
# Example to customize the SimpleFormatter output format
# to print one-line log message like this:
# <level>: <log message> [<date/time>]
#
# java.util.logging.SimpleFormatter.format=%4$s: %5$s [%1$tc]%n
############################################################
# Facility specific properties.
# Provides extra control for each logger.
############################################################
# For example, set the com.xyz.foo logger to only log SEVERE
# messages:
com.xyz.foo.level = SEVERE
栗子
配置文件resources/logging.properties:
# 指定RootLogger(顶级父类元素)的处理器(获取时设置)为:ConsoleHandler和FileHandler 即同时指定
#handlers= java.util.logging.ConsoleHandler, java.util.logging.FileHandler
# RootLogger顶级父类元素的日志等级:ALL
.level= ALL
## 文件处理器handle对象
# 输出日志级别
java.util.logging.FileHandler.pattern = d:/logs/java%u.log
# 输出日志文件限制大小(50000字节)
java.util.logging.FileHandler.limit = 50000
# 输出日志文件限制个数,5表示:0 - 4
java.util.logging.FileHandler.count = 1
# 以追加的方式添加日志内容,默认false
java.util.logging.FileHandler.append = true
# 输出日志格式,这里是XML
java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter
#java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter
## 控制台处理器handle对象
# 输出日志级别
java.util.logging.ConsoleHandler.level = ALL
# 指定输出日志消息格式对象
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
# 指定此控制台处理器handler对象的字符集
java.util.logging.ConsoleHandler.encoding = UTF-8
# 指定日志消息格式:五月 17, 2020 8:16:07 上午 top.onefine.JULTest testLogProperties 默认
#java.util.logging.ConsoleHandler.format = %4$s: %5$s [%1$tc]%n
## 自定义Logger, name = top.onefine
# 指定处理器
top.onefine.handlers = java.util.logging.ConsoleHandler
# 设置日志级别
top.onefine.level = CONFIG
# 关闭默认配置(忽略父日志设置)
top.onefine.useParentHanlders = false
java/top/onefine/JULTest.java:
package top.onefine;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.logging.*;
public class JULTest {
// 加载自定义配置文件
@Test
public void testLogProperties() throws IOException {
// 读取配置文件,通过类加载器
InputStream ins = JULTest.class.getClassLoader().getResourceAsStream("logging.properties");
// 创建LogManager
LogManager logManager = LogManager.getLogManager();
// 通过LogManager加载配置文件
logManager.readConfiguration(ins); // 注意处理异常
// 创建日志记录器
Logger logger = Logger.getLogger(this.getClass().getName());
logger.severe("severe");
logger.warning("warning");
logger.info("info");
logger.config("config");
logger.fine("fine");
logger.finer("finer");
logger.finest("finest");
}
}
3.5 日志原理解析
- 初始化LogManager
-LogManager加载logging.properties配置
-添加Logger到LogManager - 从单例LogManager获取Logger
- 设置级别Level,并指定日志记录LogRecord
- Filter提供了日志级别之外更细粒度的控制
- Handler是用来处理日志输出位置
- Formatter是用来格式化LogRecord的
四、LOG4J 学习
Log4j是Apache下的一款开源的日志框架,通过在项目中使用 Log4J,我们可以控制日志信息输出到控制台、文件、甚至是数据库中。我们可以控制每一条日志的输出格式,通过定义日志的输出级别,可以更灵活的控制日志的输出过程。方便项目的调试。
官方网站: http://logging.apache.org/log4j/1.2/
4.1 Log4j入门
- 建立maven工程
- 添加依赖
dependencies {
// // https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core
// compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.13.3'
// https://mvnrepository.com/artifact/log4j/log4j
compile group: 'log4j', name: 'log4j', version: '1.2.17'
// https://mvnrepository.com/artifact/junit/junit
testCompile group: 'junit', name: 'junit', version: '4.13'
}
- java代码
package top.onefine;
import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Logger;
import org.junit.Test;
public class Log4jTest {
@Test
public void testQuick() {
// 初始化配置信息,这里暂不使用配置文件
BasicConfigurator.configure();
// 1. 获取日志记录器对象
Logger logger = Logger.getLogger(Log4jTest.class);
// 2. 日志记录输出
logger.info("hello log4j!");
// 日志级别
logger.fatal("fatal"); // 严重错误,一般会造成系统崩溃和终止运行
// 最常用
logger.error("error"); // 错误信息,但不会影响系统运行
logger.warn("warn"); // 警告信息,可能会发生问题
logger.info("info"); // 程序运行信息,数据库的连接、网络、IO操作等
logger.debug("debug"); // 调试信息,一般在开发阶段使用,记录程序的变量、参数等
// log4j默认级别是debug
logger.trace("trace"); // 追踪信息,记录程序的所有流程信息
}
}
- 日志的级别
*每个Logger都被了一个日志级别(log level),用来控制日志信息的输出。日志级别从高到低分为:
fatal 指出每个严重的错误事件将会导致应用程序的退出。
error 指出虽然发生错误事件,但仍然不影响系统的继续运行。
warn 表明会出现潜在的错误情形。
info 一般和在粗粒度级别上,强调应用程序的运行全程。
debug 一般用于细粒度级别上,对调试应用程序非常有帮助。默认级别
trace 是程序追踪,可以用于输出程序运行中的变量,显示执行的流程。
* 还有两个特殊的级别:
OFF,可用来关闭日志记录。
ALL,启用所有消息的日志记录。
注:一般只使用4个级别,优先级从高到低为 ERROR > WARN > INFO > DEBUG
4.2 Log4j组件
Log4J 主要由 Loggers (日志记录器)、Appenders(输出端)和 Layout(日志格式化器)组成。其中:
- Loggers 控制日志的输出级别与日志是否输出;
- Appenders 指定日志的输出方式(输出到控制台、文件等);
- Layout 控制日志信息的输出格式。
resources/log4j.properties:
# 指定RootLogger顶级父元素默认配置信息
# 指定日志级别为trace,使用的appender为console
log4j.rootLogger = trace, console
# 指定控制台日志输出的appender
log4j.appender.console = org.apache.log4j.ConsoleAppender
# 指定消息格式layout
#log4j.appender.console.layout = org.apache.log4j.SimpleLayout
#log4j.appender.console.layout = org.apache.log4j.HTMLLayout
#log4j.appender.console.layout = org.apache.log4j.xml.XMLLayout
# 自定义消息输出格式及配置
log4j.appender.console.layout = org.apache.log4j.PatternLayout
#log4j.appender.console.layout.conversionPattern = %r [%t] %p %c %x - %m%n
#%d{yyyy年MM月dd日 HH:mm:ss.SSS} 表示 年月日时分秒毫秒
log4j.appender.console.layout.conversionPattern = %d{yyyy-MM-dd HH:mm:ss.SSS}%n
java/top/onefine/Log4jTest.java:
package top.onefine;
import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Logger;
import org.apache.log4j.helpers.LogLog;
import org.junit.Test;
public class Log4jTest {
@Test
public void testQuick() {
// // 开启log4j内置日志记录
// LogLog.setInternalDebugging(true);
// // 初始化配置信息,这里暂不使用配置文件
// BasicConfigurator.configure();
// 1. 获取日志记录器对象
Logger logger = Logger.getLogger(Log4jTest.class);
// 2. 日志记录输出
logger.info("hello log4j!");
// 日志级别
logger.fatal("fatal"); // 严重错误,一般会造成系统崩溃和终止运行
// 最常用
logger.error("error"); // 错误信息,但不会影响系统运行
logger.warn("warn"); // 警告信息,可能会发生问题
logger.info("info"); // 程序运行信息,数据库的连接、网络、IO操作等
logger.debug("debug"); // 调试信息,一般在开发阶段使用,记录程序的变量、参数等
// log4j默认级别是debug
logger.trace("trace"); // 追踪信息,记录程序的所有流程信息
}
}
4.2.1 Loggers
日志记录器,负责收集处理日志记录,实例的命名就是类“XX”的full quailied name(类的全限定名),Logger的名字大小写敏感,其命名有继承机制:例如:name为org.apache.commons
的logger会继承name为org.apache
的logger。
Log4J中有一个特殊的logger叫做“root”,他是所有logger的根,也就意味着其他所有的logger都会直接或者间接地继承自root。root logger可以用Logger.getRootLogger()
方法获取。
但是,自log4j 1.2版以来, Logger 类已经取代了 Category 类。对于熟悉早期版本的log4j的人来说,Logger 类可以被视为 Category 类的别名。
4.2.2 Appenders
Appender 用来指定日志输出到哪个地方,可以同时指定日志的输出目的地。Log4j 常用的输出目的地有以下几种:
输出端类型 | 作用 |
---|---|
ConsoleAppender | 将日志输出到控制台 |
FileAppender | 将日志输出到文件中 |
DailyRollingFileAppender | 将日志输出到一个日志文件,并且每天输出到一个新的文件 |
RollingFileAppender | 将日志信息输出到一个日志文件,并且指定文件的尺寸,当文件大小达到指定尺寸时,会自动把文件改名,同时产生一个新的文件 |
JDBCAppender | 把日志信息保存到数据库中 |
4.2.3 Layouts
布局器 Layouts用于控制日志输出内容的格式,让我们可以使用各种需要的格式输出日志。Log4j常用的Layouts:
格式化器类型 | 作用 |
---|---|
HTMLLayout | 格式化日志输出为HTML表格形式 |
SimpleLayout | 简单的日志输出格式化,打印的日志格式为(info - message) |
PatternLayout | 最强大的格式化期,可以根据自定义格式输出日志,如果没有指定转换格式,就是用默认的转换格式 |
4.3 Layout的格式
在 log4j.properties 配置文件中,我们定义了日志输出级别与输出端,在输出端中分别配置日志的输出格式。
* log4j 采用类似 C 语言的 printf 函数的打印格式格式化日志信息,具体的占位符及其含义如下:
%m 输出代码中指定的日志信息
%p 输出优先级,及 DEBUG、INFO 等
%n 换行符(Windows平台的换行符为 "\n",Unix 平台为 "\n")
%r 输出自应用启动到输出该 log 信息耗费的毫秒数
%c 输出打印语句所属的类的全名
%t 输出产生该日志的线程全名
%d 输出服务器当前时间,默认为 ISO8601,也可以指定格式,如:%d{yyyy年MM月dd日 HH:mm:ss}
%l 输出日志时间发生的位置,包括类名、线程、及在代码中的行数。如:Test.main(Test.java:10)
%F 输出日志消息产生时所在的文件名称
%L 输出代码中的行号
%% 输出一个 "%" 字符
* 可以在 % 与字符之间加上修饰符来控制最小宽度、最大宽度和文本的对其方式。如:
%5c 输出category名称,最小宽度是5,category<5,默认的情况下右对齐
%-5c 输出category名称,最小宽度是5,category<5,"-"号指定左对齐,会有空格
%.5c 输出category名称,最大宽度是5,category>5,就会将左边多出的字符截掉,<5不会有空格
%20.30c category名称<20补空格,并且右对齐,>30字符,就从左边交远销出的字符截掉
4.4 Appender的输出
控制台,文件,数据库
# 指定RootLogger顶级父元素默认配置信息
# 指定日志级别为trace,使用的appender为console
#log4j.rootLogger = trace, console, file
#log4j.rootLogger = trace, rollingFile
#log4j.rootLogger = trace, dailyFile
#log4j.rootLogger = trace, logDB
log4j.rootLogger = trace, A
# 指定控制台日志输出的appender
log4j.appender.console = org.apache.log4j.ConsoleAppender
# 指定消息格式layout
#log4j.appender.console.layout = org.apache.log4j.SimpleLayout
#log4j.appender.console.layout = org.apache.log4j.HTMLLayout
#log4j.appender.console.layout = org.apache.log4j.xml.XMLLayout
# 自定义消息输出格式及配置
log4j.appender.console.layout = org.apache.log4j.PatternLayout
#log4j.appender.console.layout.conversionPattern = %d [%t] %-5p [%c] - %m%n
#%d{yyyy-MM-dd HH:mm:ss.SSS} 表示 年月日时分秒毫秒
log4j.appender.console.layout.conversionPattern = %d{yyyy-MM-dd HH:mm:ss-SSS}%n
## 日志文件输出的appender对象
log4j.appender.file = org.apache.log4j.FileAppender
# 指定消息格式layout
log4j.appender.file.layout = org.apache.log4j.PatternLayout
# 指定消息格式的内容
log4j.appender.file.layout.conversionPattern = %r [%t] %p %c %x - %m%n
# 指定日志文件保存路径
log4j.appender.file.file = d:/logs/log4j.log
# 指定日志文件的字符集
log4j.appender.file.encoding = UTF-8
# 文件输出配置
log4j.appender.A = org.apache.log4j.DailyRollingFileAppender
#指定日志的输出路径
log4j.appender.A.File = d:/logs/log.txt
log4j.appender.A.Append = true
#使用自定义日志格式化器
log4j.appender.A.layout = org.apache.log4j.PatternLayout
#指定日志的输出格式
log4j.appender.A.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [%t:%r] - [%p] %m%n
#指定日志的文件编码
log4j.appender.A.encoding=UTF-8
## 按照文件大小拆分的appender对象
# 日志文件输出的appender对象
log4j.appender.rollingFile = org.apache.log4j.RollingFileAppender
# 指定消息格式layout
log4j.appender.rollingFile.layout = org.apache.log4j.PatternLayout
# 指定消息格式的内容
log4j.appender.rollingFile.layout.conversionPattern = %r [%t] %p %c %x - %m%n
# 指定日志文件保存路径
log4j.appender.rollingFile.file = d:/logs/log4j.log
# 指定日志文件的字符集
log4j.appender.rollingFile.encoding = UTF-8
# 指定日志文件内容的大小
log4j.appender.rollingFile.maxFileSize = 1MB
# 指定日志文件的数量
log4j.appender.rollingFile.maxBackupIndex = 10
## 按照时间拆分的appender对象
# 日志文件输出的appender对象
log4j.appender.dailyFile = org.apache.log4j.DailyRollingFileAppender
# 指定消息格式layout
log4j.appender.dailyFile.layout = org.apache.log4j.PatternLayout
# 指定消息格式的内容
log4j.appender.dailyFile.layout.conversionPattern = [%-10p]%r %l %d{yyyy-MM-dd HH:mm:ss.SSS} %m%n
# 指定日志文件保存路径
log4j.appender.dailyFile.file = d:/logs/log4j.log
# 指定日志文件的字符集
log4j.appender.dailyFile.encoding = UTF-8
# 指定日期拆分的规则
log4j.appender.dailyFile.datePattern = '.'yyyy-MM-dd-HH-mm-ss
# mysql
log4j.appender.logDB=org.apache.log4j.jdbc.JDBCAppender
log4j.appender.logDB.layout=org.apache.log4j.PatternLayout
log4j.appender.logDB.Driver=com.mysql.cj.jdbc.Driver
log4j.appender.logDB.URL=jdbc:mysql://localhost:3306/test_db?serverTimezone=UTC
log4j.appender.logDB.User=root
log4j.appender.logDB.Password=123456
log4j.appender.logDB.Sql=INSERT INTO log(project_name, create_date, level, category, file_name, thread_name, line, all_category, message) values('one fine','%d{yyyy-MM-ddHH:mm:ss}','%p','%c','%F','%t','%L','%l','%m')
建表sql语句:
CREATE TABLE `log` (
`log_id` int(11) NOT NULL AUTO_INCREMENT,
`project_name` varchar(255) DEFAULT NULL COMMENT '目项名',
`create_date` varchar(255) DEFAULT NULL COMMENT '创建时间',
`level` varchar(255) DEFAULT NULL COMMENT '优先级',
`category` varchar(255) DEFAULT NULL COMMENT '所在类的全名',
`file_name` varchar(255) DEFAULT NULL COMMENT '输出日志消息产生时所在的文件名称 ',
`thread_name` varchar(255) DEFAULT NULL COMMENT '日志事件的线程名',
`line` varchar(255) DEFAULT NULL COMMENT '号行',
`all_category` varchar(255) DEFAULT NULL COMMENT '日志事件的发生位置',
`message` varchar(4000) DEFAULT NULL COMMENT '输出代码中指定的消息',
PRIMARY KEY (`log_id`)
);
导入mysql依赖:
dependencies {
// ...
// https://mvnrepository.com/artifact/mysql/mysql-connector-java
compile group: 'mysql', name: 'mysql-connector-java', version: '8.0.20'
}
4.5 自定义Logger
# RootLogger配置
log4j.rootLogger = trace, console
# 自定义Logger
log4j.logger.top.onefine = info, file
log4j.logger.org.apache = error
# 指定控制台日志输出的appender
log4j.appender.console = org.apache.log4j.ConsoleAppender
# 指定消息格式layout
#log4j.appender.console.layout = org.apache.log4j.SimpleLayout
#log4j.appender.console.layout = org.apache.log4j.HTMLLayout
#log4j.appender.console.layout = org.apache.log4j.xml.XMLLayout
# 自定义消息输出格式及配置
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.conversionPattern = %d [%t] %-5p [%c] - %m%n
## 日志文件输出的appender对象
log4j.appender.file = org.apache.log4j.FileAppender
# 指定消息格式layout
log4j.appender.file.layout = org.apache.log4j.PatternLayout
# 指定消息格式的内容
log4j.appender.file.layout.conversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [%t:%r] - [%p] %m%n
# 指定日志文件保存路径
log4j.appender.file.file = d:/logs/log4j.log
# 指定日志文件的字符集
log4j.appender.file.encoding = UTF-8
package top.onefine;
import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Logger;
import org.apache.log4j.helpers.LogLog;
import org.junit.Test;
public class Log4jTest {
@Test
public void testQuick() {
// // 开启log4j内置日志记录
// LogLog.setInternalDebugging(true);
// // 初始化配置信息,这里暂不使用配置文件
// BasicConfigurator.configure();
// 1. 获取日志记录器对象
Logger logger = Logger.getLogger(Log4jTest.class);
// 2. 日志记录输出
// for (int i = 0; i < 100; i++) {
// 日志级别
logger.fatal("fatal"); // 严重错误,一般会造成系统崩溃和终止运行
// 最常用
logger.error("error"); // 错误信息,但不会影响系统运行
logger.warn("warn"); // 警告信息,可能会发生问题
logger.info("info"); // 程序运行信息,数据库的连接、网络、IO操作等
logger.debug("debug"); // 调试信息,一般在开发阶段使用,记录程序的变量、参数等
// log4j默认级别是debug
logger.trace("trace"); // 追踪信息,记录程序的所有流程信息
// }
// 自定义 org.apache
Logger logger2 = Logger.getLogger(Logger.class);
logger2.fatal("fatal logger2"); // 严重错误,一般会造成系统崩溃和终止运行
logger2.error("error logger2"); // 错误信息,但不会影响系统运行
logger2.warn("warn logger2"); // 警告信息,可能会发生问题
logger2.info("info logger2"); // 程序运行信息,数据库的连接、网络、IO操作等
logger2.debug("debug logger2"); // 调试信息,一般在开发阶段使用,记录程序的变量、参数等
logger2.trace("trace logger2"); // 追踪信息,记录程序的所有流程信息
}
}
控制台输出:
2020-05-17 20:58:13,203 [Test worker] FATAL [top.onefine.Log4jTest] - fatal
2020-05-17 20:58:13,206 [Test worker] ERROR [top.onefine.Log4jTest] - error
2020-05-17 20:58:13,207 [Test worker] WARN [top.onefine.Log4jTest] - warn
2020-05-17 20:58:13,207 [Test worker] INFO [top.onefine.Log4jTest] - info
2020-05-17 20:58:13,208 [Test worker] FATAL [org.apache.log4j.Logger] - fatal logger2
2020-05-17 20:58:13,208 [Test worker] ERROR [org.apache.log4j.Logger] - error logger2
五、JCL 学习【了解】
JCL全称为Jakarta Commons Logging,是Apache提供的一个通用日志API。
它是为 "所有的Java日志实现"提供一个统一的接口,它自身也提供一个日志的实现,但是功能非常常弱(SimpleLog),所以一般不会单独使用它。他允许开发人员使用不同的具体日志实现工具: Log4j, Jdk自带的日志(JUL)
JCL 有两个基本的抽象类:
- Log:基本记录器
- LogFactory:负责创建Log实例
5.1 JCL入门
- 建立gradle工程
- 添加依赖
dependencies {
// https://mvnrepository.com/artifact/commons-logging/commons-logging
compile group: 'commons-logging', name: 'commons-logging', version: '1.2'
// https://mvnrepository.com/artifact/junit/junit
testCompile group: 'junit', name: 'junit', version: '4.13'
// // https://mvnrepository.com/artifact/log4j/log4j
// compile group: 'log4j', name: 'log4j', version: '1.2.17'
}
- 入门代码
package top.onefine;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Test;
public class JCLTest {
@Test
public void testQuick() {
// 获取log日志记录器对象
Log log = LogFactory.getLog(JCLTest.class);
// 日志记录输出
log.info("hello jcl"); // 若不导入任何日志实现,则默认使用jdk14
}
}
不导入log4j的输出:
导入log4j的输出:
我们为什么要使用日志门面:
- 面向接口开发,不再依赖具体的实现类。减少代码的耦合
- 项目通过导入不同的日志实现类,可以灵活的切换日志框架
- 统一API,方便开发者学习和使用
- 统一配置便于项目日志的管理
5.2 JCL原理
- 通过LogFactory动态加载Log实现类
- 日志门面支持的日志实现数组
private static final String[] classesToDiscover =
new String[]{"org.apache.commons.logging.impl.Log4JLogger",
"org.apache.commons.logging.impl.Jdk14Logger",
"org.apache.commons.logging.impl.Jdk13LumberjackLogger",
"org.apache.commons.logging.impl.SimpleLog"
};
- 获取具体的日志实现
for(int i = 0; i < classesToDiscover.length && result == null; ++i) {
result = this.createLogFromClass(classesToDiscover[i], logCategory, true);
}
一、日志框架
日志框架的能力:
- 定制输出目标
- 定制输出格式
- 携带上下文信息
- 运行时选择性输出
- 灵活的配置
- 优异的性能
名词解释:
- 日志门面:日志的统一的接口层(抽象层)
- 日志实现:日志接口的实现层
日志门面 (日志的抽象层) | 日志实现 |
---|---|
左边选一个门面(抽象层)、右边来选一个实现:
- 日志门面:
SLF4J
; - 日志实现:
Logback
;
SpringBoot选用 SLF4j和logback这个组合!另外,SpringBoot底层是Spring框架,Spring框架默认是用JCL。
二、SLF4j使用
1、如何在系统中使用SLF4j:https://www.slf4j.org
以后开发的时候,日志记录方法的调用不应该来直接调用日志的实现类,而是调用日志抽象层里面的方法;
图片来源: http://www.slf4j.org/manual.html
导入slf4j 和 logback 的坐标:
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.30</version>
</dependency>
每一个日志的实现框架都有自己的配置文件。使用slf4j(日志门面)以后,配置文件还是做成 日志实现框架 自己本身的配置文件。
2. 遗留问题
我的系统使用slf4j+logback,而Spring(commons-logging)、Hibernate(jboss-logging)、MyBatis、xxxx…
如何统一日志记录,即使是别的框架也和我的系统一起统一使用slf4j进行输出?
图片来源:http://www.slf4j.org/legacy.html
如何让系统中所有的日志都统一到slf4j:
-
将系统中其他日志框架先排除出去;
-
用中间包来替换原有的日志框架;
-
导入slf4j其他的实现;
3. 使用SLF4J
package top.onefine;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class LoggerTest {
private Logger logger = LoggerFactory.getLogger(LoggerTest.class); //
@Test
public void loggerTest() {
// 日志的级别由低到高:trace < debug < info < warn < error
// SpringBoot默认日志级别是:info级别(只会输出info及以后的内容),可在配置文件中调整输出的日志级别
logger.debug("debug...");
logger.info("info...");
logger.error("error...");
}
}
或者引入lombok
:
package top.onefine;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
//import org.slf4j.Logger;
//import org.slf4j.LoggerFactory;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j // 引入lombok
public class LoggerTest {
// private Logger logger = LoggerFactory.getLogger(LoggerTest.class);
@Test
public void loggerTest() {
// 日志的级别由低到高:trace < debug < info < warn < error
// SpringBoot默认日志级别是:info级别(只会输出info及以后的内容),可在配置文件中调整输出的日志级别
log.debug("debug...");
log.info("info...");
log.error("error...");
}
}
使用变量:
三、SpringBoot日志关系
总结:
-
SpringBoot底层也是使用slf4j+logback的方式进行日志记录
-
SpringBoot也把其他的日志都替换成了slf4j;
如果要引入其他框架,一定要把这个框架的默认日志依赖移除掉,如Spring框架用的是commons-logging,SpringBoot的做法:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring‐core</artifactId>
<exclusions>
<exclusion>
<groupId>commons‐logging</groupId>
<artifactId>commons‐logging</artifactId>
</exclusion>
</exclusions>
</dependency>
SpringBoot能自动适配所有的日志,而且底层使用slf4j+logback的方式记录日志,引入其他框架的时候,只需要把这个框架依赖的日志框架排除掉即可;