先來看下日志對於一個接口服務的作用:
上面的這幾點需求,如果接口服務使用的是一些高級產品比如dubbo,其實它已經實現了大部分功能,不需要人為的去全部處理功能。
[DUBBO] [2016-08-27 14:47:06] 10.10.50.20:64948 -> 10.10.50.20:20960 - ProductFacadeService getDataDictionaryByType(DataDictionaryParamInfo) [{"code":0,"type":1}], dubbo version: 2.8.1, current host: 10.10.50.20
上面的日志我們可以看到如下有用的信息:
1:調用方IP以及端口信息:10.10.50.20:64948
2:服務端的IP以及端口信息:10.10.50.20:20960
3:調用時間:2016-08-27 14:47:06
4:調用的接口方法:ProductFacadeService getDataDictionaryByType(DataDictionaryParamInfo)
5:調用的接口方法參數:[{"code":0,"type":1}]
其實dubbo自己提供的日志功能已經非常強了,大多數情況下已經夠用,但如果想有一些更加強的功能就需要自己想想辦法了:
如果你的服務接口不是dubbo,比如是基於spring mvc 實現的rest api接口,那麼就沒有上面dubbo幫你做的那些好用的日志功能了。但是spring mvc實現上面的日志需求方法也是很多的,典型的例子可以借助HandlerInterceptor這類攔截器,只需要實現這個接口,然後在實現類中增加日志功能即可:我們可以通過handler這個參數來獲取你想要的所有數據,另外還有request以及response兩個信息豐富的對象供你使用。
public interface HandlerInterceptor { boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception; void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception; void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception; }
還有一種是自己實現一個AOP功能去解決我們的需求問題。AOP不是這篇的重點,我貼一張圖用來簡單展示AOP的作用(上面提到的HandlerInterceptor 也屬於AOP的具體應用)。
將某些非業務的功能通過攔截的形式注入到流程中,而非緊密耦合在業務邏輯中,比如下面的記錄方法參數的代碼就是緊耦合,不推薦:
public ValueResult<ApplyIsvRoleInfo> queryUserApplyIsvInfo(String appKey, String accessToken) { log.info("receive queryUserApplyIsvInfo! and the params appKey: [" + appKey + "], accessToken:[" + accessToken + "]"); ...... do something }
AOP我這裡使用aspectj,當然也可以使用spring自帶的。
@Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface MethodLog { /** * 是否啟動日志功能 * @return */ boolean enable() default true; /** * 是否記錄返回值 * 列表數據不推薦記錄 * @return */ boolean isLogResult() default false; /** * 是否記錄方法的參數名稱以及值 * @return */ boolean isLogParameter() default true; /** * 是否記錄執行時間 * @return */ boolean isLogElapsed() default true; }
a:計算時間,這裡采用google提供的Stopwatch,這個東西對於從.NET轉JAVA的來講太親切了
Stopwatch sw=Stopwatch.createStarted(); Object result = joinPoint.proceed(); sw.stop(); long elapsed=sw.elapsed(TimeUnit.NANOSECONDS);
b:記錄參數內容,對於object對象,我們將其序列化成json,可以利用jackson完成,其它的復雜點的就是通過反射來完成參數以及值。記錄日志根據注解的配置來判斷應該記錄哪些日志項。
@Aspect public class ServicesLoggerInterceptor { @Pointcut("execution(* com.chanjet.csp.product.service.service.impl.*ServiceImpl*.*(..))") public void pointCut() { } @Around(value = "pointCut()") public Object around(ProceedingJoinPoint joinPoint) throws Throwable { Stopwatch sw=Stopwatch.createStarted(); Object result = joinPoint.proceed(); sw.stop(); long elapsed=sw.elapsed(TimeUnit.NANOSECONDS); MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); Method targetMethod = methodSignature.getMethod(); MethodLog methodLog= targetMethod.getAnnotation(MethodLog.class); if(null!=methodLog&&methodLog.enable()){ StringBuilder logLine = new StringBuilder(joinPoint.getSignature().getName()); if(methodLog.isLogParameter()) { Object[] paramValues = joinPoint.getArgs(); CodeSignature codeSignature= ((CodeSignature) joinPoint.getSignature()); String[] paramNames = codeSignature.getParameterNames(); logLine.append("("); if (paramNames.length != 0) { AspectLogUtils.logParamValues(logLine, paramNames, paramValues); } logLine.append(") - started"); } if(methodLog.isLogResult()) { logLine.append(" Return Value : "); logLine.append(AspectLogUtils.toString(result)); } if(methodLog.isLogElapsed()) { logLine.append(" elapsed nanoseconds:" + elapsed); } AspectLogUtils.getLogger().info(logLine.toString()); } return result; } }
c:創建一個日志的工具類,用來實現具體的日志記錄。
public final class AspectLogUtils { private static Logger logger = LoggerFactory.getLogger(AspectLogUtils.class); private AspectLogUtils() { } public static Logger getLogger() { return logger; } public static void logParamValues(StringBuilder logLine, String[] paramNames, Object[] paramValues) { for (int i = 0; i < paramValues.length; i++) { logLine.append(paramNames[i]).append("=").append(toString(paramValues[i])); if (i < paramValues.length - 1) { logLine.append(", "); } } } public static String toString(Object object) { return JsonUtils.toJsonString(object); } }
<bean class="ServicesLoggerInterceptor"></bean> <aop:aspectj-autoproxy proxy-target-class="true"/>
INFO getDataDictionaryByType(dataDictionaryParamInfo={"type":1,"code":0}) - started elapsed nanoseconds:11491670