Span.java 14.7 KB
Newer Older
P
pengys5 已提交
1
package org.skywalking.apm.trace;
2

3 4 5 6 7 8 9
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject;
import com.google.gson.TypeAdapter;
import com.google.gson.annotations.JsonAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
10 11
import java.io.ByteArrayOutputStream;
import java.io.IOException;
wu-sheng's avatar
wu-sheng 已提交
12 13 14 15 16
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
17 18 19 20
import org.skywalking.apm.trace.tag.BooleanTagItem;
import org.skywalking.apm.trace.tag.IntTagItem;
import org.skywalking.apm.trace.tag.StringTagItem;
import org.skywalking.apm.util.StringUtil;
wu-sheng's avatar
wu-sheng 已提交
21

22 23 24
/**
 * Span is a concept from OpenTracing Spec, also from Google Dapper Paper.
 * Traces in OpenTracing are defined implicitly by their Spans.
P
pengys5 已提交
25
 * <p>
26 27
 * Know more things about span concept:
 * {@see https://github.com/opentracing/specification/blob/master/specification.md#the-opentracing-data-model}
P
pengys5 已提交
28
 * <p>
29 30
 * Created by wusheng on 2017/2/17.
 */
31
@JsonAdapter(Span.Serializer.class)
32
public class Span {
wu-sheng's avatar
wu-sheng 已提交
33
    private static Gson SERIALIZATION_GSON = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
wu-sheng's avatar
wu-sheng 已提交
34

35
    private int spanId;
wu-sheng's avatar
wu-sheng 已提交
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
    private int parentSpanId;

    /**
     * The start time of this Span.
     */
    private long startTime;

    /**
     * The end time of this Span.
     */
    private long endTime;

    /**
     * The operation name ot this Span.
     * If you want to know, how to set an operation name,
     * {@see https://github.com/opentracing/specification/blob/master/specification.md#start-a-new-span}
     */
    private String operationName;

55
    /**
wu-sheng's avatar
wu-sheng 已提交
56
     * {@link #peer_host}, {@link #port} and {@link #peers} were part of tags,
57 58
     * independence them from tags for better performance and gc.
     */
wu-sheng's avatar
wu-sheng 已提交
59
    private String peer_host;
60 61 62 63 64

    private int port;

    private String peers;

wu-sheng's avatar
wu-sheng 已提交
65 66
    /**
     * Tag is a concept from OpenTracing spec.
P
pengys5 已提交
67
     * <p>
wu-sheng's avatar
wu-sheng 已提交
68 69
     * {@see https://github.com/opentracing/specification/blob/master/specification.md#set-a-span-tag}
     */
70
    private List<StringTagItem> tagsWithStr;
71

72
    private List<BooleanTagItem> tagsWithBool;
73

74
    private List<IntTagItem> tagsWithInt;
wu-sheng's avatar
wu-sheng 已提交
75 76 77

    /**
     * Log is a concept from OpenTracing spec.
P
pengys5 已提交
78
     * <p>
wu-sheng's avatar
wu-sheng 已提交
79 80
     * {@see https://github.com/opentracing/specification/blob/master/specification.md#log-structured-data}
     */
81
    private List<LogData> logs;
wu-sheng's avatar
wu-sheng 已提交
82 83

    /**
84
     * Create a new span, by given span id, parent span id and operationName.
wu-sheng's avatar
wu-sheng 已提交
85 86
     * This span must belong a {@link TraceSegment}, also is a part of Distributed Trace.
     *
87 88 89
     * @param spanId given by the creator, and must be unique id in the {@link TraceSegment}
     * @param parentSpanId given by the creator, and must be an existed span id in the {@link TraceSegment}. Value -1
     * means no parent span if this {@link TraceSegment}.
wu-sheng's avatar
wu-sheng 已提交
90 91
     * @param operationName {@link #operationName}
     */
92
    protected Span(int spanId, int parentSpanId, String operationName) {
93 94 95 96
        this(spanId, parentSpanId, operationName, System.currentTimeMillis());
    }

