/** * Copyright 2015-2016 The OpenZipkin 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 zipkin.collector; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.logging.Logger; import zipkin.Codec; import zipkin.Span; import zipkin.storage.Callback; import zipkin.storage.StorageComponent; import static java.lang.String.format; import static java.util.logging.Level.WARNING; import static zipkin.internal.Util.checkNotNull; /** * This component takes action on spans received from a transport. This includes deserializing, * sampling and scheduling for storage. * * <p>Callbacks passed do not propagate to the storage layer. They only return success or failures * before storage is attempted. This ensures that calling threads are disconnected from storage * threads. */ public final class Collector { /** Needed to scope this to the correct logging category */ public static Builder builder(Class<?> loggingClass) { return new Builder(Logger.getLogger(checkNotNull(loggingClass, "loggingClass").getName())); } public static final class Builder { final Logger logger; StorageComponent storage = null; CollectorSampler sampler = CollectorSampler.ALWAYS_SAMPLE; CollectorMetrics metrics = CollectorMetrics.NOOP_METRICS; Builder(Logger logger) { this.logger = logger; } /** @see {@link CollectorComponent.Builder#storage(StorageComponent)} */ public Builder storage(StorageComponent storage) { this.storage = checkNotNull(storage, "storage"); return this; } /** @see {@link CollectorComponent.Builder#metrics(CollectorMetrics)} */ public Builder metrics(CollectorMetrics metrics) { this.metrics = checkNotNull(metrics, "metrics"); return this; } /** @see {@link CollectorComponent.Builder#sampler(CollectorSampler)} */ public Builder sampler(CollectorSampler sampler) { this.sampler = checkNotNull(sampler, "sampler"); return this; } public Collector build() { return new Collector(this); } } final Logger logger; final StorageComponent storage; final CollectorSampler sampler; final CollectorMetrics metrics; Collector(Builder builder) { this.logger = checkNotNull(builder.logger, "logger"); this.storage = checkNotNull(builder.storage, "storage"); this.sampler = builder.sampler == null ? CollectorSampler.ALWAYS_SAMPLE : builder.sampler; this.metrics = builder.metrics == null ? CollectorMetrics.NOOP_METRICS : builder.metrics; } public void acceptSpans(byte[] serializedSpans, Codec codec, Callback<Void> callback) { metrics.incrementBytes(serializedSpans.length); List<Span> spans; try { spans = codec.readSpans(serializedSpans); } catch (RuntimeException e) { callback.onError(errorReading(e)); return; } accept(spans, callback); } public void acceptSpans(List<byte[]> serializedSpans, Codec codec, Callback<Void> callback) { List<Span> spans = new ArrayList<>(serializedSpans.size()); try { int bytesRead = 0; for (byte[] serializedSpan : serializedSpans) { bytesRead += serializedSpan.length; spans.add(codec.readSpan(serializedSpan)); } metrics.incrementBytes(bytesRead); } catch (RuntimeException e) { callback.onError(errorReading(e)); return; } accept(spans, callback); } public void accept(List<Span> spans, Callback<Void> callback) { if (spans.isEmpty()) { callback.onSuccess(null); return; } metrics.incrementSpans(spans.size()); List<Span> sampled = sample(spans); if (sampled.isEmpty()) { callback.onSuccess(null); return; } try { storage.asyncSpanConsumer().accept(sampled, acceptSpansCallback(sampled)); callback.onSuccess(null); } catch (RuntimeException e) { callback.onError(errorStoringSpans(sampled, e)); return; } } List<Span> sample(List<Span> input) { List<Span> sampled = new ArrayList<>(input.size()); for (Span s : input) { if (sampler.isSampled(s)) sampled.add(s); } int dropped = input.size() - sampled.size(); if (dropped > 0) metrics.incrementSpansDropped(dropped); return sampled; } Callback<Void> acceptSpansCallback(final List<Span> spans) { return new Callback<Void>() { @Override public void onSuccess(Void value) { } @Override public void onError(Throwable t) { errorStoringSpans(spans, t); } @Override public String toString() { return appendSpanIds(spans, new StringBuilder("AcceptSpans(")).append(")").toString(); } }; } RuntimeException errorReading(Throwable e) { return errorReading("Cannot decode spans", e); } RuntimeException errorReading(String message, Throwable e) { metrics.incrementMessagesDropped(); return doError(message, e); } /** * When storing spans, an exception can be raised before or after the fact. This adds context of * span ids to give logs more relevance. */ RuntimeException errorStoringSpans(List<Span> spans, Throwable e) { metrics.incrementSpansDropped(spans.size()); // The exception could be related to a span being huge. Instead of filling logs, // print trace id, span id pairs StringBuilder msg = appendSpanIds(spans, new StringBuilder("Cannot store spans ")); return doError(msg.toString(), e); } RuntimeException doError(String message, Throwable e) { if (e instanceof RuntimeException && e.getMessage() != null && e.getMessage() .startsWith("Malformed")) { logger.log(WARNING, e.getMessage(), e); return (RuntimeException) e; } else { message = format("%s due to %s(%s)", message, e.getClass().getSimpleName(), e.getMessage() == null ? "" : e.getMessage()); logger.log(WARNING, message, e); return new RuntimeException(message, e); } } static StringBuilder appendSpanIds(List<Span> spans, StringBuilder message) { message.append("["); for (Iterator<Span> iterator = spans.iterator(); iterator.hasNext(); ) { message.append(iterator.next().idString()); if (iterator.hasNext()) message.append(", "); } return message.append("]"); } }