Skip to content

Commit

Permalink
Merge pull request #135 from breedx-splk/global_attributes_supplier
Browse files Browse the repository at this point in the history
Global attributes supplier
  • Loading branch information
breedx-splk authored Nov 2, 2023
2 parents d41a161 + 5d4e6fe commit a6afa4d
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@

package io.opentelemetry.android;

import static java.util.Objects.requireNonNull;

import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.context.Context;
Expand All @@ -15,11 +13,13 @@
import io.opentelemetry.sdk.trace.SpanProcessor;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.Supplier;

/**
* A {@link SpanProcessor} implementation that appends a set of {@linkplain Attributes attributes}
* to every span that is exported. The attributes collection is mutable, and can be updated by
* calling {@link #update(Consumer)}.
* to every span that is exported. The attributes are supplied via Supplier. This Supplier may alter
* its results and return different attributes over time. collection is mutable, and can be updated
* by calling {@link #update(Consumer)}.
*
* <p>This class is internal and is hence not for public use. Its APIs are unstable and can change
* at any time.
Expand All @@ -32,18 +32,39 @@ public final class GlobalAttributesSpanAppender implements SpanProcessor {
* @param initialState The initial collection of attributes to append to every span.
*/
public static GlobalAttributesSpanAppender create(Attributes initialState) {
return new GlobalAttributesSpanAppender(initialState);
return create(() -> initialState);
}

/**
* Returns a new {@link GlobalAttributesSpanAppender} which calls the given supplier to populate
* the global attributes;
*
* @param attributeSupplier a Supplier of Attributes to be placed on every span.
*/
public static GlobalAttributesSpanAppender create(Supplier<Attributes> attributeSupplier) {
return new GlobalAttributesSpanAppender(attributeSupplier);
}

private final AtomicReference<Attributes> attributes;
private final AtomicReference<Supplier<Attributes>> attributesSupplier;

private GlobalAttributesSpanAppender(Attributes initialState) {
this.attributes = new AtomicReference<>(initialState);
private GlobalAttributesSpanAppender(Supplier<Attributes> initialState) {
this.attributesSupplier = new AtomicReference<>(initialState);
}

@Override
public void onStart(Context parentContext, ReadWriteSpan span) {
span.setAllAttributes(attributes.get());
span.setAllAttributes(getAttributes());
}

private Attributes getAttributes() {
Supplier<Attributes> supplier = attributesSupplier.get();
if (supplier != null) {
Attributes result = supplier.get();
if (result != null) {
return result;
}
}
return Attributes.empty();
}

@Override
Expand All @@ -60,26 +81,32 @@ public boolean isEndRequired() {
}

/**
* Update the global set of attributes that will be appended to every span.
* Update the global set of attributes to be appended to every span.
*
* <p>Note: this operation performs an atomic update. The passed function should be free from
* side effects, since it may be called multiple times in case of thread contention.
* <p>Note: Calling this method invalidates the Supplier originally passed to this {@link
* GlobalAttributesSpanAppender} and any other previously updated Supplier.
*
* @param attributesUpdater A function which will update the current set of attributes, by
* operating on a {@link AttributesBuilder} from the current set.
*/
public void update(Consumer<AttributesBuilder> attributesUpdater) {
while (true) {
// we're absolutely certain this will never be null
Attributes oldAttributes = requireNonNull(attributes.get());
synchronized (attributesSupplier) {
Attributes oldAttributes = getAttributes();

AttributesBuilder builder = oldAttributes.toBuilder();
attributesUpdater.accept(builder);
Attributes newAttributes = builder.build();

if (attributes.compareAndSet(oldAttributes, newAttributes)) {
break;
}
attributesSupplier.set(() -> newAttributes);
}
}

/**
* Replaces the currently configured attributes Supplier with a new one.
*
* @param attributesSupplier Supplier to call to obtain Attributes for every span.
*/
public void update(Supplier<Attributes> attributesSupplier) {
this.attributesSupplier.set(attributesSupplier);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,6 @@ static OpenTelemetryRumBuilder builder(Application application) {
* OpenTelemetry SDK but would still prefer to allow OpenTelemetry RUM to create the SDK for
* you. If you would like to "bring your own" SDK, call the two-argument version that takes the
* SDK as a parameter.
*
* @param application
* @param config
* @return
*/
static OpenTelemetryRumBuilder builder(Application application, OtelRumConfig config) {
return new OpenTelemetryRumBuilder(application, config);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ private void applyConfiguration() {
if (config.hasGlobalAttributes()) {
// Add span processor that appends global attributes.
GlobalAttributesSpanAppender appender =
GlobalAttributesSpanAppender.create(config.getGlobalAttributes());
GlobalAttributesSpanAppender.create(config.getGlobalAttributesSupplier());
addTracerProviderCustomizer(
(tracerProviderBuilder, app) ->
tracerProviderBuilder.addSpanProcessor(appender));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import io.opentelemetry.android.instrumentation.network.CurrentNetworkProvider;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.common.AttributesBuilder;
import java.util.function.Supplier;

/**
* Configuration object for OpenTelemetry Android. The configuration items in this class will be
Expand All @@ -16,7 +16,7 @@
*/
public class OtelRumConfig {

private AttributesBuilder globalAttributes = Attributes.builder();
private Supplier<Attributes> globalAttributesSupplier = Attributes::empty;
private boolean includeNetworkAttributes = true;
private boolean generateSdkInitializationEvents = true;
private boolean includeScreenAttributes = true;
Expand All @@ -26,16 +26,21 @@ public class OtelRumConfig {
* configured attributes will be dropped. Default = none.
*/
public OtelRumConfig setGlobalAttributes(Attributes attributes) {
globalAttributes = attributes.toBuilder();
return setGlobalAttributes(() -> attributes);
}

public OtelRumConfig setGlobalAttributes(Supplier<Attributes> globalAttributesSupplier) {
this.globalAttributesSupplier = globalAttributesSupplier;
return this;
}

boolean hasGlobalAttributes() {
return !globalAttributes.build().isEmpty();
Attributes attributes = globalAttributesSupplier.get();
return attributes != null && !attributes.isEmpty();
}

Attributes getGlobalAttributes() {
return globalAttributes.build();
Supplier<Attributes> getGlobalAttributesSupplier() {
return globalAttributesSupplier;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,15 @@
import static io.opentelemetry.api.common.AttributeKey.stringKey;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.context.Context;
import io.opentelemetry.sdk.trace.ReadWriteSpan;
import java.util.function.Supplier;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
Expand All @@ -24,11 +28,10 @@ class GlobalAttributesSpanAppenderTest {

@Mock private ReadWriteSpan span;

private final GlobalAttributesSpanAppender globalAttributes =
GlobalAttributesSpanAppender.create(Attributes.of(stringKey("key"), "value"));

@Test
void shouldAppendGlobalAttributes() {
GlobalAttributesSpanAppender globalAttributes =
GlobalAttributesSpanAppender.create(Attributes.of(stringKey("key"), "value"));
globalAttributes.update(attributesBuilder -> attributesBuilder.put("key", "value2"));
globalAttributes.update(
attributesBuilder -> attributesBuilder.put(longKey("otherKey"), 1234L));
Expand All @@ -42,4 +45,46 @@ void shouldAppendGlobalAttributes() {

assertFalse(globalAttributes.isEndRequired());
}

@Test
void createWithSupplier() {
Attributes attrs = Attributes.of(stringKey("foo"), "bar");
GlobalAttributesSpanAppender globalAttributes =
GlobalAttributesSpanAppender.create(() -> attrs);

globalAttributes.onStart(Context.root(), span);
verify(span).setAllAttributes(Attributes.of(stringKey("foo"), "bar"));
}

@Test
void updateWithSupplierReplacesSupplier() {
Attributes attrs = Attributes.of(stringKey("foo"), "bar");
Supplier<Attributes> originalSupplier = () -> fail("Should not have been called");

GlobalAttributesSpanAppender globalAttributes =
GlobalAttributesSpanAppender.create(originalSupplier);
globalAttributes.update(() -> attrs);

globalAttributes.onStart(Context.root(), span);
verify(span).setAllAttributes(Attributes.of(stringKey("foo"), "bar"));
}

@Test
void updateWithAttributesReplacesSupplier() {
Attributes attrs = Attributes.of(stringKey("foo"), "bar");
Attributes extra = Attributes.of(stringKey("bar"), "baz");
Supplier<Attributes> originalSupplier = mock(Supplier.class);

when(originalSupplier.get())
.thenReturn(attrs)
.thenThrow(new RuntimeException("Should not have been called again."));

GlobalAttributesSpanAppender globalAttributes =
GlobalAttributesSpanAppender.create(originalSupplier);
globalAttributes.update(builder -> builder.putAll(extra));

globalAttributes.onStart(Context.root(), span);
verify(span)
.setAllAttributes(Attributes.of(stringKey("foo"), "bar", stringKey("bar"), "baz"));
}
}

0 comments on commit a6afa4d

Please sign in to comment.