    /**
97
     * Create a new span, by given span id, parent span id, operationName and startTime.
98 99
     * This span must belong a {@link TraceSegment}, also is a part of Distributed Trace.
     *
100 101 102
     * @param spanId given by the creator, and must be unique id in the {@link TraceSegment}
     * @param parentSpanId given by the creator, and must be an existed span id in the {@link TraceSegment}. Value -1
     * means no parent span if this {@link TraceSegment}.
103
     * @param operationName {@link #operationName}
104
     * @param startTime given start timestamp.
105
     */
106
    protected Span(int spanId, int parentSpanId, String operationName, long startTime) {
107
        this();
wu-sheng's avatar
wu-sheng 已提交
108 109
        this.spanId = spanId;
        this.parentSpanId = parentSpanId;
110
        this.startTime = startTime;
wu-sheng's avatar
wu-sheng 已提交
111
        this.operationName = operationName;
wu-sheng's avatar
wu-sheng 已提交
112 113 114 115 116 117
    }

    /**
     * Create a new span, by given span id and no parent span id.
     * No parent span id means that, this Span is the first span of the {@link TraceSegment}
     *
118
     * @param spanId given by the creator, and must be unique id in the {@link TraceSegment}
wu-sheng's avatar
wu-sheng 已提交
119 120
     * @param operationName {@link #operationName}
     */
121
    public Span(int spanId, String operationName) {
wu-sheng's avatar
wu-sheng 已提交
122 123 124
        this(spanId, -1, operationName);
    }

125 126 127 128
    /**
     * Create a new span, by given span id and give startTime but no parent span id,
     * No parent span id means that, this Span is the first span of the {@link TraceSegment}
     *
129
     * @param spanId given by the creator, and must be unique id in the {@link TraceSegment}
130
     * @param operationName {@link #operationName}
131
     * @param startTime given start time of span
132 133 134 135 136
     */
    public Span(int spanId, String operationName, long startTime) {
        this(spanId, -1, operationName, startTime);
    }

137 138 139
    /**
     * Create a new span, by given span id and given parent {@link Span}.
     *
140 141
     * @param spanId given by the creator, and must be unique id in the {@link TraceSegment}
     * @param parentSpan {@link Span}
142 143
     * @param operationName {@link #operationName}
     */
144
    public Span(int spanId, Span parentSpan, String operationName) {
145 146 147 148
        this(spanId, parentSpan.spanId, operationName, System.currentTimeMillis());
    }

    /**
149 150
     * Create a new span, by given span id, parent span, operationName and startTime.
     * This span must belong a {@link TraceSegment}, also is a part of Distributed Trace.
151
     *
152 153
     * @param spanId given by the creator, and must be unique id in the {@link TraceSegment}
     * @param parentSpan {@link Span}
154
     * @param operationName {@link #operationName}
155
     * @param startTime given start timestamp
156 157 158
     */
    public Span(int spanId, Span parentSpan, String operationName, long startTime) {
        this(spanId, parentSpan.spanId, operationName, startTime);
159 160
    }

161
    /**
162
     * Create a new/empty span.
163
     */
164
    public Span() {
165 166
    }

wu-sheng's avatar
wu-sheng 已提交
167 168 169 170 171 172
    /**
     * Finish the active Span.
     * When it is finished, it will be archived by the given {@link TraceSegment}, which owners it.
     *
     * @param owner of the Span.
     */
173
    public void finish(TraceSegment owner) {
174 175 176 177 178 179 180 181
        this.finish(owner, System.currentTimeMillis());
    }

    /**
     * Finish the active Span.
     * When it is finished, it will be archived by the given {@link TraceSegment}, which owners it.
     * At the same out, set the {@link #endTime} as the given endTime
     *
182
     * @param owner of the Span.
183 184
     * @param endTime of the Span.
     */
185
    public void finish(TraceSegment owner, long endTime) {
186
        this.endTime = endTime;
wu-sheng's avatar
wu-sheng 已提交
187 188 189
        owner.archive(this);
    }

190 191 192 193 194
    /**
     * Sets the string name for the logical operation this span represents.
     *
     * @return this Span instance, for chaining
     */
195
    public Span setOperationName(String operationName) {
196 197 198 199
        this.operationName = operationName;
        return this;
    }

wu-sheng's avatar
wu-sheng 已提交
200 201
    /**
     * Set a key:value tag on the Span.
202 203
     *
     * @return this Span instance, for chaining
wu-sheng's avatar
wu-sheng 已提交
204
     */
205
    public Span setTag(String key, String value) {
206 207 208 209
        if (tagsWithStr == null) {
            tagsWithStr = new LinkedList<StringTagItem>();
        }
        tagsWithStr.add(new StringTagItem(key, value));
wu-sheng's avatar
wu-sheng 已提交
210 211 212
        return this;
    }

213
    public Span setTag(String key, boolean value) {
214 215 216 217
        if (tagsWithBool == null) {
            tagsWithBool = new LinkedList<BooleanTagItem>();
        }
        tagsWithBool.add(new BooleanTagItem(key, value));
wu-sheng's avatar
wu-sheng 已提交
218 219 220
        return this;
    }

221
    public Span setTag(String key, Integer value) {
222 223 224 225
        if (tagsWithInt == null) {
            tagsWithInt = new LinkedList<IntTagItem>();
        }
        tagsWithInt.add(new IntTagItem(key, value));
wu-sheng's avatar
wu-sheng 已提交
226 227 228 229
        return this;
    }

