目录
为什么要使用logback ?
Logback 与 Log4J
Logback的结构
快速上手
一个简单的例子
Logger,Appenders 与 Layouts
Logger
分层命名规则
有效级别即级别继承
Appender
Layout
补充
常用配置
在开发中不建议使用System.out因为大量的使用会增加资源的消耗。因为使用System.out是在当前线程执行的,写入文件也是写入完毕之后才继续执行下面的程序。而使用Log工具不但可以控制日志是否输出,怎么输出,它的处理机制也是通知写日志,继续执行后面的代码不必等日志写完。
Slf4j是The Simple Logging Facade for Java的简称,是一个简单日志门面抽象框架,它本身只提供了日志Facade API和一个简单的日志类实现,一般常配合Log4j,LogBack,java.util.logging使用。Slf4j作为应用层的Log接入时,程序可以根据实际应用场景动态调整底层的日志实现框架(Log4j/LogBack/JdkLog...);
LogBack和Log4j都是开源日记工具库,LogBack是Log4j的改良版本,比Log4j拥有更多的特性,同时也带来很大性能提升。详细数据可参照下面地址:Reasons to prefer logback over log4j LogBack官方建议配合Slf4j使用,这样可以灵活地替换底层日志框架。
默认情况下,Spring Boot会用Logback来记录日志,并用INFO级别输出到控制台。
2018-06-15 17:11:43.510 INFO 11956 --- [ main] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@6c130c45: startup date [Fri Jun 15 17:11:43 CST 2018]; root of context hierarchy
2018-06-15 17:11:43.649 INFO 11956 --- [ main] f.a.AutowiredAnnotationBeanPostProcessor : JSR-330 'javax.inject.Inject' annotation found and supported for autowiring
2018-06-15 17:11:43.671 INFO 11956 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'configurationPropertiesRebinderAutoConfiguration' of type [class org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration$$EnhancerBySpringCGLIB$$e47494a7] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v1.4.1.RELEASE)
2018-06-15 17:11:44.070 INFO 11956 --- [ main] c.csizg.iot.model.CsizgModelApplication : No active profile set, falling back to default profiles: default
2018-06-15 17:11:44.084 INFO 11956 --- [ main] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@246f8b8b: startup date [Fri Jun 15 17:11:44 CST 2018]; parent: org.springframework.context.annotation.AnnotationConfigApplicationContext@6c130c45
2018-06-15 17:11:45.501 INFO 11956 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Multiple Spring Data modules found, entering strict repository configuration mode!
2018-06-15 17:11:45.716 INFO 11956 --- [ main] o.s.cloud.context.scope.GenericScope : BeanFactory id=ce09d350-18eb-3a03-8c8c-ed3394b359ad
从上图可以看到,日志输出内容元素具体如下:
LogBack被分为3个组件,logback-core, logback-classic 和 logback-access.
其中logback-core提供了LogBack的核心功能,是另外两个组件的基础。
logback-classic则实现了Slf4j的API,所以当想配合Slf4j使用时,需要将logback-classic加入classpath。
可以启动服务的时候指定 profile (如不指定使用默认),如指定prod 的方式为:
java -jar xxx.jar --spring.profiles.active=prod
如果在 logback.xml 和 application.properties 中定义了相同的配置(如都配置了 org.springframework.web)但是输出级别不同,则实际上 application.properties 的优先级高于 logback.xml
Logback 可以使用 {} 占位符来拼接字符串,而不需要使用 ““+”” 来连接字符串。 @Slf4j 就不用 private static final Logger logger = LogManager.getLogger(ApproveTaskController.class); logging.file ,设置文件,可以是绝对路径,也可以是相对路径。如: logging.file=my.log logging.path ,设置目录,会在该目录下创建 spring.log 文件,并写入日志内容,如: logging.path=/var/log
org.slf4j
slf4j-api
ch.qos.logback
logback-core
ch.qos.logback
logback-classic
package com.csizg.iot.portal.v1.controller;
import com.alibaba.fastjson.JSONObject;
import com.csizg.iot.portal.v1.domain.dto.user.SafetyUser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* @author limengying-8088
* @version v1.0
* @date 16:15 2018/6/15
*/
@RestController
@RequestMapping("/v1/portal/logDemo/")
public class LogDemoController {
private static final Logger logger = LoggerFactory.getLogger(LogDemoController.class);
@PostMapping("/addLog")
public SafetyUser addLog(@RequestBody SafetyUser safetyUser) {
logger.info("========我是log");
return safetyUser;
}
}
控制台结果
2018-06-16 13:57:32.591 INFO 10044 --- [nio-8005-exec-1] c.c.i.p.v1.controller.LogDemoController : ========我是log
LoggerFactory 的 getLogger() 方法接收一个参数,以这个参数决定 logger 的名字,这里传入了 LogDemoController 这个类的 Class 实例,那么 logger 的名字便是 LogDemoController 这个类的全限定类名:com.csizg.iot.portal.v1.controlle.LogDemoController
在 logback 里,最重要的三个类分别是 Logger Appender Layout
每个 logger 都有一个 name,这个 name 的格式与 Java 语言中的包名格式相同。这也是前面的例子中直接把一个 class 对象传进 LoggerFactory.getLogger() 方法作为参数的原因。
例如, 命名为 com.foo 的 logger,是命名为 com.foo.Bar 的 logger 的父亲,是命名为 com.foo.Bar.demo 的 logger 的祖先。
在 logger 上下文中,有一个 root logger,作为所有 logger 的祖先,这是 logback 内部维护的一个 logger,并非开发者自定义的 logger。
可通过以下方式获得这个 logger :
Logger rootLogger = LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
日志打印级别从低级到高级排序的顺序是: TRACE < DEBUG < INFO < WARN < ERROR
如果一个 logger 允许打印一条具有某个日志级别的信息,那么它也必须允许打印具有比这个日志级别更高级别的信息,而不允许打印具有比这个日志级别更低级别的信息。
public void testLevel(){
//这里强制类型转换时为了能设置 logger 的 Level
ch.qos.logback.classic.Logger logger =
(ch.qos.logback.classic.Logger) LoggerFactory.getLogger("com.foo");
logger.setLevel(Level.INFO);
// barlogger 是 logger 的一个子 logger
// 它继承了 logger 的级别 INFO
Logger barlogger = LoggerFactory.getLogger("com.foo.Bar");
// 这个语句能打印,因为 WARN > INFO
logger.warn("can be printed because WARN > INFO");
// 这个语句不能打印,因为 DEBUG < INFO.
logger.debug("can not be printed because DEBUG < INFO");
// 以下语句能打印,因为 INFO >= INFO
barlogger.info("can be printed because INFO >= INFO");
// 以下语句不能打印,因为 DEBUG < INFO
barlogger.debug("can not be printed because DEBUG < INFO");
}
控制台结果
2018-06-16 18:01:02.354 WARN 9908 --- [nio-8005-exec-5] com.foo : can be printed because WARN > INFO
2018-06-16 18:01:02.354 INFO 9908 --- [nio-8005-exec-5] com.foo.Bar : can be printed because INFO >= INFO
logback允许打印记录请求到多个目的地,目前有控制台、文件、远程套接字服务器、MySQL等多种appender。
Appender 是绑定在 logger 上的,同时,一个 logger 可以绑定多个 Appender,意味着一条信息可以同时打印到不同的目的地去。例如,常见的做法是,日志信息既输出到控制台,同时也记录到日志文件中,这就需要为 logger 绑定两个不同的 logger,而 logger 又有继承关系,因此一个 logger 打印信息时的目的地 Appender 需要参考它的父亲和祖先。。在 logback 中,默认情况下,如果一个 logger 打印一条信息,那么这条信息首先会打印至它自己的 Appender,然后打印至它的父亲和父亲以上的祖先的 Appender,但如果它的父亲设置了 additivity = false,那么这个 logger 除了打印至它自己的 Appender 外,只会打印至其父亲的 Appender
name指定
%d - %msg%n
日志打印格式
if(logger.isDebugEnabled()) {
logger.debug("the message is " + msg + " from " + somebody);
}
无论日志级别是什么,程序总要先执行 "the message is " + msg + " from " + somebody 这段字符串的拼接操作
一种改进的打印日志方式
if(logger.isDebugEnabled()) {
logger.debug("the message is " + msg + " from " + somebody);
}
当日志级别为 DEBUG 时,那么打印这行消息,需要判断两次日志级别。一次是logger.isDebugEnabled(),另一次是 logger.debug() 方法内部也会做的判断。
logger.debug("the message {} is from {}", msg, somebody);
In Logger, the logging methods are overloaded with forms that accept one, two or more values.[9] Occurrences of the simple pattern {} in the log message are replaced in turn with the values. This is simple to use yet provides a performance benefit when the values have expensive toString() methods. When logging is disabled at the DEBUG level, the logging framework does not need to evaluate the string representation of the values. In the following example, the values count or userAccountList only need to be evaluated when DEBUG is enabled; otherwise the overhead of the debug call is trivial.
根节点是 configuration,可包含0个或多个 appender,0个或多个 logger,最多一个 root。
logback加载 我们简单分析一下logback加载过程,当我们使用logback-classic.jar时,应用启动,那么logback会按照如下顺序进行扫描:
以上任何一项找到了,就不进行后续扫描,按照对应的配置进行logback的初始化,具体代码实现可见ch.qos.logback.classic.util.ContextInitializer类的findURLOfDefaultConfigurationFile方法。
当所有以上四项都找不到的情况下,logback会调用ch.qos.logback.classic.BasicConfigurator的configure方法,构造一个ConsoleAppender用于向控制台输出日志,默认日志输出格式为”%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} – %msg%n”。
logback的configuration
...
有一个level属性,没有name属性,因为已经被命名为"ROOT" 也是
异步写日志 日志通常来说都以文件形式记录到磁盘,例如使用
0
256
原理:当我们配置了AsyncAppender,系统启动时会初始化一条名为"AsyncAppender-Worker-ASYNC"的线程
当Logging Event进入AsyncAppender后,AsyncAppender会调用appender方法,appender方法中再将event填入Buffer(使用的Buffer为BlockingQueue,具体实现为ArrayBlockingQueye)前,会先判断当前Buffer的容量以及丢弃日志特性是否开启,当消费能力不如生产能力时,AsyncAppender会将超出Buffer容量的Logging Event的级别进行丢弃,作为消费速度一旦跟不上生产速度导致Buffer溢出处理的一种方式。
上面的线程的作用,就是从Buffer中取出Event,交给对应的appender进行后面的日志推送
从上面的描述我们可以看出,AsyncAppender并不处理日志,只是将日志缓冲到一个BlockingQueue里面去,并在内部创建一个工作线程从队列头部获取日志,之后将获取的日志循环记录到附加的其他appender上去,从而达到不阻塞主线程的效果。因此AsyncAppender仅仅充当的是事件转发器,必须引用另外一个appender来做事。
从上述原理,我们就能比较清晰地理解几个参数的作用了:
${CONSOLE_LOG_PATTERN}
INFO
ACCEPT
DENY
${FILE_LOG_PATTERN}
${LOG_HOME}/${INFO_LOG}/user-info-%d{yyyy-MM-dd}.%i.log
10MB
WARN
ACCEPT
DENY
${FILE_LOG_PATTERN}
${LOG_HOME}/${WARN_LOG}/user-warn-%d{yyyy-MM-dd}.log
${LOG_HOME}/user-error.log
ERROR
${FILE_LOG_PATTERN}
${LOG_HOME}/${ERROR_LOG}/user-error-%d{yyyy-MM-dd}.%i.log
5MB