Java tutorial
/* * Copyright 2013-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.yoho.core.trace; import java.math.BigInteger; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** * Class for gathering and reporting statistics about a block of execution. * <p> * Spans should form a directed acyclic graph structure. It should be possible to keep * following the parents of a span until you arrive at a span with no parents. * <p> * Spans can be either annotated with tags or logs. * <p> * An <b>Annotation</b> is used to record existence of an event in time. Below you can find some * of the core annotations used to define the start and stop of a request: * <p> * <ul> * <li><b>cs</b> - Client Sent</li> * <li><b>sr</b> - Server Received</li> * <li><b>ss</b> - Server Sent</li> * <li><b>cr</b> - Client Received</li> * </ul> * * Spring Cloud Sleuth uses Zipkin compatible header names * * <ul> * <li>X-B3-TraceId: 64 encoded bits</li> * <li>X-B3-SpanId: 64 encoded bits</li> * <li>X-B3-ParentSpanId: 64 encoded bits</li> * <li>X-B3-Sampled: Boolean (either 1? or 0?)</li> * </ul> * * @author Spencer Gibb * @author Marcin Grzejszczak * @since 1.0.0 */ /* * OpenTracing spans can affect the trace tree by creating children. In this way, they are * like scoped tracers. Sleuth spans are DTOs, whose sole responsibility is the current * span in the trace tree. */ public class Span { public static final String SAMPLED_NAME = "X-B3-Sampled"; public static final String PROCESS_ID_NAME = "X-Process-Id"; public static final String PARENT_ID_NAME = "X-B3-ParentSpanId"; public static final String TRACE_ID_NAME = "X-B3-TraceId"; public static final String SPAN_NAME_NAME = "X-Span-Name"; public static final String SPAN_ID_NAME = "X-B3-SpanId"; public static final String SPAN_EXPORT_NAME = "X-Span-Export"; public static final String SPAN_SAMPLED = "1"; public static final String SPAN_NOT_SAMPLED = "0"; public static final String SPAN_LOCAL_COMPONENT_TAG_NAME = "lc"; /** * <b>cr</b> - Client Receive. Signifies the end of the span. The client has successfully received the * response from the server side. If one subtracts the cs timestamp from this timestamp one * will receive the whole time needed by the client to receive the response from the server. */ public static final String CLIENT_RECV = "cr"; /** * <b>cs</b> - Client Sent. The client has made a request (a client can be e.g. * {@link org.springframework.web.client.RestTemplate}. This annotation depicts * the start of the span. */ // For an outbound RPC call, it should log a "cs" annotation. // If possible, it should log a binary annotation of "sa", indicating the // destination address. public static final String CLIENT_SEND = "cs"; /** * <b>sr</b> - Server Receive. The server side got the request and will start processing it. * If one subtracts the cs timestamp from this timestamp one will receive the network latency. */ // If an inbound RPC call, it should log a "sr" annotation. // If possible, it should log a binary annotation of "ca", indicating the // caller's address (ex X-Forwarded-For header) public static final String SERVER_RECV = "sr"; /** * <b>ss</b> - Server Send. Annotated upon completion of request processing (when the response * got sent back to the client). If one subtracts the sr timestamp from this timestamp one * will receive the time needed by the server side to process the request. */ public static final String SERVER_SEND = "ss"; /** * <a href="https://github.com/opentracing/opentracing-go/blob/master/ext/tags.go">As in Open Tracing</a> */ public static final String SPAN_PEER_SERVICE_TAG_NAME = "peer.service"; private final long begin; private long end = 0; private final String name; private final long traceId; private List<Long> parents = new ArrayList<>(); private final long spanId; private boolean remote = false; private boolean exportable = true; private final Map<String, String> tags; private final String processId; private final List<Log> logs; private final Span savedSpan; @SuppressWarnings("unused") private Span() { this(-1, -1, "dummy", 0, Collections.<Long>emptyList(), 0, false, false, null); } /** * Creates a new span that still tracks tags and logs of the * current span. This is crucial when continuing spans * since the changes in those collections done in the continued span * need to be reflected until the span gets closed. */ public Span(Span current, Span savedSpan) { this.begin = current.getBegin(); this.end = current.getEnd(); this.name = current.getName(); this.traceId = current.getTraceId(); this.parents = current.getParents(); this.spanId = current.getSpanId(); this.remote = current.isRemote(); this.exportable = current.isExportable(); this.processId = current.getProcessId(); this.tags = current.tags; this.logs = current.logs; this.savedSpan = savedSpan; } public Span(long begin, long end, String name, long traceId, List<Long> parents, long spanId, boolean remote, boolean exportable, String processId) { this(begin, end, name, traceId, parents, spanId, remote, exportable, processId, null); } public Span(long begin, long end, String name, long traceId, List<Long> parents, long spanId, boolean remote, boolean exportable, String processId, Span savedSpan) { this.begin = begin <= 0 ? System.currentTimeMillis() : begin; this.end = end; this.name = name != null ? name : ""; this.traceId = traceId; this.parents = parents; this.spanId = spanId; this.remote = remote; this.exportable = exportable; this.processId = processId; this.savedSpan = savedSpan; this.tags = new LinkedHashMap<>(); this.logs = new ArrayList<>(); } public static SpanBuilder builder() { return new SpanBuilder(); } /** * The block has completed, stop the clock */ public synchronized void stop() { if (this.end == 0) { if (this.begin == 0) { throw new IllegalStateException("Span for " + this.name + " has not been started"); } this.end = System.currentTimeMillis(); } } /** * Return the total amount of time elapsed since start was called, if running, or * difference between stop and start */ public synchronized long getAccumulatedMillis() { if (this.begin == 0) { return 0; } if (this.end > 0) { return this.end - this.begin; } return System.currentTimeMillis() - this.begin; } /** * Has the span been started and not yet stopped? */ public synchronized boolean isRunning() { return this.begin != 0 && this.end == 0; } /** * Add a tag or data annotation associated with this span. The tag will be * added only if it has a value. */ public void tag(String key, String value) { if (StringUtils.hasText(value)) { this.tags.put(key, value); } } /** * Add an {@link Log#event event} to the timeline associated with this span. */ public void logEvent(String event) { this.logs.add(new Log(System.currentTimeMillis(), event)); } /** * Get tag data associated with this span (read only) * <p/> * <p/> * Will never be null. */ public Map<String, String> tags() { return Collections.unmodifiableMap(this.tags); } /** * Get any timestamped events (read only) * <p/> * <p/> * Will never be null. */ public List<Log> logs() { return Collections.unmodifiableList(this.logs); } /** * Returns the saved span. The one that was "current" before this span. * <p> * Might be null */ public Span getSavedSpan() { return this.savedSpan; } public boolean hasSavedSpan() { return this.savedSpan != null; } /** * A human-readable name assigned to this span instance. * <p> */ public String getName() { return this.name; } /** * A pseudo-unique (random) number assigned to this span instance. * <p> * <p> * The span id is immutable and cannot be changed. It is safe to access this from * multiple threads. */ public long getSpanId() { return this.spanId; } /** * A pseudo-unique (random) number assigned to the trace associated with this span */ public long getTraceId() { return this.traceId; } /** * Return a unique id for the process from which this span originated. * <p> * Might be null */ public String getProcessId() { return this.processId; } /** * Returns the parent IDs of the span. * <p> * <p> * The collection will be empty if there are no parents. */ public List<Long> getParents() { return this.parents; } /** * Flag that tells us whether the span was started in another process. Useful in RPC * tracing when the receiver actually has to add annotations to the senders span. */ public boolean isRemote() { return this.remote; } /** * Get the start time, in milliseconds */ public long getBegin() { return this.begin; } /** * Get the stop time, in milliseconds */ public long getEnd() { return this.end; } /** * Is the span eligible for export? If not then we may not need accumulate annotations * (for instance). */ public boolean isExportable() { return this.exportable; } /** * Represents given long id as hex string */ public static String idToHex(long id) { return Long.toHexString(id); } /** * Represents hex string as long */ public static long hexToId(String hexString) { Assert.hasText(hexString, "Can't convert empty hex string to long"); return new BigInteger(hexString, 16).longValue(); } @Override public String toString() { return "[Trace: " + idToHex(this.traceId) + ", Span: " + idToHex(this.spanId) + ", exportable=" + this.exportable + ", tags=" + this.tags() + "]"; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + (int) (this.spanId ^ (this.spanId >>> 32)); result = prime * result + (int) (this.traceId ^ (this.traceId >>> 32)); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Span other = (Span) obj; if (this.spanId != other.spanId) return false; if (this.traceId != other.traceId) return false; return true; } public static class SpanBuilder { private long begin; private long end; private String name; private long traceId; private ArrayList<Long> parents = new ArrayList<>(); private long spanId; private boolean remote; private boolean exportable = true; private String processId; private Span savedSpan; private List<Log> logs = new ArrayList<>(); private Map<String, String> tags = new LinkedHashMap<>(); SpanBuilder() { } public Span.SpanBuilder begin(long begin) { this.begin = begin; return this; } public Span.SpanBuilder end(long end) { this.end = end; return this; } public Span.SpanBuilder name(String name) { this.name = name; return this; } public Span.SpanBuilder traceId(long traceId) { this.traceId = traceId; return this; } public Span.SpanBuilder parent(Long parent) { this.parents.add(parent); return this; } public Span.SpanBuilder parents(Collection<Long> parents) { this.parents.addAll(parents); return this; } public Span.SpanBuilder log(Log log) { this.logs.add(log); return this; } public Span.SpanBuilder logs(Collection<Log> logs) { this.logs.addAll(logs); return this; } public Span.SpanBuilder tag(String tagKey, String tagValue) { this.tags.put(tagKey, tagValue); return this; } public Span.SpanBuilder tags(Map<String, String> tags) { this.tags.putAll(tags); return this; } public Span.SpanBuilder spanId(long spanId) { this.spanId = spanId; return this; } public Span.SpanBuilder remote(boolean remote) { this.remote = remote; return this; } public Span.SpanBuilder exportable(boolean exportable) { this.exportable = exportable; return this; } public Span.SpanBuilder processId(String processId) { this.processId = processId; return this; } public Span.SpanBuilder savedSpan(Span savedSpan) { this.savedSpan = savedSpan; return this; } public Span build() { Span span = new Span(this.begin, this.end, this.name, this.traceId, this.parents, this.spanId, this.remote, this.exportable, this.processId, this.savedSpan); span.logs.addAll(this.logs); span.tags.putAll(this.tags); return span; } @Override public String toString() { return "SpanBuilder{" + "begin=" + this.begin + ", end=" + this.end + ", name=" + this.name + ", traceId=" + this.traceId + ", parents=" + this.parents + ", spanId=" + this.spanId + ", remote=" + this.remote + ", exportable=" + this.exportable + ", processId='" + this.processId + '\'' + ", savedSpan=" + this.savedSpan + ", logs=" + this.logs + ", tags=" + this.tags + '}'; } } }