trace的一個demo版本,可以查看接口調用的整個鏈路,各種常規指標的統計,比如接口耗時,接口異常等。在此基礎上手機日志后,可以做一些簡單的logview展示。在實際工程中,根據業務需要,記錄相關監控指標數據,可以在此基礎上進行擴展,本demo展示的是trace鏈路原理上的處理方案。
1.注解類:對所有需要進行攔截的類添加該注解后,就會被掃描到做切面操作。
@Target({ElementType.METHOD,ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface TraceAnno { String desc() default ""; }
2.trace攔截類:對於注解類切面后的操作,記錄程序入口和接口整個鏈路
@Aspect public class TraceApsect { private final static Logger logger = LoggerFactory.getLogger(TraceApsect.class); @Pointcut("@annotation(com.demo.it.trace.TraceAnno) " + "|| @within(com.demo.it.trace.TraceAnno)") public void pointCut() { } @Around("pointCut()") public Object invoke(final ProceedingJoinPoint joinPoint) throws Throwable { if (!TraceSwitch.getInstance().isOpenProfilerTree()) { return joinPoint.proceed(); } String methodName = this.getClassAndMethodName(joinPoint); if (null == methodName) { return joinPoint.proceed(); } try { if (TraceProfiler.getEntry() == null) { TraceProfiler.start(methodName); } else { TraceProfiler.enter(methodName); } return joinPoint.proceed(); } catch (Throwable e) { // 異常通知 logger.error("The method " + methodName + " occurs expection : " + e); TraceProfiler.error(e); return e.getMessage(); } finally { TraceProfiler.release(); // 當root entry為狀態為release的時候,打印信息,並做reset操作 TraceProfiler.Entry rootEntry = TraceProfiler.getEntry(); if (rootEntry != null) { if (rootEntry.isReleased()) { long duration = rootEntry.getDuration(); if (duration > TraceSwitch.getInstance().getInvokeTimeout()) { logger.warn(TraceProfiler.dump()); } else { logger.info(TraceProfiler.dump()); } TraceProfiler.reset(); } } } } private String getClassAndMethodName(ProceedingJoinPoint joinPoint) { try { MethodSignature sign = (MethodSignature) joinPoint.getSignature(); String clazzName = joinPoint.getTarget().toString(); StringBuilder sb = new StringBuilder(); sb.append(TraceProfiler.split(clazzName, "@")[0]); sb.append(":").append(sign.getMethod().getName()); sb.append("(param:").append(sign.getMethod().getParameterTypes().length); sb.append(")"); return sb.toString(); } catch (Exception e) { e.printStackTrace(); return null; } }
3.trace 需要統計的內容:錯誤日志,耗時,ip, 接口調用連等
public final class TraceProfiler { /** 構建實體的存儲緩存體 */ private static final ThreadLocal<Entry> entryStack = new ThreadLocal<Entry>(); /** 開始計時 */ public static void start() { start((String) null); } /** 開始計時,創建一個Entry的實體對象 */ public static void start(String message) { entryStack.set(new Entry(message, null, null)); } /** threadLocal緩存清理,由於現在大多是線程池的設置,所以要做一個清理 */ public static void reset() { entryStack.set(null); } /** 由於Entry自身是樹狀結構,所以如果是進入非Root的節點,那就需要enter來搞 */ public static void enter(String message) { Entry currentEntry = getCurrentEntry(); if (currentEntry != null) { currentEntry.enterSubEntry(message); } } /** 保存異常信息 */ public static void error(Throwable e){ Entry currentEntry = getCurrentEntry(); if (currentEntry != null) { currentEntry.setErr(e); } } /** 方法運行結束之后,把當前的Entry的endTime來設置成當前時間 */ public static void release() { Entry currentEntry = getCurrentEntry(); if (currentEntry != null) { currentEntry.release(); } } /** 獲取start和end的時間差 */ public static long getDuration() { Entry entry = (Entry) entryStack.get(); if (entry != null) { return entry.getDuration(); } else { return -1; } } /** 把Entry的信息dump出來,可以打印到日志中去 */ public static String dump() { Entry entry = (Entry) entryStack.get(); if (entry != null) { String str=JSONObject.toJSONString(entry); return str; } return ""; } /** 獲取Entry信息 */ public static Entry getEntry() { return (Entry) entryStack.get(); } /** entry中含有subentry,如此這樣來進行循環來保持樹狀的結構 */ private static Entry getCurrentEntry() { Entry subEntry = (Entry) entryStack.get(); Entry entry = null; if (subEntry != null) { do { entry = subEntry; subEntry = entry.getUnreleasedEntry(); } while (subEntry != null); } return entry; } public static final class CONSTANT { public final static String DATE_FORMAT="yyyy-MM-dd hh:mm:ss SSS"; } /** * 代表一個計時單元。 */ public static final class Entry { // subEntries來表示樹狀的子節點 private final List<Entry> subEntries = new ArrayList<Entry>(); private final Object message; private final Entry firstEntry; private final long startTime; private long endTime; private Throwable err; private Entry(Object message/* 描述信息 */, Entry parentEntry/* 父節點信息 */, Entry firstEntry/* 第一個節點 */) { this.message = message; this.startTime = TraceSwitch.getInstance().isOpenProfilerNanoTime() == true ? System .nanoTime() : System.currentTimeMillis(); this.firstEntry = (Entry) defaultIfNull(firstEntry, this); } /** * 取得entry的信息。 */ public String getMessage() { return defaultIfEmpty((String) message, null); } public static String defaultIfEmpty(String str, String defaultStr) { return ((str == null) || (str.length() == 0)) ? defaultStr : str; } public static Object defaultIfNull(Object object, Object defaultValue) { return (object != null) ? object : defaultValue; } /** 獲取當前節點的開始時間 */ public String getStartTime() { SimpleDateFormat fmt=new SimpleDateFormat(CONSTANT.DATE_FORMAT); return fmt.format(startTime) ; } /** 獲取當前節點的結束時間 */ public String getEndTime() { SimpleDateFormat fmt=new SimpleDateFormat(CONSTANT.DATE_FORMAT); return fmt.format(endTime) ; } /** 獲取持續時間 */ public long getDuration() { if (endTime < startTime) { return -1; } else { return endTime - startTime; } } /** 取得entry自身所用的時間,即總時間減去所有子entry所用的時間。 */ public long getDurationOfSelf() { long duration = getDuration(); if (duration < 0) { return -1; } else if (subEntries.isEmpty()) { return duration; } else { for (int i = 0; i < subEntries.size(); i++) { Entry subEntry = (Entry) subEntries.get(i); duration -= subEntry.getDuration(); } if (duration < 0) { return -1; } else { return duration; } } } /** 取得所有子entries。 */ public List<Entry> getSubEntries() { return Collections.unmodifiableList(subEntries); } /** 結束當前entry,並記錄結束時間。 */ private void release() { endTime = TraceSwitch.getInstance().isOpenProfilerNanoTime() == true ? System .nanoTime() : System.currentTimeMillis(); } /** 判斷當前entry是否結束。 */ public boolean isReleased() { return endTime > 0; } /** 創建一個新的子entry */ private void enterSubEntry(Object message) { Entry subEntry = new Entry(message, this, firstEntry); subEntries.add(subEntry); } /** 添加異常信息 */ public String getError() { return err==null?"":err.getMessage(); } /** 設置異常信息 */ public void setErr(Throwable e){ this.err=e; } /** 取得未結束的子entry,鏈表中的最后一個元素 */ private Entry getUnreleasedEntry() { Entry subEntry = null; if (!subEntries.isEmpty()) { subEntry = (Entry) subEntries.get(subEntries.size() - 1); if (subEntry.isReleased()) { subEntry = null; } } return subEntry; } } public static String[] split(String str, String separatorChars) { return split(str, separatorChars, -1); } private static String[] split(String str, String separatorChars, int max) { if (str == null) { return null; } int length = str.length(); if (length == 0) { return new String[0]; } List<String> list = new LinkedList<String>(); int sizePlus1 = 1; int i = 0; int start = 0; boolean match = false; if (separatorChars == null) { // null表示使用空白作為分隔符 while (i < length) { if (Character.isWhitespace(str.charAt(i))) { if (match) { if (sizePlus1++ == max) { i = length; } list.add(str.substring(start, i)); match = false; } start = ++i; continue; } match = true; i++; } } else if (separatorChars.length() == 1) { char sep = separatorChars.charAt(0); while (i < length) { if (str.charAt(i) == sep) { if (match) { if (sizePlus1++ == max) { i = length; } list.add(str.substring(start, i)); match = false; } start = ++i; continue; } match = true; i++; } } else { // 一般情形 while (i < length) { if (separatorChars.indexOf(str.charAt(i)) >= 0) { if (match) { if (sizePlus1++ == max) { i = length; } list.add(str.substring(start, i)); match = false; } start = ++i; continue; } match = true; i++; } } if (match) { list.add(str.substring(start, i)); } return list.toArray(new String[list.size()]); } }
4.開關,各種指標監控
public class TraceSwitch { private static TraceSwitch instance = new TraceSwitch();public static TraceSwitch getInstance(){ return instance; } /** * 是否打開打印日志的開關 */ private boolean openProfilerTree =true;/** * 超時時間 */ private long invokeTimeout =500;/** * 是否打印納秒 * @return */ private boolean openProfilerNanoTime = false;public boolean isOpenProfilerTree() { return openProfilerTree; } public void setOpenProfilerTree(boolean openProfilerTree) { this.openProfilerTree = openProfilerTree; } public long getInvokeTimeout() { return invokeTimeout; } public void setInvokeTimeout(long invokeTimeout) { this.invokeTimeout = invokeTimeout; } public boolean isOpenProfilerNanoTime() { return openProfilerNanoTime; } public void setOpenProfilerNanoTime(boolean openProfilerNanoTime) { this.openProfilerNanoTime = openProfilerNanoTime; } }