Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Global attributes supplier #135

Merged
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 @@ -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"));
}
}