相信各位同道在寫代碼的時候,確定會寫一些日誌打印,由於這對日後的運維而言,相當重要的。java
那麼咱們請求一個restfull接口的時候,哪些信息是應該被日誌記錄的呢?git
如下作了一個基本的簡單例子,這裏只是示例說明基本常規實現記錄的信息,根據項目的真實狀況選用 : github
1 . Http請求攔截器(Filter) : 從 HttpServletRequest獲取基本的請求信息web
package name.ealen.config; import name.ealen.util.CommonUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.UUID; /** * Created by EalenXie on 2018/9/7 15:56. * Http請求攔截器,日誌打印請求相關信息 */ @Configuration public class FilterConfiguration { private static final Logger log = LoggerFactory.getLogger(FilterConfig.class); @Bean @Order(Integer.MIN_VALUE) @Qualifier("filterRegistration") public FilterRegistrationBean filterRegistration() { FilterRegistrationBean<Filter> registration = new FilterRegistrationBean<>(); registration.setFilter(controllerFilter()); registration.addUrlPatterns("/*"); return registration; } private Filter controllerFilter() { return new Filter() { @Override public void init(FilterConfig filterConfig) { log.info("ControllerFilter init Success"); } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) servletRequest; HttpServletResponse response = (HttpServletResponse) servletResponse; String requestId = request.getHeader("Request-Id"); if (requestId == null) requestId = request.getRequestedSessionId(); if (requestId == null) requestId = UUID.randomUUID().toString(); if (!"OPTIONS".equalsIgnoreCase(request.getMethod())) { System.out.println(); log.info("Http Request Request-Id : " + requestId); log.info("Http Request Information : {\"URI\":\"" + request.getRequestURL() + "\",\"RequestMethod\":\"" + request.getMethod() + "\",\"ClientIp\":\"" + CommonUtil.getIpAddress(request) + "\",\"Content-Type\":\"" + request.getContentType() + "\",\"UserAgent\":\"" + request.getHeader("user-agent") + "\"}"); } chain.doFilter(request, response); } @Override public void destroy() { log.info("ControllerFilter destroy"); } }; } }
2 . Controller的攔截AOP : 獲取 請求的對象,請求參數,返回數據,請求返回狀態,內部方法耗時。spring
package name.ealen.config; import com.alibaba.fastjson.JSON; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.env.Environment; import org.springframework.stereotype.Component; import javax.annotation.Resource; /** * Created by EalenXie on 2018/9/7 14:19. * AOP打印日誌 : 請求的對象,請求參數,返回數據,請求狀態,內部方法耗時 */ @Aspect @Component public class ControllerInterceptor { private static final Logger log = LoggerFactory.getLogger(ControllerInterceptor.class); @Resource private Environment environment; private String getAppName() { try { return environment.getProperty("spring.application.name"); } catch (Exception ignore) { return "unnamed"; } } /** * 注意 : pjp.proceed()執行的異常請務必拋出,交由ControllerAdvice捕捉到並處理 */ @Around(value = "execution (* name.ealen.web.*.*(..))") public Object processApiFacade(ProceedingJoinPoint pjp) throws Throwable { long startTime = System.currentTimeMillis(); String name = pjp.getTarget().getClass().getSimpleName(); String method = pjp.getSignature().getName(); Object result; try { result = pjp.proceed(); log.info("RequestTarget : " + getAppName() + "." + name + "." + method); Object[] requestParams = pjp.getArgs(); if (requestParams.length > 0) { //日誌打印請求參數 try { log.info("RequestParam : {}", JSON.toJSON(requestParams)); } catch (Exception e) { for (Object param : requestParams) { try { log.info("RequestParam : {}", JSON.toJSON(param)); } catch (Exception ig) { log.info("RequestParam : {}", param.toString()); } } } } } finally { log.info("Internal Method Cost Time: {}ms", System.currentTimeMillis() - startTime); } return result; } }
3 . 全局的異常處理返回Advice : json
package name.ealen.config; import com.alibaba.fastjson.JSON; import name.ealen.util.CommonUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.HttpServerErrorException; import javax.servlet.http.HttpServletRequest; import java.util.HashMap; import java.util.Map; /** * Created by EalenXie on 2018/11/8 16:25. * 全局異常、錯誤返回處理 */ @ControllerAdvice public class ControllerExceptionListener { private final Logger log = LoggerFactory.getLogger(ControllerExceptionListener.class); @ExceptionHandler(value = Throwable.class) public ResponseEntity Throwable(Throwable throwable, HttpServletRequest request) { Map<String, String> resultMap = getThrowable(throwable); if (request != null) { Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code"); resultMap.put("Requester-Ip", CommonUtil.getIpAddress(request)); resultMap.put("Requester-Agent", request.getHeader("user-agent")); if (statusCode != null) { new ResponseEntity<>(JSON.toJSON(resultMap).toString(), HttpStatus.valueOf(statusCode)); } } return new ResponseEntity<>(JSON.toJSON(resultMap).toString(), HttpStatus.INTERNAL_SERVER_ERROR); } @ExceptionHandler(value = HttpServerErrorException.class) public ResponseEntity HttpServerErrorException(HttpServerErrorException serverError) { Map<String, String> resultMap = getThrowable(serverError); HttpStatus status = serverError.getStatusCode(); resultMap.put("responseBody", "" + serverError.getResponseBodyAsString()); resultMap.put("statusCode", "" + status.toString()); resultMap.put("statusText", "" + serverError.getStatusText()); resultMap.put("statusReasonPhrase", "" + status.getReasonPhrase()); return new ResponseEntity<>(JSON.toJSON(resultMap).toString(), status); } @ExceptionHandler(value = HttpClientErrorException.class) public ResponseEntity HttpClientErrorException(HttpClientErrorException clientError) { Map<String, String> resultMap = getThrowable(clientError); HttpStatus status = clientError.getStatusCode(); resultMap.put("responseBody", "" + clientError.getResponseBodyAsString()); resultMap.put("statusCode", "" + clientError.getStatusCode().toString()); resultMap.put("statusText", "" + clientError.getStatusText()); resultMap.put("statusReasonPhrase", "" + status.getReasonPhrase()); return new ResponseEntity<>(JSON.toJSON(resultMap).toString(), status); } /** * 公共異常信息 */ private Map<String, String> getThrowable(Throwable throwable) { Map<String, String> resultMap = new HashMap<>(); resultMap.put("throwable", "" + throwable); resultMap.put("throwableTime", "" + CommonUtil.getCurrentDateTime()); resultMap.put("message", "" + throwable.getMessage()); resultMap.put("localizedMessage", "" + throwable.getLocalizedMessage()); log.error("Exception : {}", JSON.toJSON(resultMap)); throwable.printStackTrace(); return resultMap; } }
4 . 提供一個簡單的restfull接口 : springboot
package name.ealen.web; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * Created by EalenXie on 2018/9/7 14:24. */ @RestController public class SayHelloController { @RequestMapping("/sayHello") public String sayHello() { return "hello world"; } @RequestMapping("/say") public ResponseEntity<?> say(@RequestBody Object o) { return new ResponseEntity<>(o, HttpStatus.OK); } }
4 . 使用Postman進行基本測試 : restful
5 . 控制檯能夠看到基本效果 : app
以上只是關於Controller應該記錄日誌的一個簡單的例子,完整代碼可見 https://github.com/EalenXie/springboot-controller-logger運維
感謝各位提出意見和支持。