為了保證服務(wù)的高可用,及時(shí)發(fā)現(xiàn)問題,迅速解決問題,為應(yīng)用添加log是必不可少的。
大同ssl適用于網(wǎng)站、小程序/APP、API接口等需要進(jìn)行數(shù)據(jù)傳輸應(yīng)用場(chǎng)景,ssl證書未來市場(chǎng)廣闊!成為創(chuàng)新互聯(lián)的ssl證書銷售渠道,可以享受市場(chǎng)價(jià)格4-6折優(yōu)惠!如果有意向歡迎電話聯(lián)系或者加微信:028-86922220(備注:SSL證書合作)期待與您的合作!
但是隨著項(xiàng)目的增大,方法增多,每個(gè)方法加單獨(dú)加日志處理會(huì)有很多冗余
那在SpringBoot項(xiàng)目中如何統(tǒng)一的處理Web請(qǐng)求日志?
基本思想:
采用AOP的方式,攔截請(qǐng)求,寫入日志
AOP 是面向切面的編程,就是在運(yùn)行期通過動(dòng)態(tài)代理的方式對(duì)代碼進(jìn)行增強(qiáng)處理
基于AOP不會(huì)破壞原來程序邏輯,因此它可以很好的對(duì)業(yè)務(wù)邏輯的各個(gè)部分進(jìn)行隔離,從而使得業(yè)務(wù)邏輯各部分之間的耦合度降低,提高程序的可重用性,同時(shí)提高了開發(fā)的效率。
1.添加依賴
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
引入spring-boot-starter-web 依賴之后無需在引入相關(guān)的日志依賴,spring-boot-starter-web中已經(jīng)集成了slf4j 的依賴
引入spring-boot-starter-aop 依賴之后,AOP 的功能即是啟動(dòng)狀態(tài)
2.配置
application.properties添加
# AOP spring.aop.auto=true spring.aop.proxy-target-class=true
logback-spring.xml,主要是ControllerRequest那部分
<?xml version="1.0" encoding="UTF-8"?> <configuration scan="true" scanPeriod="60 seconds" debug="false"> <property name="log.path" value="logs" /> <!--0. 日志格式和顏色渲染 --> <!-- 彩色日志依賴的渲染類 --> <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}}"/> <!--1. 輸出到控制臺(tái)--> <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> <!--此日志appender是為開發(fā)使用,只配置最底級(jí)別,控制臺(tái)輸出的日志級(jí)別是大于或等于此級(jí)別的日志信息--> <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <level>info</level> </filter> <encoder> <Pattern>${CONSOLE_LOG_PATTERN}</Pattern> <!-- 設(shè)置字符集 --> <charset>UTF-8</charset> </encoder> </appender> <!--2. 輸出到文檔--> <!-- 2.1 level為 DEBUG 日志,時(shí)間滾動(dòng)輸出 --> <appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <!-- 正在記錄的日志文檔的路徑及文檔名 --> <file>${log.path}/debug/debug.log</file> <!--日志文檔輸出格式--> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern> <charset>UTF-8</charset> <!-- 設(shè)置字符集 --> </encoder> <!-- 日志記錄器的滾動(dòng)策略,按日期,按大小記錄 --> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <!-- 日志歸檔 --> <fileNamePattern>${log.path}/debug/debug-%d{yyyy-MM-dd}.%i.log</fileNamePattern> <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> <maxFileSize>100MB</maxFileSize> </timeBasedFileNamingAndTriggeringPolicy> <!--日志文檔保留天數(shù)--> <maxHistory>15</maxHistory> </rollingPolicy> <!-- 此日志文檔只記錄debug級(jí)別的 --> <filter class="ch.qos.logback.classic.filter.LevelFilter"> <level>debug</level> <onMatch>ACCEPT</onMatch> <onMismatch>DENY</onMismatch> </filter> </appender> <!-- 2.2 level為 INFO 日志,時(shí)間滾動(dòng)輸出 --> <appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <!-- 正在記錄的日志文檔的路徑及文檔名 --> <file>${log.path}/info/info.log</file> <!--日志文檔輸出格式--> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern> <charset>UTF-8</charset> </encoder> <!-- 日志記錄器的滾動(dòng)策略,按日期,按大小記錄 --> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <!-- 每天日志歸檔路徑以及格式 --> <fileNamePattern>${log.path}/info/info-%d{yyyy-MM-dd}.%i.log</fileNamePattern> <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> <maxFileSize>100MB</maxFileSize> </timeBasedFileNamingAndTriggeringPolicy> <!--日志文檔保留天數(shù)--> <maxHistory>15</maxHistory> </rollingPolicy> <!-- 此日志文檔只記錄info級(jí)別的 --> <filter class="ch.qos.logback.classic.filter.LevelFilter"> <level>info</level> <onMatch>ACCEPT</onMatch> <onMismatch>DENY</onMismatch> </filter> </appender> <!-- 2.3 level為 WARN 日志,時(shí)間滾動(dòng)輸出 --> <appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <!-- 正在記錄的日志文檔的路徑及文檔名 --> <file>${log.path}/warn/warn.log</file> <!--日志文檔輸出格式--> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern> <charset>UTF-8</charset> <!-- 此處設(shè)置字符集 --> </encoder> <!-- 日志記錄器的滾動(dòng)策略,按日期,按大小記錄 --> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>${log.path}/warn/warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern> <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> <maxFileSize>100MB</maxFileSize> </timeBasedFileNamingAndTriggeringPolicy> <!--日志文檔保留天數(shù)--> <maxHistory>15</maxHistory> </rollingPolicy> <!-- 此日志文檔只記錄warn級(jí)別的 --> <filter class="ch.qos.logback.classic.filter.LevelFilter"> <level>warn</level> <onMatch>ACCEPT</onMatch> <onMismatch>DENY</onMismatch> </filter> </appender> <!-- 2.4 level為 ERROR 日志,時(shí)間滾動(dòng)輸出 --> <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <!-- 正在記錄的日志文檔的路徑及文檔名 --> <file>${log.path}/error/error.log</file> <!--日志文檔輸出格式--> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern> <charset>UTF-8</charset> <!-- 此處設(shè)置字符集 --> </encoder> <!-- 日志記錄器的滾動(dòng)策略,按日期,按大小記錄 --> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>${log.path}/error/error-%d{yyyy-MM-dd}.%i.log</fileNamePattern> <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> <maxFileSize>100MB</maxFileSize> </timeBasedFileNamingAndTriggeringPolicy> <!--日志文檔保留天數(shù)--> <maxHistory>15</maxHistory> </rollingPolicy> <!-- 此日志文檔只記錄ERROR級(jí)別的 --> <filter class="ch.qos.logback.classic.filter.LevelFilter"> <level>ERROR</level> <onMatch>ACCEPT</onMatch> <onMismatch>DENY</onMismatch> </filter> </appender> <springProfile name="dev"> <root level="info"> <appender-ref ref="CONSOLE" /> <appender-ref ref="DEBUG_FILE" /> <appender-ref ref="INFO_FILE" /> <appender-ref ref="WARN_FILE" /> <appender-ref ref="ERROR_FILE" /> </root> </springProfile> <springProfile name="prod"> <root level="info"> <appender-ref ref="DEBUG_FILE" /> <appender-ref ref="INFO_FILE" /> <appender-ref ref="WARN_FILE" /> <appender-ref ref="ERROR_FILE" /> </root> </springProfile> <appender name="ControllerRequest" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>${log.path}/request/info.log</file> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <FileNamePattern>${log.path}/request/info.log.%d{yyyy-MM-dd}</FileNamePattern> <maxHistory>30</maxHistory> </rollingPolicy> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern> </encoder> </appender> <logger name="ControllerRequest" level="DEBUG" additivity="false"> <appender-ref ref="ControllerRequest"/> </logger> </configuration>
3..實(shí)現(xiàn)
實(shí)現(xiàn)切面的注解
(1)類注解
A. @Aspect 將一個(gè)java類定義為切面類
B. @order(i) 標(biāo)記切面類的處理優(yōu)先級(jí),i值越小,優(yōu)先級(jí)別越高??梢宰⒔忸?也能注解到方法上
(2)方法注解
A. @Pointcut 定義一個(gè)切入點(diǎn),可以是一個(gè)表達(dá)式
execution表達(dá)式,eg:
任意公共方法的執(zhí)行 execution(public * *(..)) 任何一個(gè)以“set”開始的方法的執(zhí)行 execution(* set*(..)) 定義在controller包里的任意方法的執(zhí)行 execution(public * com.example.demo.controller.*(..)) 定義在controller包里的任意方法的執(zhí)行 execution(public * com.example.demo.controller.*.*(..)) 定義在controller包和所有子包里的任意類的任意方法的執(zhí)行 execution(public * com.example.demo.controller..*.*(..))
B. 實(shí)現(xiàn)在不同的位置切入
C.@order(i) 標(biāo)記切點(diǎn)的優(yōu)先級(jí),i越小,優(yōu)先級(jí)越高
@order(i)注解說明
注解類,i值是,值越小,優(yōu)先級(jí)越高
注解方法,分兩種情況
注解的是 @Before 是i值越小,優(yōu)先級(jí)越高
注解的是 @After或@AfterReturning 中,i值越大,優(yōu)先級(jí)越高
具體實(shí)現(xiàn)
package com.example.demo.configure; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.aspectj.lang.reflect.MethodSignature; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.DefaultParameterNameDiscoverer; import org.springframework.core.ParameterNameDiscoverer; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; import java.lang.reflect.Method; import java.net.InetAddress; import java.util.HashMap; import java.util.Map; @Aspect @Component public class WebRequestLogAspect { private final Logger loggerController = LoggerFactory.getLogger("ControllerRequest"); private final Logger logger = LoggerFactory.getLogger(WebRequestLogAspect.class); ThreadLocal<Long> startTime = new ThreadLocal<>(); ThreadLocal<String> beanName = new ThreadLocal<>(); ThreadLocal<String> user = new ThreadLocal<>(); ThreadLocal<String> methodName = new ThreadLocal<>(); ThreadLocal<String> params = new ThreadLocal<>(); ThreadLocal<String> remoteAddr = new ThreadLocal<>(); ThreadLocal<String> uri = new ThreadLocal<>(); private static Map<String, Object> getFieldsName(ProceedingJoinPoint joinPoint) { // 參數(shù)值 Object[] args = joinPoint.getArgs(); ParameterNameDiscoverer pnd = new DefaultParameterNameDiscoverer(); MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); String[] parameterNames = pnd.getParameterNames(method); Map<String, Object> paramMap = new HashMap<>(32); for (int i = 0; i < parameterNames.length; i++) { paramMap.put(parameterNames[i], args[i] + "(" + args[i].getClass().getSimpleName() + ")"); } return paramMap; } @Pointcut("execution(public * com.example.demo.controller..*.*(..))") public void webRequestLog() { } /** * 前置通知,方法調(diào)用前被調(diào)用 * @param joinPoint */ @Before("webRequestLog()") public void doBefore(JoinPoint joinPoint) { try { startTime.set(System.currentTimeMillis()); // 接收到請(qǐng)求,記錄請(qǐng)求內(nèi)容 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); beanName.set(joinPoint.getSignature().getDeclaringTypeName()); methodName.set(joinPoint.getSignature().getName()); uri.set(request.getRequestURI()); remoteAddr.set(getIpAddr(request)); user.set((String) request.getSession().getAttribute("user")); } catch (Exception e) { logger.error("***操作請(qǐng)求日志記錄失敗doBefore()***", e); } } /** * 環(huán)繞通知,環(huán)繞增強(qiáng),相當(dāng)于MethodInterceptor * @param thisJoinPoint */ @Around("webRequestLog()") public Object proceed(ProceedingJoinPoint thisJoinPoint) throws Throwable { Object object = thisJoinPoint.proceed(); Map<String, Object> fieldsName = getFieldsName(thisJoinPoint); params.set(fieldsName.toString()); return object; } /** * 處理完請(qǐng)求返回內(nèi)容 * @param result */ @AfterReturning(returning = "result", pointcut = "webRequestLog()") public void doAfterReturning(Object result) { try { long requestTime = (System.currentTimeMillis() - startTime.get()) / 1000; loggerController.info("請(qǐng)求耗時(shí):" + requestTime + ", uri=" + uri.get() + "; beanName=" + beanName.get() + "; remoteAddr=" + remoteAddr.get() + "; user=" + user.get() + "; methodName=" + methodName.get() + "; params=" + params.get() + "; RESPONSE : " + result); } catch (Exception e) { logger.error("***操作請(qǐng)求日志記錄失敗doAfterReturning()***", e); } } /** * 獲取登錄用戶遠(yuǎn)程主機(jī)ip地址 * * @param request * @return */ private String getIpAddr(HttpServletRequest request) { String ip = request.getHeader("x-forwarded-for"); if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("Proxy-Client-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("WL-Proxy-Client-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getRemoteAddr(); if (ip.equals("127.0.0.1") || ip.equals("0:0:0:0:0:0:0:1")) { //根據(jù)網(wǎng)卡取本機(jī)配置的IP InetAddress inet = null; try { inet = InetAddress.getLocalHost(); } catch (Exception e) { e.printStackTrace(); } ip = inet.getHostAddress(); } } // 多個(gè)代理的情況,第一個(gè)IP為客戶端真實(shí)IP,多個(gè)IP按照','分割 if (ip != null && ip.length() > 15) { if (ip.indexOf(",") > 0) { ip = ip.substring(0, ip.indexOf(",")); } } return ip; } }
4.測(cè)試類
package com.example.demo.controller; import com.alibaba.fastjson.JSONObject; import com.example.demo.dao.UserRepository; import com.example.demo.domain.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.List; import java.util.Map; @RestController public class Demo { @RequestMapping (value = "test1") public String test1(@RequestParam(defaultValue = "0") Integer id,@RequestParam(defaultValue = "0")String name){ return id+name; } @RequestMapping("hello") public String hello() { return "Hello World!"; } @PostMapping("/updateStatus") public Object updateStatus(@RequestBody JSONObject jsonParam) { return jsonParam; } }
輸出到logs/request/info.log內(nèi)容
2019-09-11 13:31:45.729 [http-nio-8080-exec-4] INFO ControllerRequest - 請(qǐng)求耗時(shí):0, uri=/test1; beanName=com.example.demo.controller.Demo; remoteAddr=172.27.0.17; user=null; methodName=test1; params={name=abcdef(String), id=123(Integer)}; RESPONSE : 123abcdef 2019-09-11 13:32:16.692 [http-nio-8080-exec-5] INFO ControllerRequest - 請(qǐng)求耗時(shí):0, uri=/updateStatus; beanName=com.example.demo.controller.Demo; remoteAddr=172.27.0.17; user=null; methodName=updateStatus; params={jsonParam={"id":"17","type":3,"status":2}(JSONObject)}; RESPONSE : {"id":"17","type":3,"status":2} 2019-09-11 13:33:32.584 [http-nio-8080-exec-7] INFO ControllerRequest - 請(qǐng)求耗時(shí):0, uri=/hello; beanName=com.example.demo.controller.Demo; remoteAddr=172.27.0.17; user=null; methodName=hello; params={}; RESPONSE : Hello World!
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持創(chuàng)新互聯(lián)。
分享名稱:springboot如何使用AOP統(tǒng)一處理web請(qǐng)求
轉(zhuǎn)載來于:http://muchs.cn/article46/pihpeg.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站制作、自適應(yīng)網(wǎng)站、網(wǎng)頁(yè)設(shè)計(jì)公司、定制網(wǎng)站、品牌網(wǎng)站制作、ChatGPT
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)