有日志么?
日志有价值么?
日志框架了解么?
“日志”这个词最早见于航海领域,是记录航行主要情况的载体文件,内容包括操作指令、气象、潮流、航向、航速、旅客、货物等,是处理海事纠纷或者海难的原始依据之一。尔后延伸到航空领域,黑匣子就是一个重要的航空日志载体,调查空难原因时第一反应是找到黑匣子,并通过解析其中的日志信息来还原空难的事实真相 码出高效:Java开发手册
本期内容提要
JUL、log4j1、log4j2、logback 基础
1. JUL(Java Util Logging)
JUL(Java Util Logging),也称做 JDK Logging,是自 JDK 1.4 以来自带的日志记录技术。
1.1. 基础
用JDK Logging 记日志的例子: package webj2ee; import java.io.IOException;import java.util.logging.*; public class JdkLoggingDemo { public static void main(String[] args) throws IOException { // Handler - ConsoleHander ConsoleHandler consoleHandler = new ConsoleHandler(); consoleHandler.setLevel(Level.ALL); consoleHandler.setFormatter(new Formatter() { // Formatter @Override public String format(LogRecord record) { return "[CONSOLE-1]"+record.getMessage()+"\n"; } }); // Handler - FileHandler FileHandler fileHandler = new FileHandler("filelog.log"); fileHandler.setLevel(Level.ALL); fileHandler.setFormatter(new Formatter() { // Formatter @Override public String format(LogRecord record) { return "[FILE-1]"+record.getMessage()+"\n"; } }); // Logger Logger logger = Logger.getLogger(JdkLoggingDemo.class.getName()); logger.addHandler(consoleHandler); logger.addHandler(fileHandler); logger.setUseParentHandlers(false); // 避免将日志打到父Logger上 logger.setLevel(Level.ALL); logger.setFilter(new Filter() { @Override public boolean isLoggable(LogRecord record) { return true; } }); // 7 个日志级别 logger.finest("finest msg!"); logger.finer("finer msg!"); logger.fine("fine msg!"); logger.config("confg msg!"); logger.info("info msg!"); logger.warning("warning msg!"); logger.severe("severe msg!"); // 还能记录异常 try{ throw new NullPointerException("exception!"); }catch(NullPointerException ex){ logger.log(Level.SEVERE, ex.getMessage(), ex); } }}
关键点:
1.2. logging.properties 配置
1.3. 基本原理
2. Log4j 1.x
注意:Apache 官方于 2015.8.5 宣布 Log4j 1.x 项目生命周期结束,并建议用户升级到 Log4j 2(Log4j2 更快、更可靠、使用更简单)。
2.1. POM
<!-- https://mvnrepository.com/artifact/log4j/log4j --><dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version></dependency>
注:log4j-1.2.17 是 log4j 1.x 的最后一个官方发布版本。
2.2. Loggers、Appenders、Layouts 概述
Log4j 有 3 类主要组件:loggers、appenders、layouts。
2.3. Loggers
2.3.1 Loggers 的层次结构
A logger is said to be an ancestor of another logger if its name followed by a dot is a prefix of the descendant logger name. A logger is said to be a parent of a child logger if there are no ancestors between itself and the descendant logger. Named Hierarchy
例如:
Logger 的基本接口,如下所示:
package org.apache.log4j;
public class Logger { // Creation & retrieval methods: public static Logger getRootLogger(); public static Logger getLogger(String name);
// printing methods: public void trace(Object message); public void debug(Object message); public void info(Object message); public void warn(Object message); public void error(Object message); public void fatal(Object message);
// generic printing method: public void log(Level l, Object message);}
2.3.2 Logger 的 LEVEL
Logger 可以设置 level:TRACE、DEBUG、INFO、WARN、ERROR、FATAL;
示例:
图:Logger LEVEL 的继承关系
2.3.3 日志记录请求(logging request)的LEVEL
A log request of level p in a logger with (either assigned or inherited, whichever is appropriate) level q, is enabled if p >= q. Basic Selection Rule
例子:
package webj2ee;
import org.apache.log4j.Level;import org.apache.log4j.Logger;
public class Log4jDemo { public static void main(String[] args) { // get a logger instance named "com.foo" Logger logger = Logger.getLogger("com.foo");
// Now set its level. Normally you do not need to set the // level of a logger programmatically. This is usually done // in configuration files. logger.setLevel(Level.INFO);
Logger barlogger = Logger.getLogger("com.foo.Bar");
// This request is enabled, because WARN >= INFO. logger.warn("Low fuel level.");
// This request is disabled, because DEBUG < INFO. logger.debug("Starting search for nearest gas station.");
// The logger instance barlogger, named "com.foo.Bar", // will inherit its level from the logger named // "com.foo" Thus, the following request is enabled // because INFO >= INFO. barlogger.info("Located nearest gas station.");
// This request is disabled, because DEBUG < INFO. barlogger.debug("Exiting gas station search"); }}
2.3.4 Logger 的单例特性
Calling the getLogger method with the same name will always return a reference to the exact same logger object.
Logger x = Logger.getLogger("wombat");Logger y = Logger.getLogger("wombat");assert x == y;
进阶示例:
package webj2ee;
import org.apache.log4j.BasicConfigurator;import org.apache.log4j.Level;import org.apache.log4j.Logger;
import java.util.concurrent.Callable;import java.util.concurrent.FutureTask;
public class Log4jDemo { public static void main(String[] args) throws InterruptedException { BasicConfigurator.configure();
// get a logger instance named "com.foo" Logger logger = Logger.getLogger("com.foo");
// Now set its level. Normally you do not need to set the // level of a logger programmatically. This is usually done // in configuration files. logger.setLevel(Level.INFO);
new Thread(new FutureTask<String>(new Callable<String>() { @Override public String call() throws Exception { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } Logger logger = Logger.getLogger("com.foo"); logger.setLevel(Level.DEBUG); return null; } })).start();
int i=0; while (i++ < 3){ Logger barlogger = Logger.getLogger("com.foo.Bar");
// This request is enabled, because WARN >= INFO. logger.warn(">>>>>>Low fuel level.");
// This request is disabled, because DEBUG < INFO. logger.debug("Starting search for nearest gas station.");
// The logger instance barlogger, named "com.foo.Bar", // will inherit its level from the logger named // "com.foo" Thus, the following request is enabled // because INFO >= INFO. barlogger.info("Located nearest gas station.");
// This request is disabled, because DEBUG < INFO. barlogger.debug("Exiting gas station search");
Thread.sleep(1000); } }}
2.4. Appenders
The output of a log statement of logger C will go to all the appenders in C and its ancestors. This is the meaning of the term "appender additivity". However, if an ancestor of logger C, say P, has the additivity flag set to false, then C's output will be directed to all the appenders in C and its ancestors upto and including P but not the appenders in any of the ancestors of P. Loggers have their additivity flag set to true by default. —— Appender Additivity
图:appender additivity 特性图
2.5. Layouts
Appenders 用于控制日志的输出目的地,Layouts 则可以附加到 Appender上控制日志输出格式。
2.6. 配置 Log4j
2.6.1 BasicConfigurator.configure()
BasicConfigurator.configure() 是 log4j 自带的、最基础的配置模块,它会给 root logger 添加一个 ConsoleAppender。
示例:
package webj2ee;
import org.apache.log4j.BasicConfigurator;import org.apache.log4j.Logger;
public class Log4jDemo { public static void main(String[] args) { BasicConfigurator.configure();
Logger logger = Logger.getLogger(Log4jDemo.class); logger.info("log some message!"); }}
2.6.2 PropertyConfigurator.configure()
PropertyConfigurator.configure() 可以从文件、I/O、URL、Properties 中读取对 log4j 的配置:
示例:
package webj2ee;
import org.apache.log4j.Logger;import org.apache.log4j.PropertyConfigurator;
import java.util.Properties;
public class Log4jDemo { public static void main(String[] args) { // Log4j Configuration: Properties Properties log4jprops = new Properties(); log4jprops.setProperty("log4j.rootLogger", "DEBUG, A1"); log4jprops.setProperty("log4j.appender.A1", "org.apache.log4j.ConsoleAppender"); log4jprops.setProperty("log4j.appender.A1.layout", "org.apache.log4j.PatternLayout"); log4jprops.setProperty("log4j.appender.A1.layout.ConversionPattern", "%-4r [%t] %-5p %c %x - %m%n");
// Load Log4j Configuration: PropertyConfigurator.configure(log4jprops);
// do logging Logger logger = Logger.getLogger(Log4jDemo.class); logger.info("log some message!"); }}
2.6.4 Log4j 的默认初始化套路
3. Log4j2
package webj2ee;
import org.apache.logging.log4j.LogManager;import org.apache.logging.log4j.Logger;
public class Log4j2Demo { public static void main(String[] args) { // root logger Logger logger = LogManager.getRootLogger(); // 同一个 name,同一个 logger Logger x = LogManager.getLogger("wombat"); Logger y = LogManager.getLogger("wombat"); System.out.println(x == y); // 同样有 Logger 分级 // 同样有 logging request 分级 // 同样有 additivity 控制策略 }}
4. Logback
4.1. Logback 基本构成
Logback 分3个模块:logback-core, logback-classic,logback-access。
备注:Logback-classic module requires the presence of slf4j-api.jar and logback-core.jar in addition to logback-classic.jar on the classpath.
maven 依赖:
<dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> <version>1.2.3</version></dependency><dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version></dependency><dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.29</version></dependency>
示例:
package webj2ee;
import org.slf4j.Logger;import org.slf4j.LoggerFactory;
public class LogbackDemo { public static void main(String[] args) { Logger logger = LoggerFactory.getLogger(LogbackDemo.class); logger.debug("Hello world!"); }}
4.2. Logger、Appenders、Layouts
4.2.1 LoggerContext、Logger 继承关系
图:logger level 策略示意
图:logging request level 策略示意
图:additivity 策略示意
例1:logging request level 与 logger level 关系;
package webj2ee;
import org.slf4j.Logger;import org.slf4j.LoggerFactory;
public class LogbackDemo { public static void main(String[] args) { Logger x = LoggerFactory.getLogger("wombat"); Logger y = LoggerFactory.getLogger("wombat"); System.out.println(x == y); }}
4.3. logback 配置
4.3.1 默认配置步骤
4.3.2 经典配置
<configuration> <appender name="FILE" class="ch.qos.logback.core.FileAppender"> <file>foo.log</file> <encoder> <pattern>%date %level [%thread] %logger{10} [%file : %line] %msg%n</pattern> </encoder> </appender>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%msg%n</pattern> </encoder> </appender>
<logger name="chapters.configuration.Foo" additivity="false"> <appender-ref ref="FILE" /> </logger>
<root level="debug"> <appender-ref ref="STDOUT" /> </root></configuration>
5. 怎么合理记录日志?
5.1. 正确区分日志级别
5.2. 最佳实践
正例:logger.error(各类参数或对象toString()+"_" + e.getMessage(), e);
正例:logger.debug("Processing trade with id: {} and symbol: {}", id, symbol);
if (logger.isDebugEnabled()) { logger.debug("Processing trade with id: {} and symbol: {}", id, symbol);}
5.3. 案例分析
2.3.1 例1
改造前:
改造后:
参考:
《码出高效:Java 开发手册》 《Java 开发手册 1.5.0》 JDK Logging 深入分析: https://blog.csdn.net/qingkangxu/article/details/7514770 jdk-logging、log4j、logback日志介绍及原理: https://my.oschina.net/pingpangkuangmo/blog/406618 优雅的使用slf4j: https://www.jianshu.com/p/7b5860be190f log4j1.2: http://logging.apache.org/log4j/1.2/ Apache™ Logging Services™ Project Announces Log4j™ 1 End-Of-Life; Recommends Upgrade to Log4j 2: https://blogs.apache.org/foundation/entry/apache_logging_services_project_announces 《The complete log4j manual》 logback: http://logback.qos.ch/index.html