LogBack
是一個日誌框架,它與Log4j能夠說是同出一源,都出自Ceki Gülcü
之手。(log4j
的原型是早前由Ceki Gülcü
貢獻給Apache
基金會的)下載地址 logback.qos.ch/download.ht…html
Slf4j
是The Simple Logging Facade for Java
的簡稱,是一個簡單日誌門面抽象框架,它自己只提供了日誌Facade API
和一個簡單的日誌類實現,通常常配合Log4j,LogBack,java.util.logging
使用。Slf4j
做爲應用層的Log接入時,程序能夠根據實際應用場景動態調整底層的日誌實現框架(Log4j/LogBack/JdkLog…)
。java
LogBack
和Log4j
都是開源日記工具庫,LogBack
是Log4j
的改良版本,比Log4j
擁有更多的特性,同時也帶來很大性能提高。詳細數據可參照下面地址:Reasons to prefer logback over log4j
。git
LogBack
官方建議配合Slf4j
使用,這樣能夠靈活地替換底層日誌框架。github
TIPS:爲了優化log4j
,以及更大性能的提高,Apache基金會已經着手開發了log4j 2.0
, 其中也借鑑和吸取了logback
的一些先進特性,目前log4j2
還處於beta
階段spring
一、更快的實現:Logback
的內核重寫了,在一些關鍵執行路徑上性能提高10倍以上。並且logback
不只性能提高了,初始化內存加載也更小了。
二、很是充分的測試:Logback
通過了幾年,數不清小時的測試。Logback
的測試徹底不一樣級別的。
三、Logback-classic
很是天然實現了SLF4j:Logback-classic
實現了SLF4j
。在使用SLF4j中,你都感受不到logback-classic
。並且由於logback-classic
很是天然地實現了slf4j
, 所 以切換到log4j
或者其餘,很是容易,只須要提供成另外一個jar
包就OK,根本不須要去動那些經過SLF4JAPI
實現的代碼。
四、很是充分的文檔 官方網站有兩百多頁的文檔。
五、自動從新加載配置文件,當配置文件修改了,Logback-classic
能自動從新加載配置文件。掃描過程快且安全,它並不須要另外建立一個掃描線程。這個技術充分保證了應用程序能跑得很歡在JEE環境裏面。
六、Lilith
是log
事件的觀察者,和log4j
的chainsaw
相似。而lilith
還能處理大數量的log數據 。
七、謹慎的模式和很是友好的恢復,在謹慎模式下,多個FileAppender
實例跑在多個JVM下,能 夠安全地寫道同一個日誌文件。RollingFileAppender
會有些限制。Logback
的FileAppender
和它的子類包括 RollingFileAppender
可以很是友好地從I/O異常中恢復。
八、配置文件能夠處理不一樣的狀況,開發人員常常須要判斷不一樣的Logback配置文件在不一樣的環境下(開發,測試,生產)。而這些配置文件僅僅只有一些很小的不一樣,能夠經過,和來實現,這樣一個配置文件就能夠適應多個環境。
九、Filters
(過濾器)有些時候,須要診斷一個問題,須要打出日誌。在log4j
,只有下降日誌級別,不過這樣會打出大量的日誌,會影響應用性能。在Logback
,你能夠繼續 保持那個日誌級別而除掉某種特殊狀況,如alice
這個用戶登陸,她的日誌將打在DEBUG
級別而其餘用戶能夠繼續打在WARN
級別。要實現這個功能只需加4行XML
配置。能夠參考MDCFIlter
。
十、SiftingAppender
(一個很是多功能的Appender
):它能夠用來分割日誌文件根據任何一個給定的運行參數。如,SiftingAppender
可以區別日誌事件跟進用戶的Session
,而後每一個用戶會有一個日誌文件。
十一、自動壓縮已經打出來的log:RollingFileAppender
在產生新文件的時候,會自動壓縮已經打出來的日誌文件。壓縮是個異步過程,因此甚至對於大的日誌文件,在壓縮過程當中應用不會受任何影響。
十二、堆棧樹帶有包版本:Logback
在打出堆棧樹日誌時,會帶上包的數據。
1三、自動去除舊的日誌文件:經過設置TimeBasedRollingPolicy
或者SizeAndTimeBasedFNATP的maxHistory
屬性,你能夠控制已經產生日誌文件的最大數量。若是設置maxHistory 12
,那那些log
文件超過12
個月的都會被自動移除。apache
LogBack被分爲3個組件,logback-core, logback-classic
和 logback-access
。json
logback-core提供了LogBack的核心功能,是另外兩個組件的基礎。瀏覽器
logback-classic則實現了Slf4j
的API
,因此當想配合Slf4j使用時,須要將logback-classic
加入classpath
。安全
logback-access是爲了集成Servlet
環境而準備的,可提供HTTP-access
的日誌接口。springboot
代碼我已放到 Github ,導入spring-boot-logback
項目
github spring-boot-logback
假如maven依賴中添加了spring-boot-starter-logging
:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
複製代碼
那麼,咱們的Spring Boot應用將自動使用logback做爲應用日誌框架,Spring Boot啓動的時候,由org.springframework.boot.logging.Logging-Application-Listener根據狀況初始化並使用。
可是呢,實際開發中咱們不須要直接添加該依賴,你會發現spring-boot-starter其中包含了 spring-boot-starter-logging,該依賴內容就是 Spring Boot 默認的日誌框架 logback
配置文件 logback-spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 日誌根目錄-->
<springProperty scope="context" name="LOG_HOME" source="logging.path" defaultValue="/data/logs/spring-boot-logback"/>
<!-- 日誌級別 -->
<springProperty scope="context" name="LOG_ROOT_LEVEL" source="logging.level.root" defaultValue="DEBUG"/>
<!-- 標識這個"STDOUT" 將會添加到這個logger -->
<springProperty scope="context" name="STDOUT" source="log.stdout" defaultValue="STDOUT"/>
<!-- 日誌文件名稱-->
<property name="LOG_PREFIX" value="spring-boot-logback" />
<!-- 日誌文件編碼-->
<property name="LOG_CHARSET" value="UTF-8" />
<!-- 日誌文件路徑+日期-->
<property name="LOG_DIR" value="${LOG_HOME}/%d{yyyyMMdd}" />
<!--對日誌進行格式化-->
<property name="LOG_MSG" value="- | [%X{requestUUID}] | [%d{yyyyMMdd HH:mm:ss.SSS}] | [%level] | [${HOSTNAME}] | [%thread] | [%logger{36}] | --> %msg|%n "/>
<!--文件大小,默認10MB-->
<property name="MAX_FILE_SIZE" value="50MB" />
<!-- 配置日誌的滾動時間 ,表示只保留最近 10 天的日誌-->
<property name="MAX_HISTORY" value="10"/>
<!--輸出到控制檯-->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- 輸出的日誌內容格式化-->
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>${LOG_MSG}</pattern>
</layout>
</appender>
<!--輸出到文件-->
<appender name="0" class="ch.qos.logback.core.rolling.RollingFileAppender">
</appender>
<!-- 定義 ALL 日誌的輸出方式:-->
<appender name="FILE_ALL" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--日誌文件路徑,日誌文件名稱-->
<File>${LOG_HOME}/all_${LOG_PREFIX}.log</File>
<!-- 設置滾動策略,當天的日誌大小超過 ${MAX_FILE_SIZE} 文件大小時候,新的內容寫入新的文件, 默認10MB -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日誌文件路徑,新的 ALL 日誌文件名稱,「 i 」 是個變量 -->
<FileNamePattern>${LOG_DIR}/all_${LOG_PREFIX}%i.log</FileNamePattern>
<!-- 配置日誌的滾動時間 ,表示只保留最近 10 天的日誌-->
<MaxHistory>${MAX_HISTORY}</MaxHistory>
<!--當天的日誌大小超過 ${MAX_FILE_SIZE} 文件大小時候,新的內容寫入新的文件, 默認10MB-->
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>${MAX_FILE_SIZE}</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<!-- 輸出的日誌內容格式化-->
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>${LOG_MSG}</pattern>
</layout>
</appender>
<!-- 定義 ERROR 日誌的輸出方式:-->
<appender name="FILE_ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 下面爲配置只輸出error級別的日誌 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<OnMismatch>DENY</OnMismatch>
<OnMatch>ACCEPT</OnMatch>
</filter>
<!--日誌文件路徑,日誌文件名稱-->
<File>${LOG_HOME}/err_${LOG_PREFIX}.log</File>
<!-- 設置滾動策略,當天的日誌大小超過 ${MAX_FILE_SIZE} 文件大小時候,新的內容寫入新的文件, 默認10MB -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日誌文件路徑,新的 ERR 日誌文件名稱,「 i 」 是個變量 -->
<FileNamePattern>${LOG_DIR}/err_${LOG_PREFIX}%i.log</FileNamePattern>
<!-- 配置日誌的滾動時間 ,表示只保留最近 10 天的日誌-->
<MaxHistory>${MAX_HISTORY}</MaxHistory>
<!--當天的日誌大小超過 ${MAX_FILE_SIZE} 文件大小時候,新的內容寫入新的文件, 默認10MB-->
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>${MAX_FILE_SIZE}</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<!-- 輸出的日誌內容格式化-->
<layout class="ch.qos.logback.classic.PatternLayout">
<Pattern>${LOG_MSG}</Pattern>
</layout>
</appender>
<!-- additivity 設爲false,則logger內容不附加至root ,配置以配置包下的全部類的日誌的打印,級別是 ERROR-->
<logger name="org.springframework" level="ERROR" />
<logger name="org.apache.commons" level="ERROR" />
<logger name="org.apache.zookeeper" level="ERROR" />
<logger name="com.alibaba.dubbo.monitor" level="ERROR"/>
<logger name="com.alibaba.dubbo.remoting" level="ERROR" />
<!-- ${LOG_ROOT_LEVEL} 日誌級別 -->
<root level="${LOG_ROOT_LEVEL}">
<!-- 標識這個"${STDOUT}"將會添加到這個logger -->
<appender-ref ref="${STDOUT}"/>
<!-- FILE_ALL 日誌輸出添加到 logger -->
<appender-ref ref="FILE_ALL"/>
<!-- FILE_ERROR 日誌輸出添加到 logger -->
<appender-ref ref="FILE_ERROR"/>
</root>
</configuration>
複製代碼
配置文件 application.properties
#日誌級別從低到高分爲TRACE < DEBUG < INFO < WARN < ERROR < FATAL,若是設置爲WARN,則低於WARN的信息都不會輸出
logging.level.root=trace
logging.path=/data/logs/spring-boot-logback
複製代碼
這裏參考,嘟嘟獨立博客,和 Albin 的文章
Spring Boot乾貨系列:(七)默認日誌logback配置解析
日誌會天天新建一個文件夾,日文文件配置的每50兆,一個文本文件,超過新寫入一個
文件夾:20171031
文件夾內容:all_spring-boot-logback0.log
文件夾內容:all_spring-boot-logback1.log
文件夾內容:all_spring-boot-logback2.log
文件夾內容:err_spring-boot-logback0.log
複製代碼
一種多線程下日誌管理實踐方式
logback MDC(Mapped Diagnostic Context)與分佈式系統的跟蹤系統
Slf4j MDC 使用和 基於 Logback 的實現分析
MDC(Mapped Diagnostic Context,映射調試上下文)是 log4j 和 logback 提供的一種方便在多線程條件下記錄日誌的功能。某些應用程序採用多線程的方式來處理多個用戶的請求。在一個用戶的使用過程當中,可能有多個不一樣的線程來進行處理。典型的例子是 Web 應用服務器。當用戶訪問某個頁面時,應用服務器可能會建立一個新的線程來處理該請求,也可能從線程池中複用已有的線程。在一個用戶的會話存續期間,可能有多個線程處理過該用戶的請求。這使得比較難以區分不一樣用戶所對應的日誌。當須要追蹤某個用戶在系統中的相關日誌記錄時,就會變得很麻煩。
一種解決的辦法是採用自定義的日誌格式,把用戶的信息採用某種方式編碼在日誌記錄中。這種方式的問題在於要求在每一個使用日誌記錄器的類中,均可以訪問到用戶相關的信息。這樣纔可能在記錄日誌時使用。這樣的條件一般是比較難以知足的。MDC 的做用是解決這個問題。
MDC 能夠當作是一個與當前線程綁定的哈希表,能夠往其中添加鍵值對。MDC 中包含的內容能夠被同一線程中執行的代碼所訪問。當前線程的子線程會繼承其父線程中的 MDC 的內容。當須要記錄日誌時,只須要從 MDC 中獲取所需的信息便可。MDC 的內容則由程序在適當的時候保存進去。對於一個 Web 應用來講,一般是在請求被處理的最開始保存這些數據。
自定義攔截器 logback requestUUID
/** * 描述: 自定義攔截器 logback requestUUID * * @author yanpenglei * @create 2017-10-30 16:15 **/
public class ControllerInterceptor extends HandlerInterceptorAdapter {
private Logger LOGGER = LoggerFactory.getLogger(ControllerInterceptor.class);
//在請求處理以前回調方法
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String requestUUID = MDC.get("requestUUID");
if (requestUUID == null || "".equals(requestUUID)) {
String uuid = UUID.randomUUID().toString();
uuid = uuid.replaceAll("-", "").toUpperCase();
MDC.put("requestUUID", uuid);
LOGGER.info("ControllerInterceptor preHandle 在請求處理以前生成 logback requestUUID:{}", uuid);
}
return true;// 只有返回true纔會繼續向下執行,返回false取消當前請求
}
//請求處理以後回調方法
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
/* 線程結束後須要清除,不然當前線程會一直佔用這個requestId值 */
MDC.remove("requestUUID");
LOGGER.info("ControllerInterceptor postHandle 請求處理以後清除 logback MDC requestUUID");
}
//整個請求處理完畢回調方法
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
/*整個請求線程結束後須要清除,不然當前線程會一直佔用這個requestId值 */
MDC.clear();
LOGGER.info("ControllerInterceptor afterCompletion 整個請求處理完畢清除 logback MDC requestUUID");
}
}
複製代碼
對日誌進行格式化,時候用到
<property name="LOG_MSG" value="- | [%X{requestUUID}] | [%d{yyyyMMdd HH:mm:ss.SSS}] | [%level] | [${HOSTNAME}] | [%thread] | [%logger{36}] | --> %msg|%n "/>
複製代碼
/** * 描述:攔截器配置 * * @author yanpenglei * @create 2017-10-30 16:54 **/
@Configuration
public class MyWebMvcConfigurer extends WebMvcConfigurerAdapter {
@Override
public void addInterceptors(InterceptorRegistry registry) {
/** * 多個攔截器組成一個攔截器鏈 * addPathPatterns 用於添加攔截規則 * excludePathPatterns 用於排除攔截 */
registry.addInterceptor(new ControllerInterceptor()).addPathPatterns("/**");
super.addInterceptors(registry);
}
}
複製代碼
@Aspect
@Component
public class LogAspect {
private static final Logger log = LoggerFactory.getLogger(LogAspect.class);
private static final String dateFormat = "yyyy-MM-dd HH:mm:ss";
private static final String STRING_START = "\n>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n";
private static final String STRING_END = "\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n";
@Pointcut("execution(* io.ymq.logback.controller..*(..))")
public void serviceLog() {
}
@Around("serviceLog()")
public Object around(ProceedingJoinPoint joinPoint) {
try {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
Class<?> targetClass = method.getDeclaringClass();
StringBuffer classAndMethod = new StringBuffer();
Log classAnnotation = targetClass.getAnnotation(Log.class);
Log methodAnnotation = method.getAnnotation(Log.class);
if (classAnnotation != null) {
if (classAnnotation.ignore()) {
return joinPoint.proceed();
}
classAndMethod.append(classAnnotation.value()).append("-");
}
if (methodAnnotation != null) {
if (methodAnnotation.ignore()) {
return joinPoint.proceed();
}
classAndMethod.append(methodAnnotation.value());
}
String target = targetClass.getName() + "#" + method.getName();
String params = JSONObject.toJSONStringWithDateFormat(joinPoint.getArgs(), dateFormat, SerializerFeature.WriteMapNullValue);
log.info(STRING_START + "{} 開始調用--> {} 參數:{}", classAndMethod.toString(), target, params);
long start = System.currentTimeMillis();
Object result = joinPoint.proceed();
long timeConsuming = System.currentTimeMillis() - start;
log.info("\n{} 調用結束<-- {} 返回值:{} 耗時:{}ms" + STRING_END, classAndMethod.toString(), target, JSONObject.toJSONStringWithDateFormat(result, dateFormat, SerializerFeature.WriteMapNullValue), timeConsuming);
return result;
} catch (Throwable throwable) {
log.error(throwable.getMessage(), throwable);
}
return null;
}
}
複製代碼
瀏覽器訪問:http://127.0.0.1:8080/index/?content="我是測試內容"
@RestController
@RequestMapping(value = "/index")
public class IndexController {
private final Logger LOGGER = LoggerFactory.getLogger(this.getClass());
/** * http://127.0.0.1:8080/index/?content="我是測試內容" * * @param content * @return */
@Log("首頁IndexController")
@RequestMapping(value="", method= RequestMethod.GET)
public String index(@RequestParam String content) {
LocalDateTime localDateTime = LocalDateTime.now();
LOGGER.trace("請求參數:content:{}", content);
LOGGER.debug("請求參數:content:{}", content);
LOGGER.info("請求參數:content:{}", content);
LOGGER.warn("請求參數:content:{}", content);
LOGGER.error("請求參數:content:{}", content);
return localDateTime + ",content:" + content;
}
}
複製代碼
前面的07E94BA525CF4C97851E4B9E4ABB4890
就是經過logback
的 MDC 作到的
首頁IndexController 開始調用--> io.ymq.logback.controller.IndexController#index 參數:["\"我是測試內容\""]|
- | [07E94BA525CF4C97851E4B9E4ABB4890] | [20171101 10:02:35.589] | [DEBUG] | [DESKTOP-VG43S0C] | [http-nio-8080-exec-1] | [i.y.l.controller.IndexController] | --> 請求參數:content:"我是測試內容"|
- | [07E94BA525CF4C97851E4B9E4ABB4890] | [20171101 10:02:35.589] | [INFO] | [DESKTOP-VG43S0C] | [http-nio-8080-exec-1] | [i.y.l.controller.IndexController] | --> 請求參數:content:"我是測試內容"|
- | [07E94BA525CF4C97851E4B9E4ABB4890] | [20171101 10:02:35.589] | [WARN] | [DESKTOP-VG43S0C] | [http-nio-8080-exec-1] | [i.y.l.controller.IndexController] | --> 請求參數:content:"我是測試內容"|
- | [07E94BA525CF4C97851E4B9E4ABB4890] | [20171101 10:02:35.590] | [ERROR] | [DESKTOP-VG43S0C] | [http-nio-8080-exec-1] | [i.y.l.controller.IndexController] | --> 請求參數:content:"我是測試內容"|
- | [07E94BA525CF4C97851E4B9E4ABB4890] | [20171101 10:02:35.606] | [INFO] | [DESKTOP-VG43S0C] | [http-nio-8080-exec-1] | [i.y.logback.config.commons.LogAspect] | -->
首頁IndexController 調用結束<-- io.ymq.logback.controller.IndexController#index 返回值:"2017-11-01T10:02:35.589,content:\"我是測試內容\"" 耗時:23ms
複製代碼
從上圖能夠看到,日誌輸出內容元素具體以下:
requestUUID:一次請求是惟一的
時間日期:精確到毫秒
日誌級別:ERROR, WARN, INFO, DEBUG or TRACE
主機名:
進程ID:
類路徑:
分隔符: --> 標識實際日誌的開始
日誌內容:
日誌切面的響應:
首頁IndexController 開始調用--> io.ymq.logback.controller.IndexController#index 參數:["\"我是測試內容\""]|
首頁IndexController 調用結束<-- io.ymq.logback.controller.IndexController#index 返回值:"2017-11-01T10:02:35.589,content:\"我是測試內容\"" 耗時:23ms
複製代碼
代碼我已放到 Github ,導入spring-boot-logback
項目
github spring-boot-logback