    /**
230
     * This method is from opentracing-java. {@see https://github.com/opentracing/opentracing-java/blob/release-0.20.9/opentracing-api/src/main/java/io/opentracing/Span.java#L91}
231 232 233
     * <p> Log key:value pairs to the Span with the current walltime timestamp. <p> <p><strong>CAUTIONARY NOTE:</strong>
     * not all Tracer implementations support key:value log fields end-to-end. Caveat emptor. <p> <p>A contrived example
     * (using Guava, which is not required):
wu-sheng's avatar
wu-sheng 已提交
234
     * <pre>{@code
235 236 237 238 239 240 241
     * span.log(
     * ImmutableMap.Builder<String, Object>()
     * .put("event", "soft error")
     * .put("type", "cache timeout")
     * .put("waited.millis", 1500)
     * .build());
     * }</pre>
wu-sheng's avatar
wu-sheng 已提交
242 243
     *
     * @param fields key:value log fields. Tracer implementations should support String, numeric, and boolean values;
244
     * some may also support arbitrary Objects.
wu-sheng's avatar
wu-sheng 已提交
245 246 247
     * @return the Span, for chaining
     * @see Span#log(String)
     */
248
    public Span log(Map<String, String> fields) {
249 250 251
        if (logs == null) {
            logs = new LinkedList<LogData>();
        }
wu-sheng's avatar
wu-sheng 已提交
252 253 254 255 256
        logs.add(new LogData(System.currentTimeMillis(), fields));
        return this;
    }

    /**
257 258 259 260 261 262 263
     * Record an exception event of the current walltime timestamp.
     *
     * @param t any subclass of {@link Throwable}, which occurs in this span.
     * @return the Span, for chaining
     */
    public Span log(Throwable t) {
        Map<String, String> exceptionFields = new HashMap<String, String>();
wu-sheng's avatar
wu-sheng 已提交
264
        exceptionFields.put("event", "error");
265 266 267 268
        exceptionFields.put("error.kind", t.getClass().getName());
        exceptionFields.put("message", t.getMessage());
        exceptionFields.put("stack", ThrowableTransformer.INSTANCE.convert2String(t, 4000));

269
        return log(exceptionFields);
270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299
    }

    private enum ThrowableTransformer {
        INSTANCE;

        private String convert2String(Throwable e, int maxLength) {
            ByteArrayOutputStream buf = null;
            StringBuilder expMessage = new StringBuilder();
            try {
                buf = new ByteArrayOutputStream();
                Throwable causeException = e;
                while (expMessage.length() < maxLength && causeException != null) {
                    causeException.printStackTrace(new java.io.PrintWriter(buf, true));
                    expMessage.append(buf.toString());
                    causeException = causeException.getCause();
                }

            } finally {
                try {
                    buf.close();
                } catch (IOException ioe) {
                }
            }

            return (maxLength > expMessage.length() ? expMessage : expMessage.substring(0, maxLength)).toString();
        }
    }

    /**
     * This method is from opentracing-java. {@see https://github.com/opentracing/opentracing-java/blob/release-0.20.9/opentracing-api/src/main/java/io/opentracing/Span.java#L120}
300
     * <p> Record an event at the current walltime timestamp. <p> Shorthand for <p>
wu-sheng's avatar
wu-sheng 已提交
301
     * <pre>{@code
302 303
     * span.log(Collections.singletonMap("event", event));
     * }</pre>
wu-sheng's avatar
wu-sheng 已提交
304 305 306 307
     *
     * @param event the event value; often a stable identifier for a moment in the Span lifecycle
     * @return the Span, for chaining
     */
308
    public Span log(String event) {
wu-sheng's avatar
wu-sheng 已提交
309 310 311
        log(Collections.singletonMap("event", event));
        return this;
    }
312 313 314 315

