相信各位同道在寫代碼的時候,肯定會寫一些日誌列印,因為這對往後的營運而言,至關重要的。
那麼我們請求一個restfull介面的時候,哪些資訊是應該被日誌記錄的呢?
以下做了一個基本的簡單例子,這裡只是樣本說明基本常規實現記錄的資訊,根據項目的真實情況選用 :
1 . Http請求攔截器(Filter) : 從 HttpServletRequest擷取基本的請求資訊
import name.ealen.util.HttpUtil;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;/** * Created by EalenXie on 2018/9/7 15:56. * Http請求攔截器,日誌列印請求基本相關資訊 */@Configurationpublic 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(); System.out.println(); log.info("Http Request Request-Id : " + requestId); log.info("Http Request Information : {\"URI\":\"" + request.getRequestURL() + "\",\"RequestMethod\":\"" + request.getMethod() + "\",\"ClientIp\":\"" + HttpUtil.getIpAddress(request) + "\",\"Content-Type\":\"" + request.getContentType() + "\"}"); chain.doFilter(request, response); } @Override public void destroy() { log.info("ControllerFilter destroy"); } }; }}
2 . Controller的攔截AOP : 擷取 請求的對象,請求參數,返回資料,請求返回狀態,內部方法耗時。
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.http.HttpStatus;import org.springframework.http.ResponseEntity;import org.springframework.stereotype.Component;import javax.annotation.Resource;/** * Created by EalenXie on 2018/9/7 14:19. * AOP列印日誌 : 請求的對象,請求參數,返回資料,請求狀態,內部方法耗時 */@Aspect@Componentpublic class ControllerInterceptor { private static final Logger log = LoggerFactory.getLogger(ControllerInterceptor.class); @Resource private Environment environment; @Around(value = "execution (* name.ealen.web.*.*(..))") public Object processApiFacade(ProceedingJoinPoint pjp) { String appName; try { appName = environment.getProperty("spring.application.name").toUpperCase(); } catch (Exception e) { appName = "UNNAMED"; } long startTime = System.currentTimeMillis(); String name = pjp.getTarget().getClass().getSimpleName(); String method = pjp.getSignature().getName(); Object result = null; HttpStatus status = null; try { result = pjp.proceed(); log.info("RequestTarget : " + appName + "." + name + "." + method); log.info("RequestParam : " + JSON.toJSON(pjp.getArgs())); if (result instanceof ResponseEntity) { status = ((ResponseEntity) result).getStatusCode(); } else { status = HttpStatus.OK; } } catch (Throwable throwable) { status = HttpStatus.INTERNAL_SERVER_ERROR; result = new ResponseEntity<>("{\"Internal Server Error\" : \"" + throwable.getMessage() + "\"}", status); throwable.printStackTrace(); } finally { log.info("ResponseEntity : {" + "\"HttpStatus\":\"" + status.toString() + "\"" + ",\"ResponseBody\": " + JSON.toJSON(result) + "}"); log.info("Internal Method Cost Time: {}ms", System.currentTimeMillis() - startTime); } return result; }}
3 . 提供一個簡單的restfull介面 :
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. */@RestControllerpublic 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進行基本測試 :
5 . 控制台可以看到基本效果 :
以上只是關於Controller應該記錄日誌的一個簡單的例子,完整代碼可見 github.com/EalenXie/springboot-controller-logger
感謝各位提出意見和支援。