1. 日志
2. 日志框架 vs 日志门面
- 日志框架
- 日志门面
- 时间线:log4j > JUL
2.1 JUL (Java Util Logging)
- Java 原生日志框架
- Logger: 日志记录器,日志系统的入口程序
- Handler: 处理器,决定日志的输出位置,控制台或文件等; ConsoleHandler, FileHandler
- Filter:过滤器
- Formatter: 格式化组件
- Level: 日志的输出级别

image.png
// Logger 之间的 ‘父子’关系,关系是通过树状结构存储的,不是 java 的 extends
// 父亲做的设置,也能同时作用于儿子
Logger logger = Logger.getLogger("org.log");
Logger logger2 = Logger.getLogger("org.log.JULTest");
System.out.println(logger2.getParent() == logger); // true
// 父亲是RootLogger,名称默认是一个空字符串
System.out.println(logger.getParent()); // java.util.logging.LogManager$RootLogger@32709393
System.out.println(logger.getParent().getName()); // 空字符串
// JUL 在初始化时会创建一个顶层RootLogger 作为所有Logger的父Logger
// RootLogger 是 LogManager的内部类
Logger logger = Logger.getLogger("org.log");
// java.util.logging.Logger#demandLogger
// JUL 日志管理器
LogManager manager = LogManager.getLogManager();
// java.util.logging.LogManager#ensureLogManagerInitialized
owner.rootLogger = owner.new RootLogger();
owner.addLogger(owner.rootLogger);
if (!owner.rootLogger.isLevelInitialized()) {
// private final static Level defaultLevel = Level.INFO;
owner.rootLogger.setLevel(defaultLevel);
}
/**
* 将自定义的父子关系通过路径来进行关联
* owner.addLogger(owner.rootLogger);
* LoggerContext 保存节点的Map关系
*/
LoggerContext cx = getUserContext();
private final Hashtable<String,LoggerWeakRef> namedLoggers = new Hashtable<>();
// java.util.logging - Logger
@Test
public void test() {
// 不能直接new对象 Logger logger = new Logger();
// 参数:类的全路径
// Logger logger = Logger.getLogger("org.log.JULTest");
Logger logger = Logger.getLogger(JULTest.class.getName());
// 方式二
logger.log(Level.INFO, "message");
// 动态生成数据,用占位符
String name = "xiao";
int age = 20;
logger.log(Level.INFO, "name:{0}, age:{1}", new Object[]{name, age});
/*
日志的级别(通过源码查看,非常简单)
SEVERE : 错误 --- 最高级的日志级别
WARNING : 警告
INFO : (默认级别)消息
CONFIG : 配置
FINE : 详细信息(少)
FINER : 详细信息(中)
FINEST : 详细信息 (多) --- 最低级的日志级别
两个特殊的级别
OFF 可用来关闭日志记录
ALL 启用所有消息的日志记录
对于日志的级别,我们重点关注的是new对象的时候的第二个参数
只展示比该数值大的日志级别
*/
}
@Test
public void test() {
Logger logger = Logger.getLogger(JULTest.class.getName());
// 1. 关闭默认日志打印方式
logger.setUseParentHandlers(false);
// 2. 设置 handler 和 formatter
ConsoleHandler handler = new ConsoleHandler();
SimpleFormatter formatter = new SimpleFormatter();
handler.setFormatter(formatter);
logger.addHandler(handler);
// 3. 设置 Level, logger 和 handler 的级别必须一致
logger.setLevel(Level.ALL);
handler.setLevel(Level.ALL);
logger.info("hello");
}
@Test
public void test() throws IOException {
Logger logger = Logger.getLogger(JULTest.class.getName());
// 1. 关闭默认日志打印方式
logger.setUseParentHandlers(false);
// 2. 设置 handler 和 formatter
FileHandler fileHandler = new FileHandler("D:\\downloads\\2020.log");
SimpleFormatter formatter = new SimpleFormatter();
fileHandler.setFormatter(formatter);
logger.addHandler(fileHandler);
// 2.1 可同时添加多个 handler
ConsoleHandler consoleHandler = new ConsoleHandler();
consoleHandler.setFormatter(formatter);
logger.addHandler(consoleHandler);
// 3. 设置 Level
logger.setLevel(Level.ALL);
fileHandler.setLevel(Level.ALL);
consoleHandler.setLevel(Level.CONFIG);
logger.info("hello");
}
2.2 Log4j
- Logger 日志记录器
- log4j.jar
org.apache.log4j.Logger
- Appenders 输出控制器,示例 DailyRollingFileAppender 每天输出到一个新文件
- Layout 日志格式化
- 主要使用其配置文件 log4j.properties
- 8 个日志级别:ALL < TRACE < DEBUG(default) < INFO < WARN < ERROR < FATAL < OFF
- 只输出级别不低于设定级别的日志信息
2.2.1 Logger
- 类的全限定名,命名具有继承机制,上辈所做的属性配置会影响子辈
- 特殊的 logger root
2.2.2 Appender 日志输出的目的地
- ConsoleAppender
- FileAppender
- RollingFileAppender 按照文件大小拆分
- DailyRollingFileAppender
2.2.3 Layouts
- 日志格式化通过 Appenders 后面附加 Layouts 来实现
- HTMLLayout,SimpleLayout,PatternLayout
2.2.4 PatternLayout
- 格式化日志信息类似于 C 语言的 printf 函数
- %p 日志级别 %n 换行
- %m 代码里的信息
- %L 行号
- %c 类全名; %t 线程全名
- %d 时间,默认为 ISO8601,也可以自定义
%d{yyyy-MM-dd HH:mm:ss:SSS}
- % 与 字符 之间的数字用来控制宽度,示例
[%-5p]
默认右对齐,加 -
表示左对齐
# log4j.rootLogger=[level], appenderName, appenderName, ...
# log4j.rootLogger=debug,CONSOLE,FILE,ROLLING,DAILY
log4j.rootLogger=debug,CONSOLE,DAILY
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.conversionPattern=[%-5p] %r %c %t %d{yyyy-MM-dd HH:mm:ss:SSS} %m%n
log4j.appender.FILE=org.apache.log4j.FileAppender
log4j.appender.FILE.layout=org.apache.log4j.PatternLayout
log4j.appender.FILE.layout.conversionPattern=[%-5p] %r %c %t %d{yyyy-MM-dd HH:mm:ss:SSS} %m%n
log4j.appender.FILE.file=/Users/xingxing.xiao/Downloads/mylog.log
# FileAppender extends WriterAppender
log4j.appender.FILE.encoding=UTF-8
# FileAppender 的子类 RollingFileAppender, DailyRollingFileAppender
log4j.appender.ROLLING=org.apache.log4j.RollingFileAppender
log4j.appender.ROLLING.layout=org.apache.log4j.PatternLayout
log4j.appender.ROLLING.layout.conversionPattern=[%-5p] %r %c %t %d{yyyy-MM-dd HH:mm:ss:SSS} %m%n
log4j.appender.ROLLING.file=/Users/xingxing.xiao/Downloads/rolling.log
log4j.appender.ROLLING.encoding=UTF-8
log4j.appender.ROLLING.maxFileSize=1MB
# 文件数量
log4j.appender.ROLLING.maxBackupIndex=5
log4j.appender.DAILY=org.apache.log4j.DailyRollingFileAppender
log4j.appender.DAILY.layout=org.apache.log4j.PatternLayout
log4j.appender.DAILY.layout.conversionPattern=[%-5p] %r %c %t %d{yyyy-MM-dd HH:mm:ss:SSS} %m%n
log4j.appender.DAILY.file=/Users/xingxing.xiao/Downloads/log.log
log4j.appender.DAILY.encoding=UTF-8
# String datePattern = "'.'yyyy-MM-dd"; 默认按天
log4j.appender.DAILY.datePattern='.'yyyy-MM-dd HH-mm-ss
private static Logger logger = Logger.getLogger(Demo2.class);
public static void main(String[] args) throws InterruptedException {
// 加载初始化配置
BasicConfigurator.configure();
logger.info("242342");
}
// 源码分析:1. 创建根节点 2. 添加 Appender
static public void configure() {
Logger root = Logger.getRootLogger();
root.addAppender(new ConsoleAppender(
new PatternLayout(PatternLayout.TTCC_CONVERSION_PATTERN)));
}
Logger.getLogger(Demo2.class);
/**
* LogManager 日志管理器,静态代码块 static{}
* Loader.getResource(configurationOptionStr);
* OptionConverter.selectAndConfigure
* configurator = new PropertyConfigurator();
*/
// PropertyConfigurator
static final String ROOT_LOGGER_PREFIX = "log4j.rootLogger";
static final String APPENDER_PREFIX = "log4j.appender.";
2.2.5 Log4j自定义Logger
log4j.rootLogger=debug,CONSOLE
# 自定义logger
log4j.logger.com.company=info,FILE
# 试试 配置 apache 的logger
log4j.logger.org.apache.log4j=error,FILE
log4j.appender.FILE=org.apache.log4j.FileAppender
# ......
- rootLogger级别是 debug, 自定义logger级别是info, 日志级别以自定义logger为主
- rootLogger输出到 console, 自定义logger输出到 file, 输出位置不同,取 rootLogger 和 自定义的
并集
(console 和 file 都输出了日志)
- 打印日志时,判断父Logger 和 根Logger,无关的自定义Logger 不影响(例如 apache 的logger)
2.3 JCL
- Jakarta Commons Logging 通用日志API
- 本身不记录日志,也是日志门面(面向接口开发,灵活切换框架)
- commons-logging.jar, 默认使用 JUL 日志
- 两个基本抽象类:Log, LogFactory
private static final Log log = LogFactory.getLog(Demo2.class);
log.info("hello");
/**
* instance = this.newInstance(name);
* org.apache.commons.logging.impl.LogFactoryImpl#discoverLogImplementation
* 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"};
* c = Class.forName(logAdapterClassName, true, currentCL);
*/
Important
- 日志的
父子继承关系
是按照包结构的关系来指定的(不是 Java 中的 extends),即越抽象的包越是父亲,越具体的包越是儿子
- 例:按照路径做父子关系,父 org.log,子 org.log.JULTest
- 设置 IDEA Console 控制台颜色
File -> setting --> Editor --> Color Scheme --> Console Colors
- 应用场景
网友评论