    public int getSpanId() {
        return spanId;
    }
316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332

    public int getParentSpanId() {
        return parentSpanId;
    }

    public long getStartTime() {
        return startTime;
    }

    public long getEndTime() {
        return endTime;
    }

    public String getOperationName() {
        return operationName;
    }

333 334 335 336
    public boolean isLeaf() {
        return false;
    }

wu-sheng's avatar
wu-sheng 已提交
337 338
    public String getPeerHost() {
        return peer_host;
339 340 341 342 343 344 345 346 347 348
    }

    public int getPort() {
        return port;
    }

    public String getPeers() {
        return peers;
    }

wu-sheng's avatar
wu-sheng 已提交
349 350
    public void setPeer_host(String peer_host) {
        this.peer_host = peer_host;
351 352 353 354 355 356 357 358 359 360
    }

    public void setPort(int port) {
        this.port = port;
    }

    public void setPeers(String peers) {
        this.peers = peers;
    }

wu-sheng's avatar
wu-sheng 已提交
361 362 363 364 365 366 367 368 369
    @Override
    public String toString() {
        return "Span{" +
            "spanId=" + spanId +
            ", parentSpanId=" + parentSpanId +
            ", startTime=" + startTime +
            ", operationName='" + operationName + '\'' +
            '}';
    }
370 371 372 373 374 375 376 377 378 379 380 381 382

    public static class Serializer extends TypeAdapter<Span> {
        @Override
        public void write(JsonWriter out, Span span) throws IOException {
            out.beginObject();
            out.name("si").value(span.spanId);
            out.name("ps").value(span.parentSpanId);
            out.name("st").value(span.startTime);
            out.name("et").value(span.endTime);
            out.name("on").value(span.operationName);

            this.writeTags(out, span);

wu-sheng's avatar
wu-sheng 已提交
383 384
            if (span.logs != null) {
                out.name("logs").jsonValue(SERIALIZATION_GSON.toJson(span.logs));
385 386 387 388 389 390 391 392 393
            }

            out.endObject();
        }

        public void writeTags(JsonWriter out, Span span) throws IOException {
            JsonObject tagWithStr = null;
            JsonObject tagWithInt = null;
            JsonObject tagWithBool = null;
wu-sheng's avatar
wu-sheng 已提交
394
            if (!StringUtil.isEmpty(span.peer_host)) {
395
                tagWithStr = new JsonObject();
wu-sheng's avatar
wu-sheng 已提交
396
                tagWithStr.addProperty("peer.host", span.peer_host);
397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441
                tagWithInt = new JsonObject();
                tagWithInt.addProperty("peer.port", span.port);
            } else if (!StringUtil.isEmpty(span.peers)) {
                tagWithStr = new JsonObject();
                tagWithStr.addProperty("peers", span.peers);
            } else if (span.tagsWithStr != null) {
                tagWithStr = new JsonObject();
            }

            if (span.tagsWithStr != null) {
                for (StringTagItem item : span.tagsWithStr) {
                    tagWithStr.addProperty(item.getKey(), item.getValue());
                }
            }
            if (span.tagsWithInt != null) {
                if (tagWithInt != null) {
                    tagWithInt = new JsonObject();
                }
                for (IntTagItem item : span.tagsWithInt) {
                    tagWithInt.addProperty(item.getKey(), item.getValue());
                }
            }
            if (span.tagsWithBool != null) {
                tagWithBool = new JsonObject();
                for (BooleanTagItem item : span.tagsWithBool) {
                    tagWithBool.addProperty(item.getKey(), item.getValue());
                }
            }

            if (tagWithStr != null) {
                out.name("ts").jsonValue(tagWithStr.toString());
            }
            if (tagWithInt != null) {
                out.name("ti").jsonValue(tagWithInt.toString());
            }
            if (tagWithBool != null) {
                out.name("tb").jsonValue(tagWithBool.toString());
            }
        }

        @Override
        public Span read(JsonReader in) throws IOException {
            throw new IOException("Can't deserialize span at agent side for performance consideration");
        }
    }
442
}