Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ public static <REQUEST, RESPONSE> InstrumenterBuilder<REQUEST, RESPONSE> builder
private final AttributesExtractor<? super REQUEST, ? super RESPONSE>[] attributesExtractors;
private final ContextCustomizer<? super REQUEST>[] contextCustomizers;
private final OperationListener[] operationListeners;
private final AttributesExtractor<? super REQUEST, ? super RESPONSE>[]
operationListenerAttributesExtractors;
private final ErrorCauseExtractor errorCauseExtractor;
private final boolean propagateOperationListenersToOnEnd;
private final boolean enabled;
Expand All @@ -94,6 +96,8 @@ public static <REQUEST, RESPONSE> InstrumenterBuilder<REQUEST, RESPONSE> builder
this.attributesExtractors = builder.attributesExtractors.toArray(new AttributesExtractor[0]);
this.contextCustomizers = builder.contextCustomizers.toArray(new ContextCustomizer[0]);
this.operationListeners = builder.buildOperationListeners().toArray(new OperationListener[0]);
this.operationListenerAttributesExtractors =
builder.operationListenerAttributesExtractors.toArray(new AttributesExtractor[0]);
this.errorCauseExtractor = builder.errorCauseExtractor;
this.propagateOperationListenersToOnEnd = builder.propagateOperationListenersToOnEnd;
this.enabled = builder.enabled;
Expand Down Expand Up @@ -207,11 +211,21 @@ private Context doStartImpl(Context parentContext, REQUEST request, @Nullable In
context = context.with(span);

if (operationListeners.length != 0) {
if (operationListenerAttributesExtractors.length != 0) {
UnsafeAttributes operationAttributes = new UnsafeAttributes();
operationAttributes.putAll(attributes.asMap());
for (AttributesExtractor<? super REQUEST, ? super RESPONSE> extractor :
operationListenerAttributesExtractors) {
extractor.onStart(operationAttributes, parentContext, request);
}
attributes = operationAttributes;
}

// operation listeners run after span start, so that they have access to the current span
// for capturing exemplars
long startNanos = getNanos(startTime);
for (int i = 0; i < operationListeners.length; i++) {
context = operationListeners[i].onStart(context, attributes, startNanos);
for (OperationListener operationListener : operationListeners) {
context = operationListener.onStart(context, attributes, startNanos);
}
}
if (propagateOperationListenersToOnEnd || context.get(START_OPERATION_LISTENERS) != null) {
Expand Down Expand Up @@ -258,6 +272,16 @@ private void doEnd(
operationListeners = this.operationListeners;
}
if (operationListeners.length != 0) {
if (operationListenerAttributesExtractors.length != 0) {
UnsafeAttributes operationAttributes = new UnsafeAttributes();
operationAttributes.putAll(attributes.asMap());
for (AttributesExtractor<? super REQUEST, ? super RESPONSE> extractor :
operationListenerAttributesExtractors) {
extractor.onEnd(operationAttributes, context, request, response, error);
}
attributes = operationAttributes;
}

long endNanos = getNanos(endTime);
for (int i = operationListeners.length - 1; i >= 0; i--) {
operationListeners[i].onEnd(context, attributes, endNanos);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import io.opentelemetry.context.propagation.TextMapSetter;
import io.opentelemetry.instrumentation.api.internal.ConfigPropertiesUtil;
import io.opentelemetry.instrumentation.api.internal.EmbeddedInstrumentationProperties;
import io.opentelemetry.instrumentation.api.internal.Experimental;
import io.opentelemetry.instrumentation.api.internal.InstrumenterBuilderAccess;
import io.opentelemetry.instrumentation.api.internal.InstrumenterUtil;
import io.opentelemetry.instrumentation.api.internal.InternalInstrumenterCustomizer;
Expand Down Expand Up @@ -60,6 +61,8 @@ public final class InstrumenterBuilder<REQUEST, RESPONSE> {
final List<SpanLinksExtractor<? super REQUEST>> spanLinksExtractors = new ArrayList<>();
final List<AttributesExtractor<? super REQUEST, ? super RESPONSE>> attributesExtractors =
new ArrayList<>();
final List<AttributesExtractor<? super REQUEST, ? super RESPONSE>>
operationListenerAttributesExtractors = new ArrayList<>();
final List<ContextCustomizer<? super REQUEST>> contextCustomizers = new ArrayList<>();
private final List<OperationListener> operationListeners = new ArrayList<>();
private final List<OperationMetrics> operationMetrics = new ArrayList<>();
Expand All @@ -73,6 +76,14 @@ public final class InstrumenterBuilder<REQUEST, RESPONSE> {
boolean propagateOperationListenersToOnEnd = false;
boolean enabled = true;

{
Experimental.internalAddOperationListenerAttributesExtractor(
(builder, operationListenerAttributesExtractor) ->
this.operationListenerAttributesExtractors.add(
requireNonNull(
operationListenerAttributesExtractor, "operationListenerAttributesExtractor")));
}

InstrumenterBuilder(
OpenTelemetry openTelemetry,
String instrumentationName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@

package io.opentelemetry.instrumentation.api.internal;

import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder;
import io.opentelemetry.instrumentation.api.instrumenter.OperationListener;
import io.opentelemetry.instrumentation.api.semconv.http.HttpClientAttributesExtractorBuilder;
import io.opentelemetry.instrumentation.api.semconv.http.HttpSpanNameExtractorBuilder;
import java.util.function.BiConsumer;
Expand All @@ -25,6 +28,10 @@ public final class Experimental {
private static volatile BiConsumer<HttpSpanNameExtractorBuilder<?>, Function<?, String>>
urlTemplateExtractorSetter;

@Nullable
private static volatile BiConsumer<InstrumenterBuilder<?, ?>, AttributesExtractor<?, ?>>
operationListenerAttributesExtractorAdder;

private Experimental() {}

public static void setRedactQueryParameters(
Expand Down Expand Up @@ -54,4 +61,28 @@ public static <REQUEST> void internalSetUrlTemplateExtractor(
urlTemplateExtractorSetter) {
Experimental.urlTemplateExtractorSetter = (BiConsumer) urlTemplateExtractorSetter;
}

/**
* Add an {@link AttributesExtractor} to the given {@link InstrumenterBuilder} that provides
* attributes that are passed to the {@link OperationListener}s. This can be used to add
* attributes to the metrics without adding them to the span. To add attributes to the span use
* {@link InstrumenterBuilder#addAttributesExtractor(AttributesExtractor)}.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you think this is worth documenting here or here too, or would it be better to wait until we decide to make it not experimental?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can wait with this. Firstly need to decide whether we want to add something like this at all.

*/
public static <REQUEST, RESPONSE> void addOperationListenerAttributesExtractor(
InstrumenterBuilder<REQUEST, RESPONSE> builder,
AttributesExtractor<? super REQUEST, ? super RESPONSE> attributesExtractor) {
if (operationListenerAttributesExtractorAdder != null) {
operationListenerAttributesExtractorAdder.accept(builder, attributesExtractor);
}
}

@SuppressWarnings({"rawtypes", "unchecked"})
public static <REQUEST, RESPONSE> void internalAddOperationListenerAttributesExtractor(
BiConsumer<
InstrumenterBuilder<REQUEST, RESPONSE>,
AttributesExtractor<? super REQUEST, ? super RESPONSE>>
operationListenerAttributesExtractorAdder) {
Experimental.operationListenerAttributesExtractorAdder =
(BiConsumer) operationListenerAttributesExtractorAdder;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import io.opentelemetry.context.Context;
import io.opentelemetry.context.ContextKey;
import io.opentelemetry.context.propagation.TextMapGetter;
import io.opentelemetry.instrumentation.api.internal.Experimental;
import io.opentelemetry.instrumentation.api.internal.SchemaUrlProvider;
import io.opentelemetry.instrumentation.api.internal.SpanKey;
import io.opentelemetry.instrumentation.api.internal.SpanKeyProvider;
Expand Down Expand Up @@ -503,6 +504,69 @@ public void onEnd(Context context, Attributes endAttributes, long endNanos) {
assertThat(Span.fromContext(endContext.get()).getSpanContext().isValid()).isTrue();
}

@Test
void operationListenerAttributeExtractors() {
AtomicReference<Attributes> startContext = new AtomicReference<>();
AtomicReference<Attributes> endContext = new AtomicReference<>();

OperationListener operationListener =
new OperationListener() {
@Override
public Context onStart(Context context, Attributes startAttributes, long startNanos) {
startContext.set(startAttributes);
return context;
}

@Override
public void onEnd(Context context, Attributes endAttributes, long endNanos) {
endContext.set(endAttributes);
}
};

InstrumenterBuilder<Map<String, String>, Map<String, String>> builder =
Instrumenter.<Map<String, String>, Map<String, String>>builder(
otelTesting.getOpenTelemetry(), "test", unused -> "span")
.addOperationListener(operationListener)
.addAttributesExtractor(new AttributesExtractor1());
Experimental.addOperationListenerAttributesExtractor(builder, new AttributesExtractor2());
Instrumenter<Map<String, String>, Map<String, String>> instrumenter =
builder.buildServerInstrumenter(new MapGetter());

Context context = instrumenter.start(Context.root(), REQUEST);
SpanContext spanContext = Span.fromContext(context).getSpanContext();
instrumenter.end(context, REQUEST, RESPONSE, null);

otelTesting
.assertTraces()
.hasTracesSatisfyingExactly(
trace ->
trace.hasSpansSatisfyingExactly(
span ->
span.hasName("span")
.hasKind(SpanKind.SERVER)
.hasInstrumentationScopeInfo(InstrumentationScopeInfo.create("test"))
.hasTraceId(spanContext.getTraceId())
.hasSpanId(spanContext.getSpanId())
.hasParentSpanId(SpanId.getInvalid())
.hasStatus(StatusData.unset())
.hasAttributesSatisfyingExactly(
equalTo(AttributeKey.stringKey("req1"), "req1_value"),
equalTo(AttributeKey.stringKey("req2"), "req2_value"),
equalTo(AttributeKey.stringKey("resp1"), "resp1_value"),
equalTo(AttributeKey.stringKey("resp2"), "resp2_value"))));

assertThat(startContext.get())
.hasSize(3)
.containsEntry("req1", "req1_value")
.containsEntry("req2", "req2_2_value")
.containsEntry("req3", "req3_value");
assertThat(endContext.get())
.hasSize(3)
.containsEntry("resp1", "resp1_value")
.containsEntry("resp2", "resp2_2_value")
.containsEntry("resp3", "resp3_value");
}

@Test
void shouldNotAddInvalidLink() {
// given
Expand Down