在開發以及調試過程中,程序員對日誌的需求是非常大的,出了什麼問題,都要通過日誌去進行排查,但是如果日誌不清或者雜亂無章,則不利於維護
這邊就比較詳細的列舉幾種類型的日誌,供大家參考
首先明白logback日誌是Spring Boot自帶的,不需要引入額外的包
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-access</artifactId>
<version>${logback.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>${logback.version}</version>
</dependency>
點進pom裏的核心依賴,就能看見上面幾個,是由Spring Boot自動依賴配置好的,我們只要直接使用就好了
比較簡單的是直接在application的配置文件裏 寫參數配置就行了,他提供了日誌級別,日誌輸出路徑等,也能滿足基本的日誌輸出
我們這通過xml文件進行配置 logback-spring.xml
這樣就能直接引用到xml了,但是為什麼能引用到了
就是在logback裏有個默認的機制,內部會有幾種標準的文件格式,在LogbackLoggingSystem裏標註了
@Override
protected String[] getStandardConfigLocations() {
return new String[] { "logback-test.groovy", "logback-test.xml", "logback.groovy",
"logback.xml" };
}
所以最為標準的為這裏面的四種文件格式,但是如果項目中沒有,他還提供了擴展文件格式 就是在後面拼上-spring,例如logback.xml 擴展為logback-spring.xml
ok
下面看下xml裏面的內容:
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
<!--定義日誌文件的存儲地址 可以在LogBack 的配置中使用相對路徑-->
<property name="LOG_HOME" value="logs" />
<!-- 彩色日誌 -->
<!-- 彩色日誌依賴的渲染類 -->
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" />
<conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter" />
<conversionRule conversionWord="wEx"
converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter" />
<!-- 彩色日誌格式 -->
<property name="CONSOLE_LOG_PATTERN"
value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}" />
<!-- Console 輸出設置 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
<charset>utf8</charset>
</encoder>
</appender>
<!-- 按照每天生成日誌文件 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日誌文件輸出的文件名-->
<FileNamePattern>${LOG_HOME}/category-server-log.%d{yyyy-MM-dd}.log</FileNamePattern>
<!--日誌文件保留天數-->
<MaxHistory>30</MaxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化輸出:%d表示日期,%thread表示線程名,%-5level:級別從左顯示5個字符寬度%msg:日誌消息,%n是換行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
<!--日誌文件最大的大小
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<MaxFileSize>10MB</MaxFileSize>
</triggeringPolicy>-->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>WARN</level>
<onMatch>DENY</onMatch>
<onMismatch>NEUTRAL</onMismatch>
</filter>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>DENY</onMatch>
<onMismatch>NEUTRAL</onMismatch>
</filter>
</appender>
<!-- 出錯日誌 appender -->
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 按天回滾 daily -->
<!-- log.dir 在maven profile裏配置 -->
<FileNamePattern>${LOG_HOME}/category-server-error-log.%d{yyyy-MM-dd}.log</FileNamePattern>
<!-- 日誌最大的歷史 60天 -->
<maxHistory>60</maxHistory>
</rollingPolicy>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>WARN</level>
</filter>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
</appender>
<!-- 自己打印的日誌文件,用於記錄重要日誌信息 -->
<appender name="MY_INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!--日誌文件輸出的文件名-->
<FileNamePattern>${LOG_HOME}/category-server-myinfo-log.%d{yyyy-MM-dd}.%i.log</FileNamePattern>
<!--日誌文件保留天數-->
<MaxHistory>15</MaxHistory>
<!--日誌文件最大的大小-->
<MaxFileSize>10MB</MaxFileSize>
</rollingPolicy>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>DEBUG</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化輸出:%d表示日期,%thread表示線程名,%-5level:級別從左顯示5個字符寬度%msg:日誌消息,%n是換行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<logger name="my_info" additivity="true">
<appender-ref ref="MY_INFO_FILE"/>
</logger>
<!--myibatis log configure-->
<logger name="com.example.demo" level="TRACE"/>
<logger name="java.sql.Connection" level="DEBUG"/>
<logger name="java.sql.Statement" level="DEBUG"/>
<logger name="java.sql.PreparedStatement" level="DEBUG"/>
<!-- 日誌輸出級別 -->
<root level="INFO">
<appender-ref ref="CONSOLE" />
<appender-ref ref="FILE" />
<appender-ref ref="ERROR_FILE" />
</root>
</configuration>
這裏一共有四塊內容,第一是console的日誌輸出,第二是系統運行日誌,第三是警告以上的日誌輸出(基本上是程序出錯日誌),第四種是自定義日誌
每一塊日誌由一個appender標籤引入
CONSOLE是控制枱日誌輸出,只要規定個格式就行了
FILE是系統運行日誌,系統的所有運行信息都會保留,正常我們會把這部分信息保存在硬盤日誌文件中,按天按文件大小保存,因為這個內容實在是比較多
ERROR_FILE是WARN級別以上的日誌,這塊是開發人員和運維人員最多關注的,因為基本上所有的bug都會在這個裏面體現
MY_INFO_FILE是自定義日誌,想定義自己的日誌文件,記錄一些重要的信息
這裏的日誌都是以文件的形式保存在本地,當然像WARN級別以上日誌可以異步保存到數據庫
日誌文件定義好後,接下來就要開始定義業務邏輯了
在針對一些異常日誌,我們想盡可能完整準確的拋出異常,一眼就能知道是什麼問題,這裏我們就需要自定義異常,最多的就是像空指針,數組越界等常見異常
定義基礎異常類BaseException繼承他的父類RuntimeException
public class BaseException extends RuntimeException {
private static final long serialVersionUID = 1L;
public BaseException() {
super();
// TODO Auto-generated constructor stub
}
public BaseException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
// TODO Auto-generated constructor stub
}
public BaseException(String message, Throwable cause) {
super(message, cause);
// TODO Auto-generated constructor stub
}
public BaseException(String message) {
super(message);
// TODO Auto-generated constructor stub
}
public BaseException(Throwable cause) {
super(cause);
// TODO Auto-generated constructor stub
}
}
然後全局異常處理類:GlobalExceptionHandler
@CrossOrigin
@RestControllerAdvice
public class GlobalExceptionHandler{
private static Logger LOGGER = LoggerFactory.getLogger(GlobalExceptionHandler.class);
private static final String APPLICATION_JSON = "application/json";
private static final String UTF_8 = "UTF-8";
/**
* BaseException 處理類
* @Title: HandleBaseException
* @Description: TODO
* @param @param e
* @param @return
* @return ResponseMsg
* @throws
*/
@ExceptionHandler(BaseException.class)
@ResponseBody
public ResponseMsg HandleBaseException(RuntimeException e){
//只能輸出捕獲到的異常,未捕獲到的異常不輸出到日誌,或者通過aop攔截器攔截所有方法
LOGGER.error(getExceptionDetail(e));
//返回失敗信息
Route route = new Route();
ResponseMsg responseMsg = new ResponseMsg(route,ReturnMsgEnum.INTERNAL_ERROR.getCode(),
ReturnMsgEnum.INTERNAL_ERROR.getMsg(), "");
return responseMsg;
}
@ExceptionHandler(GlobalException.class)
@ResponseBody
public ResponseMsg HandleGlobalException(Exception e){
//只能輸出捕獲到的異常,未捕獲到的異常不輸出到日誌,或者通過aop攔截器攔截所有方法
LOGGER.error(getExceptionDetail(e));
//返回失敗信息
Route route = new Route();
ResponseMsg responseMsg = new ResponseMsg(route,ReturnMsgEnum.INTERNAL_ERROR.getCode(),
ReturnMsgEnum.INTERNAL_ERROR.getMsg(), "系統未捕獲該異常");
return responseMsg;
}
public String getExceptionDetail(Exception e) {
StringBuffer stringBuffer = new StringBuffer(e.toString() + "\n");
StackTraceElement[] messages = e.getStackTrace();
int length = messages.length;
for (int i = 0; i < length; i++) {
stringBuffer.append("\t"+messages[i].toString()+"\n");
}
return stringBuffer.toString();
}
}
@RestControllerAdvice:表明他是一個Controller 並且是異常攔截的統一處理類
定義針對自定義異常的處理方法:用@ExceptionHandler(BaseException.class)註解標註
BaseException就是剛才的自定義異常
之後所有拋出的BaseException都會由他處理
自定義異常我們都能輕鬆捕獲到了,並且輸出到日誌裏了
如果有些異常我們沒有捕獲到,我們就可以定義一個切面,讓所有方法都經過這個切面處理
/**
* 處理未捕獲到的異常
* @ClassName: SpringAOP
* @author Mr.Chengjq
* @date 2018年10月17日
* @Description: TODO
*/
@Aspect
@Configuration
public class SpringAOP {
private static final Logger logger = LoggerFactory.getLogger(SpringAOP.class);
/**
* 定義切點Pointcut
* 第一個*號:表示返回類型, *號表示所有的類型
* 第二個*號:表示類名,*號表示所有的類
* 第三個*號:表示方法名,*號表示所有的方法
* 後面括弧裏面表示方法的參數,兩個句點表示任何參數
*/
@Pointcut("execution(* com.example.demo..*.*(..))")
public void executionService() {
}
/**
* 方法調用之前調用
* @param joinPoint
*/
@Before(value = "executionService()")
public void doBefore(JoinPoint joinPoint){
//添加日誌打印
String requestId = String.valueOf(UUID.randomUUID());
MDC.put("requestId",requestId);
logger.info("=====>@Before:請求參數為:{}",Arrays.toString(joinPoint.getArgs()));
}
/**
* 方法之後調用
* @param joinPoint
* @param returnValue 方法返回值
*/
@AfterReturning(pointcut = "executionService()",returning="returnValue")
public void doAfterReturning(JoinPoint joinPoint,Object returnValue){
logger.info("=====>@AfterReturning:響應參數為:{}",returnValue);
// 處理完請求,返回內容
MDC.clear();
}
/**
* 統計方法執行耗時Around環繞通知
* @param joinPoint
* @return
*/
@Around("executionService()")
public Object timeAround(ProceedingJoinPoint joinPoint) throws Throwable{
//獲取開始執行的時間
long startTime = System.currentTimeMillis();
// 定義返回對象、得到方法需要的參數
Object obj = null;
//Object[] args = joinPoint.getArgs();
try {
obj = joinPoint.proceed();
} catch (Throwable e) {
// TODO: handle exception
logger.error(getExceptionDetail(e));
throw new GlobalException();
}
// 獲取執行結束的時間
long endTime = System.currentTimeMillis();
//MethodSignature signature = (MethodSignature) joinPoint.getSignature();
//String methodName = signature.getDeclaringTypeName() + "." + signature.getName();
// 打印耗時的信息
logger.info("=====>處理本次請求共耗時:{} ms",endTime-startTime);
return obj;
}
public String getExceptionDetail(Throwable e) {
StringBuffer stringBuffer = new StringBuffer(e.toString() + "\n");
StackTraceElement[] messages = e.getStackTrace();
int length = messages.length;
for (int i = 0; i < length; i++) {
stringBuffer.append("\t"+messages[i].toString()+"\n");
}
return stringBuffer.toString();
}
}
這個切面裏未捕獲到的異常也全部做特定處理