首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

微服務接口日誌最佳實踐:從 Dubbo 內置到 Spring MVC AOP 自定義全鏈路追蹤

     編輯:Java 綜合教學

接口日誌四大核心價值

  • 狀態監控: 聚合接口執行鏈路追蹤,快速定位卡點
  • 安全審計: 記錄調用方身份、參數,防範惡意調用
  • 性能分析: 統計耗時,發現瓶頸
  • 異常診斷: 線上無 Debug 環境下,堆棧信息是唯一線索

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

自動包含:雙向 IP/端口、調用時間、接口方法、入參、版本信息。

Dubbo 缺口

  • 無執行結束時間 → 無法計算精確耗時
  • 業務執行鏈路日誌需手動埋點

Spring MVC 方案:HandlerInterceptor 實現

public interface HandlerInterceptor {
    boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler);
    void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView);
    void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex);
}
  • preHandle:記錄入參、開始時間
  • postHandle:記錄返回值
  • afterCompletion:計算耗時、記錄異常

進階方案:AOP + 自定義註解(推薦)

1. 定義日誌註解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MethodLog {
    boolean enable() default true;
    boolean isLogResult() default false;    // 列表查詢建議關閉
    boolean isLogParameter() default true;
    boolean isLogElapsed() default true;
}

2. 切面實現

@Aspect
public class ServicesLoggerInterceptor {
    @Pointcut("execution(* com.chanjet.csp.product.service.service.impl.*ServiceImpl*.*(..))")
    public void pointCut() {}

    @Around("pointCut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        Stopwatch sw = Stopwatch.createStarted(); // Guava 高精度計時
        Object result = joinPoint.proceed();
        sw.stop();
        long elapsed = sw.elapsed(TimeUnit.NANOSECONDS);

        MethodSignature ms = (MethodSignature) joinPoint.getSignature();
        MethodLog logAnn = ms.getMethod().getAnnotation(MethodLog.class);

        if (logAnn != null && logAnn.enable()) {
            StringBuilder log = new StringBuilder(ms.getName());
            if (logAnn.isLogParameter()) {
                log.append("(");
                Object[] args = joinPoint.getArgs();
                String[] paramNames = ((CodeSignature) joinPoint.getSignature()).getParameterNames();
                AspectLogUtils.logParamValues(log, paramNames, args);
                log.append(") - started");
            }
            if (logAnn.isLogResult()) {
                log.append(" Return: ").append(AspectLogUtils.toString(result));
            }
            if (logAnn.isLogElapsed()) {
                log.append(" elapsed ns: ").append(elapsed);
            }
            AspectLogUtils.getLogger().info(log.toString());
        }
        return result;
    }
}

3. 工具類

public final class AspectLogUtils {
    private static Logger logger = LoggerFactory.getLogger(AspectLogUtils.class);
    public static void logParamValues(StringBuilder sb, String[] names, Object[] values) {
        for (int i = 0; i < values.length; i++) {
            sb.append(names[i]).append("=").append(toString(values[i]));
            if (i < values.length - 1) sb.append(", ");
        }
    }
    public static String toString(Object obj) { return JsonUtils.toJsonString(obj); }
    public static Logger getLogger() { return logger; }
}

4. Spring 配置啟用

<bean class="ServicesLoggerInterceptor"/>
<aop:aspectj-autoproxy proxy-target-class="true"/>

5. 效果示例

INFO getDataDictionaryByType(dataDictionaryParamInfo={"type":1,"code":0}) - started elapsed nanoseconds:11491670

反模式警示

// ❌ 緊耦合業務代碼,難以維護
public ValueResult query(String appKey, String token) {
    log.info("receive query! params: appKey=[" + appKey + "], token=[" + token + "]");
    // ...
}

AOP 實現關注點分離,業務代碼零侵入,配置靈活控制日誌粒度。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved