Java tutorial
/* * Copyright 2018, OpenCensus 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 io.opencensus.exporter.trace.jaeger; import static com.google.common.base.Preconditions.checkNotNull; import static java.lang.String.format; import static java.util.concurrent.TimeUnit.NANOSECONDS; import static java.util.concurrent.TimeUnit.SECONDS; import com.google.common.collect.Lists; import com.google.common.primitives.Ints; import com.google.common.primitives.Longs; import com.google.errorprone.annotations.MustBeClosed; import io.jaegertracing.internal.exceptions.SenderException; import io.jaegertracing.thrift.internal.senders.HttpSender; import io.jaegertracing.thriftjava.Log; import io.jaegertracing.thriftjava.Process; import io.jaegertracing.thriftjava.Span; import io.jaegertracing.thriftjava.SpanRef; import io.jaegertracing.thriftjava.SpanRefType; import io.jaegertracing.thriftjava.Tag; import io.jaegertracing.thriftjava.TagType; import io.opencensus.common.Function; import io.opencensus.common.Scope; import io.opencensus.common.Timestamp; import io.opencensus.trace.Annotation; import io.opencensus.trace.AttributeValue; import io.opencensus.trace.Link; import io.opencensus.trace.MessageEvent; import io.opencensus.trace.MessageEvent.Type; import io.opencensus.trace.Sampler; import io.opencensus.trace.SpanContext; import io.opencensus.trace.SpanId; import io.opencensus.trace.Status; import io.opencensus.trace.TraceId; import io.opencensus.trace.TraceOptions; import io.opencensus.trace.Tracer; import io.opencensus.trace.Tracing; import io.opencensus.trace.export.SpanData; import io.opencensus.trace.export.SpanExporter; import io.opencensus.trace.samplers.Samplers; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import javax.annotation.Nullable; import javax.annotation.concurrent.NotThreadSafe; @NotThreadSafe final class JaegerExporterHandler extends SpanExporter.Handler { private static final String EXPORT_SPAN_NAME = "ExportJaegerTraces"; private static final String SPAN_KIND = "span.kind"; private static final Tag SERVER_KIND_TAG = new Tag(SPAN_KIND, TagType.STRING).setVStr("server"); private static final Tag CLIENT_KIND_TAG = new Tag(SPAN_KIND, TagType.STRING).setVStr("client"); private static final String DESCRIPTION = "message"; private static final Tag RECEIVED_MESSAGE_EVENT_TAG = new Tag(DESCRIPTION, TagType.STRING) .setVStr("received message"); private static final Tag SENT_MESSAGE_EVENT_TAG = new Tag(DESCRIPTION, TagType.STRING).setVStr("sent message"); private static final String MESSAGE_EVENT_ID = "id"; private static final String MESSAGE_EVENT_COMPRESSED_SIZE = "compressed_size"; private static final String MESSAGE_EVENT_UNCOMPRESSED_SIZE = "uncompressed_size"; private static final Logger logger = Logger.getLogger(JaegerExporterHandler.class.getName()); /** * Sampler with low probability used during the export in order to avoid the case when user sets * the default sampler to always sample and we get the Thrift span of the Jaeger export call * always sampled and go to an infinite loop. */ private static final Sampler lowProbabilitySampler = Samplers.probabilitySampler(0.0001); private static final Tracer tracer = Tracing.getTracer(); private static final Function<? super String, Tag> stringAttributeConverter = new Function<String, Tag>() { @Override public Tag apply(final String value) { final Tag tag = new Tag(); tag.setVType(TagType.STRING); tag.setVStr(value); return tag; } }; private static final Function<? super Boolean, Tag> booleanAttributeConverter = new Function<Boolean, Tag>() { @Override public Tag apply(final Boolean value) { final Tag tag = new Tag(); tag.setVType(TagType.BOOL); tag.setVBool(value); return tag; } }; private static final Function<? super Double, Tag> doubleAttributeConverter = new Function<Double, Tag>() { @Override public Tag apply(final Double value) { final Tag tag = new Tag(); tag.setVType(TagType.DOUBLE); tag.setVDouble(value); return tag; } }; private static final Function<? super Long, Tag> longAttributeConverter = new Function<Long, Tag>() { @Override public Tag apply(final Long value) { final Tag tag = new Tag(); tag.setVType(TagType.LONG); tag.setVLong(value); return tag; } }; private static final Function<Object, Tag> defaultAttributeConverter = new Function<Object, Tag>() { @Override public Tag apply(final Object value) { final Tag tag = new Tag(); tag.setVType(TagType.STRING); tag.setVStr(value.toString()); return tag; } }; // Re-usable buffers to avoid too much memory allocation during conversions. // N.B.: these make instances of this class thread-unsafe, hence the above // @NotThreadSafe annotation. private final byte[] spanIdBuffer = new byte[SpanId.SIZE]; private final byte[] traceIdBuffer = new byte[TraceId.SIZE]; private final byte[] optionsBuffer = new byte[Integer.SIZE / Byte.SIZE]; private final HttpSender sender; private final Process process; JaegerExporterHandler(final HttpSender sender, final Process process) { this.sender = checkNotNull(sender, "Jaeger sender must NOT be null."); this.process = checkNotNull(process, "Process sending traces must NOT be null."); } @Override public void export(final Collection<SpanData> spanDataList) { final Scope exportScope = newExportScope(); try { doExport(spanDataList); } catch (SenderException e) { tracer.getCurrentSpan() // exportScope above. .setStatus(Status.UNKNOWN.withDescription(getMessageOrDefault(e))); logger.log(Level.WARNING, "Failed to export traces to Jaeger: " + e); } finally { exportScope.close(); } } @MustBeClosed private static Scope newExportScope() { // Start a new span with explicit sampler (with low probability) to avoid the case when user // sets the default sampler to always sample and we get the Thrift span of the Jaeger // export call always sampled and go to an infinite loop. return tracer.spanBuilder(EXPORT_SPAN_NAME).setSampler(lowProbabilitySampler).startScopedSpan(); } private void doExport(final Collection<SpanData> spanDataList) throws SenderException { final List<Span> spans = spanDataToJaegerThriftSpans(spanDataList); sender.send(process, spans); } private static String getMessageOrDefault(final SenderException e) { return e.getMessage() == null ? e.getClass().getSimpleName() : e.getMessage(); } private List<Span> spanDataToJaegerThriftSpans(final Collection<SpanData> spanDataList) { final List<Span> spans = Lists.newArrayListWithExpectedSize(spanDataList.size()); for (final SpanData spanData : spanDataList) { spans.add(spanDataToJaegerThriftSpan(spanData)); } return spans; } private Span spanDataToJaegerThriftSpan(final SpanData spanData) { final long startTimeInMicros = timestampToMicros(spanData.getStartTimestamp()); final long endTimeInMicros = timestampToMicros(spanData.getEndTimestamp()); final SpanContext context = spanData.getContext(); copyToBuffer(context.getTraceId()); return new io.jaegertracing.thriftjava.Span(traceIdLow(), traceIdHigh(), spanIdToLong(context.getSpanId()), spanIdToLong(spanData.getParentSpanId()), spanData.getName(), optionsToFlags(context.getTraceOptions()), startTimeInMicros, endTimeInMicros - startTimeInMicros) .setReferences(linksToReferences(spanData.getLinks().getLinks())) .setTags(attributesToTags(spanData.getAttributes().getAttributeMap(), spanKindToTag(spanData.getKind()))) .setLogs(timedEventsToLogs(spanData.getAnnotations().getEvents(), spanData.getMessageEvents().getEvents())); } private void copyToBuffer(final TraceId traceId) { // Attempt to minimise allocations, since TraceId#getBytes currently creates a defensive copy: traceId.copyBytesTo(traceIdBuffer, 0); } private long traceIdHigh() { return Longs.fromBytes(traceIdBuffer[0], traceIdBuffer[1], traceIdBuffer[2], traceIdBuffer[3], traceIdBuffer[4], traceIdBuffer[5], traceIdBuffer[6], traceIdBuffer[7]); } private long traceIdLow() { return Longs.fromBytes(traceIdBuffer[8], traceIdBuffer[9], traceIdBuffer[10], traceIdBuffer[11], traceIdBuffer[12], traceIdBuffer[13], traceIdBuffer[14], traceIdBuffer[15]); } private long spanIdToLong(final @Nullable SpanId spanId) { if (spanId == null) { return 0L; } // Attempt to minimise allocations, since SpanId#getBytes currently creates a defensive copy: spanId.copyBytesTo(spanIdBuffer, 0); return Longs.fromByteArray(spanIdBuffer); } private int optionsToFlags(final TraceOptions traceOptions) { // Attempt to minimise allocations, since TraceOptions#getBytes currently creates a defensive // copy: traceOptions.copyBytesTo(optionsBuffer, optionsBuffer.length - 1); return Ints.fromByteArray(optionsBuffer); } private List<SpanRef> linksToReferences(final List<Link> links) { final List<SpanRef> spanRefs = Lists.newArrayListWithExpectedSize(links.size()); for (final Link link : links) { copyToBuffer(link.getTraceId()); spanRefs.add(new SpanRef(linkTypeToRefType(link.getType()), traceIdLow(), traceIdHigh(), spanIdToLong(link.getSpanId()))); } return spanRefs; } private static long timestampToMicros(final @Nullable Timestamp timestamp) { return (timestamp == null) ? 0L : SECONDS.toMicros(timestamp.getSeconds()) + NANOSECONDS.toMicros(timestamp.getNanos()); } private static SpanRefType linkTypeToRefType(final Link.Type type) { switch (type) { case CHILD_LINKED_SPAN: return SpanRefType.CHILD_OF; case PARENT_LINKED_SPAN: return SpanRefType.FOLLOWS_FROM; } throw new UnsupportedOperationException( format("Failed to convert link type [%s] to a Jaeger SpanRefType.", type)); } private static List<Tag> attributesToTags(final Map<String, AttributeValue> attributes, @Nullable final Tag extraTag) { final List<Tag> tags = Lists.newArrayListWithExpectedSize(attributes.size() + 1); for (final Map.Entry<String, AttributeValue> entry : attributes.entrySet()) { final Tag tag = entry.getValue().match(stringAttributeConverter, booleanAttributeConverter, longAttributeConverter, doubleAttributeConverter, defaultAttributeConverter); tag.setKey(entry.getKey()); tags.add(tag); } if (extraTag != null) { tags.add(extraTag); } return tags; } private static List<Log> timedEventsToLogs(final List<SpanData.TimedEvent<Annotation>> annotations, final List<SpanData.TimedEvent<MessageEvent>> messageEvents) { final List<Log> logs = Lists.newArrayListWithExpectedSize(annotations.size() + messageEvents.size()); for (final SpanData.TimedEvent<Annotation> event : annotations) { final long timestampsInMicros = timestampToMicros(event.getTimestamp()); logs.add(new Log(timestampsInMicros, attributesToTags(event.getEvent().getAttributes(), descriptionToTag(event.getEvent().getDescription())))); } for (final SpanData.TimedEvent<MessageEvent> event : messageEvents) { final long timestampsInMicros = timestampToMicros(event.getTimestamp()); final Tag tagMessageId = new Tag(MESSAGE_EVENT_ID, TagType.LONG) .setVLong(event.getEvent().getMessageId()); final Tag tagCompressedSize = new Tag(MESSAGE_EVENT_COMPRESSED_SIZE, TagType.LONG) .setVLong(event.getEvent().getCompressedMessageSize()); final Tag tagUncompressedSize = new Tag(MESSAGE_EVENT_UNCOMPRESSED_SIZE, TagType.LONG) .setVLong(event.getEvent().getUncompressedMessageSize()); logs.add(new Log(timestampsInMicros, Arrays.asList( event.getEvent().getType() == Type.RECEIVED ? RECEIVED_MESSAGE_EVENT_TAG : SENT_MESSAGE_EVENT_TAG, tagMessageId, tagCompressedSize, tagUncompressedSize))); } return logs; } private static Tag descriptionToTag(final String description) { final Tag tag = new Tag(DESCRIPTION, TagType.STRING); tag.setVStr(description); return tag; } @Nullable private static Tag spanKindToTag(@Nullable final io.opencensus.trace.Span.Kind kind) { if (kind == null) { return null; } switch (kind) { case CLIENT: return CLIENT_KIND_TAG; case SERVER: return SERVER_KIND_TAG; } return null; } }