Skip to content

Commit

Permalink
Client span keys: suppressing same instrumentation (#3691)
Browse files Browse the repository at this point in the history
  • Loading branch information
lmolkova authored Aug 14, 2021
1 parent c748532 commit 080c85d
Show file tree
Hide file tree
Showing 13 changed files with 775 additions and 52 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.api.instrumenter;

import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.context.Context;

final class CompositeSuppressionStrategy extends SpanSuppressionStrategy {
private final SpanSuppressionStrategy clientStrategy;
private final SpanSuppressionStrategy producerStrategy;
private final SpanSuppressionStrategy serverStrategy;
private final SpanSuppressionStrategy consumerStrategy;

CompositeSuppressionStrategy(
SpanSuppressionStrategy client,
SpanSuppressionStrategy producer,
SpanSuppressionStrategy server,
SpanSuppressionStrategy consumer) {
this.clientStrategy = client;
this.producerStrategy = producer;
this.serverStrategy = server;
this.consumerStrategy = consumer;
}

@Override
Context storeInContext(Context context, SpanKind spanKind, Span span) {
switch (spanKind) {
case CLIENT:
return clientStrategy.storeInContext(context, spanKind, span);
case PRODUCER:
return producerStrategy.storeInContext(context, spanKind, span);
case SERVER:
return serverStrategy.storeInContext(context, spanKind, span);
case CONSUMER:
return consumerStrategy.storeInContext(context, spanKind, span);
case INTERNAL:
return context;
}
return context;
}

@Override
boolean shouldSuppress(Context parentContext, SpanKind spanKind) {
switch (spanKind) {
case CLIENT:
return clientStrategy.shouldSuppress(parentContext, spanKind);
case PRODUCER:
return producerStrategy.shouldSuppress(parentContext, spanKind);
case SERVER:
return serverStrategy.shouldSuppress(parentContext, spanKind);
case CONSUMER:
return consumerStrategy.shouldSuppress(parentContext, spanKind);
case INTERNAL:
return false;
}
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,6 @@
import io.opentelemetry.context.Context;
import io.opentelemetry.instrumentation.api.InstrumentationVersion;
import io.opentelemetry.instrumentation.api.internal.SupportabilityMetrics;
import io.opentelemetry.instrumentation.api.tracer.ClientSpan;
import io.opentelemetry.instrumentation.api.tracer.ConsumerSpan;
import io.opentelemetry.instrumentation.api.tracer.ServerSpan;
import java.util.ArrayList;
import java.util.List;
import org.checkerframework.checker.nullness.qual.Nullable;
Expand Down Expand Up @@ -76,6 +73,7 @@ public static <REQUEST, RESPONSE> InstrumenterBuilder<REQUEST, RESPONSE> newBuil
private final ErrorCauseExtractor errorCauseExtractor;
@Nullable private final StartTimeExtractor<REQUEST> startTimeExtractor;
@Nullable private final EndTimeExtractor<RESPONSE> endTimeExtractor;
private final SpanSuppressionStrategy spanSuppressionStrategy;

Instrumenter(InstrumenterBuilder<REQUEST, RESPONSE> builder) {
this.instrumentationName = builder.instrumentationName;
Expand All @@ -90,6 +88,7 @@ public static <REQUEST, RESPONSE> InstrumenterBuilder<REQUEST, RESPONSE> newBuil
this.errorCauseExtractor = builder.errorCauseExtractor;
this.startTimeExtractor = builder.startTimeExtractor;
this.endTimeExtractor = builder.endTimeExtractor;
this.spanSuppressionStrategy = builder.getSpanSuppressionStrategy();
}

/**
Expand All @@ -99,21 +98,9 @@ public static <REQUEST, RESPONSE> InstrumenterBuilder<REQUEST, RESPONSE> newBuil
* without calling those methods.
*/
public boolean shouldStart(Context parentContext, REQUEST request) {
boolean suppressed = false;
SpanKind spanKind = spanKindExtractor.extract(request);
switch (spanKind) {
case SERVER:
suppressed = ServerSpan.exists(parentContext);
break;
case CONSUMER:
suppressed = ConsumerSpan.exists(parentContext);
break;
case CLIENT:
suppressed = ClientSpan.exists(parentContext);
break;
default:
break;
}
boolean suppressed = spanSuppressionStrategy.shouldSuppress(parentContext, spanKind);

if (suppressed) {
supportability.recordSuppressedSpan(spanKind, instrumentationName);
}
Expand Down Expand Up @@ -158,16 +145,8 @@ public Context start(Context parentContext, REQUEST request) {
spanBuilder.setAllAttributes(attributes);
Span span = spanBuilder.startSpan();
context = context.with(span);
switch (spanKind) {
case SERVER:
return ServerSpan.with(context, span);
case CLIENT:
return ClientSpan.with(context, span);
case CONSUMER:
return ConsumerSpan.with(context, span);
default:
return context;
}

return spanSuppressionStrategy.storeInContext(context, spanKind, span);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,21 @@
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.metrics.GlobalMeterProvider;
import io.opentelemetry.api.metrics.Meter;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.api.trace.StatusCode;
import io.opentelemetry.context.propagation.TextMapGetter;
import io.opentelemetry.context.propagation.TextMapSetter;
import io.opentelemetry.instrumentation.api.annotations.UnstableApi;
import io.opentelemetry.instrumentation.api.config.Config;
import io.opentelemetry.instrumentation.api.instrumenter.db.DbAttributesExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpAttributesExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.messaging.MessagingAttributesExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.rpc.RpcAttributesExtractor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.checkerframework.checker.nullness.qual.Nullable;

/**
Expand All @@ -26,6 +34,12 @@
*/
public final class InstrumenterBuilder<REQUEST, RESPONSE> {

/** Instrumentation type suppression configuration property key. */
private static final boolean ENABLE_SPAN_SUPPRESSION_BY_TYPE =
Config.get()
.getBooleanProperty(
"otel.instrumentation.experimental.outgoing-span-suppression-by-type", false);

final OpenTelemetry openTelemetry;
final Meter meter;
final String instrumentationName;
Expand All @@ -43,6 +57,8 @@ public final class InstrumenterBuilder<REQUEST, RESPONSE> {
@Nullable StartTimeExtractor<REQUEST> startTimeExtractor = null;
@Nullable EndTimeExtractor<RESPONSE> endTimeExtractor = null;

private boolean enableSpanSuppressionByType = ENABLE_SPAN_SUPPRESSION_BY_TYPE;

InstrumenterBuilder(
OpenTelemetry openTelemetry,
String instrumentationName,
Expand Down Expand Up @@ -120,6 +136,38 @@ public InstrumenterBuilder<REQUEST, RESPONSE> setTimeExtractors(
return this;
}

// visible for tests
/**
* Enables suppression based on client instrumentation type.
*
* <p><strong>When enabled, suppresses nested spans depending on their {@link SpanKind} and
* type</strong>.
*
* <ul>
* <li>CLIENT and PRODUCER nested spans are suppressed based on their type (HTTP, RPC, DB,
* MESSAGING) i.e. if span with the same type is on the context, new span of this type will
* not be started.
* </ul>
*
* <p><strong>When disabled:</strong>
*
* <ul>
* <li>CLIENT and PRODUCER nested spans are always suppressed
* </ul>
*
* <p><strong>In both cases:</strong>
*
* <ul>
* <li>SERVER and CONSUMER nested spans are always suppressed
* <li>INTERNAL spans are never suppressed
* </ul>
*/
InstrumenterBuilder<REQUEST, RESPONSE> enableInstrumentationTypeSuppression(
boolean enableInstrumentationType) {
this.enableSpanSuppressionByType = enableInstrumentationType;
return this;
}

/**
* Returns a new {@link Instrumenter} which will create client spans and inject context into
* requests.
Expand Down Expand Up @@ -189,6 +237,34 @@ private Instrumenter<REQUEST, RESPONSE> newInstrumenter(
return constructor.create(this);
}

SpanSuppressionStrategy getSpanSuppressionStrategy() {
if (!enableSpanSuppressionByType) {
// if not enabled, preserve current behavior, not distinguishing types
return SpanSuppressionStrategy.SUPPRESS_ALL_NESTED_OUTGOING_STRATEGY;
}

Set<SpanKey> spanKeys = spanKeysFromAttributeExtractor(this.attributesExtractors);
return SpanSuppressionStrategy.from(spanKeys);
}

private static Set<SpanKey> spanKeysFromAttributeExtractor(
List<? extends AttributesExtractor<?, ?>> attributesExtractors) {

Set<SpanKey> spanKeys = new HashSet<>();
for (AttributesExtractor<?, ?> attributeExtractor : attributesExtractors) {
if (attributeExtractor instanceof HttpAttributesExtractor) {
spanKeys.add(SpanKey.HTTP_CLIENT);
} else if (attributeExtractor instanceof RpcAttributesExtractor) {
spanKeys.add(SpanKey.RPC_CLIENT);
} else if (attributeExtractor instanceof DbAttributesExtractor) {
spanKeys.add(SpanKey.DB_CLIENT);
} else if (attributeExtractor instanceof MessagingAttributesExtractor) {
spanKeys.add(SpanKey.MESSAGING_PRODUCER);
}
}
return spanKeys;
}

private interface InstrumenterConstructor<RQ, RS> {
Instrumenter<RQ, RS> create(InstrumenterBuilder<RQ, RS> builder);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.api.instrumenter;

import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.context.Context;

final class NoopSuppressionStrategy extends SpanSuppressionStrategy {

static final SpanSuppressionStrategy INSTANCE = new NoopSuppressionStrategy();

@Override
Context storeInContext(Context context, SpanKind spanKind, Span span) {
return context;
}

@Override
boolean shouldSuppress(Context parentContext, SpanKind spanKind) {
return false;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.api.instrumenter;

import io.opentelemetry.api.trace.Span;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.ContextKey;
import org.checkerframework.checker.nullness.qual.Nullable;

/** Makes span keys for specific instrumentation accessible to enrich and suppress spans. */
public final class SpanKey {

private static final ContextKey<Span> SERVER_KEY =
ContextKey.named("opentelemetry-traces-span-key-server");
private static final ContextKey<Span> CONSUMER_KEY =
ContextKey.named("opentelemetry-traces-span-key-consumer");

// TODO bridge these constants in AgentContextStorage
private static final ContextKey<Span> HTTP_KEY =
ContextKey.named("opentelemetry-traces-span-key-http");
private static final ContextKey<Span> RPC_KEY =
ContextKey.named("opentelemetry-traces-span-key-rpc");
private static final ContextKey<Span> DB_KEY =
ContextKey.named("opentelemetry-traces-span-key-db");
private static final ContextKey<Span> MESSAGING_KEY =
ContextKey.named("opentelemetry-traces-span-key-messaging");

// this is used instead of above, depending on the configuration value for
// otel.instrumentation.experimental.outgoing-span-suppression-by-type
private static final ContextKey<Span> CLIENT_KEY =
ContextKey.named("opentelemetry-traces-span-key-client");

private static final ContextKey<Span> PRODUCER_KEY =
ContextKey.named("opentelemetry-traces-span-key-producer");

public static final SpanKey SERVER = new SpanKey(SERVER_KEY);
public static final SpanKey CONSUMER = new SpanKey(CONSUMER_KEY);

static final SpanKey HTTP_CLIENT = new SpanKey(HTTP_KEY);
static final SpanKey RPC_CLIENT = new SpanKey(RPC_KEY);
static final SpanKey DB_CLIENT = new SpanKey(DB_KEY);
static final SpanKey MESSAGING_PRODUCER = new SpanKey(MESSAGING_KEY);

// this is used instead of above, depending on the configuration value for
// otel.instrumentation.experimental.outgoing-span-suppression-by-type
public static final SpanKey ALL_CLIENTS = new SpanKey(CLIENT_KEY);
public static final SpanKey ALL_PRODUCERS = new SpanKey(PRODUCER_KEY);

private final ContextKey<Span> key;

SpanKey(ContextKey<Span> key) {
this.key = key;
}

public Context storeInContext(Context context, Span span) {
return context.with(key, span);
}

@Nullable
public Span fromContextOrNull(Context context) {
return context.get(key);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.api.instrumenter;

import static java.util.Collections.singleton;

import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.context.Context;
import java.util.Set;

abstract class SpanSuppressionStrategy {
private static final SpanSuppressionStrategy SERVER_STRATEGY =
new SuppressIfSameSpanKeyStrategy(singleton(SpanKey.SERVER));
private static final SpanSuppressionStrategy CONSUMER_STRATEGY =
new StoreOnlyStrategy(singleton(SpanKey.CONSUMER));
private static final SpanSuppressionStrategy ALL_CLIENTS_STRATEGY =
new SuppressIfSameSpanKeyStrategy(singleton(SpanKey.ALL_CLIENTS));
private static final SpanSuppressionStrategy ALL_PRODUCERS_STRATEGY =
new SuppressIfSameSpanKeyStrategy(singleton(SpanKey.ALL_PRODUCERS));

public static final SpanSuppressionStrategy SUPPRESS_ALL_NESTED_OUTGOING_STRATEGY =
new CompositeSuppressionStrategy(
ALL_CLIENTS_STRATEGY, ALL_PRODUCERS_STRATEGY, SERVER_STRATEGY, CONSUMER_STRATEGY);

private static final SpanSuppressionStrategy NO_CLIENT_SUPPRESSION_STRATEGY =
new CompositeSuppressionStrategy(
NoopSuppressionStrategy.INSTANCE,
NoopSuppressionStrategy.INSTANCE,
SERVER_STRATEGY,
CONSUMER_STRATEGY);

static SpanSuppressionStrategy from(Set<SpanKey> clientSpanKeys) {
if (clientSpanKeys.isEmpty()) {
return NO_CLIENT_SUPPRESSION_STRATEGY;
}

SpanSuppressionStrategy clientOrProducerStrategy =
new SuppressIfSameSpanKeyStrategy(clientSpanKeys);
return new CompositeSuppressionStrategy(
clientOrProducerStrategy, clientOrProducerStrategy, SERVER_STRATEGY, CONSUMER_STRATEGY);
}

abstract Context storeInContext(Context context, SpanKind spanKind, Span span);

abstract boolean shouldSuppress(Context parentContext, SpanKind spanKind);
}
Loading

0 comments on commit 080c85d

Please sign in to comment.