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
4 changes: 2 additions & 2 deletions core/api/core.api
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,8 @@ public class io/opentelemetry/android/config/OtelRumConfig {
public fun suppressInstrumentation (Ljava/lang/String;)Lio/opentelemetry/android/config/OtelRumConfig;
}

public class io/opentelemetry/android/export/AttributeModifyingSpanExporter : io/opentelemetry/sdk/trace/export/SpanExporter {
public fun <init> (Lio/opentelemetry/sdk/trace/export/SpanExporter;Ljava/util/Map;)V
public final class io/opentelemetry/android/export/AttributeModifyingSpanExporter : io/opentelemetry/sdk/trace/export/SpanExporter {
public fun <init> (Lio/opentelemetry/sdk/trace/export/SpanExporter;Lkotlin/jvm/functions/Function2;)V
public fun export (Ljava/util/Collection;)Lio/opentelemetry/sdk/common/CompletableResultCode;
public fun flush ()Lio/opentelemetry/sdk/common/CompletableResultCode;
public fun shutdown ()Lio/opentelemetry/sdk/common/CompletableResultCode;
Expand Down

This file was deleted.

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

package io.opentelemetry.android.export

import io.opentelemetry.api.common.AttributeKey
import io.opentelemetry.api.common.Attributes
import io.opentelemetry.sdk.common.CompletableResultCode
import io.opentelemetry.sdk.trace.data.SpanData
import io.opentelemetry.sdk.trace.export.SpanExporter

typealias SpanAttributeReplacement = (AttributeKey<*>, Any?) -> Any?

/**
* A SpanExporter that is configured to modify some of its attributes at export time.
*/
class AttributeModifyingSpanExporter(
private val delegate: SpanExporter,
private val spanAttributeReplacements: SpanAttributeReplacement,
) : SpanExporter {
override fun export(spans: Collection<SpanData>): CompletableResultCode = delegate.export(spans.map(::buildModifiedAttributes))

private fun buildModifiedAttributes(span: SpanData): ModifiedSpanData {
val originals = span.attributes.asMap()
val modified =
originals.mapValues { entry ->
spanAttributeReplacements(entry.key, entry.value)
}

val builder = Attributes.builder()
modified.forEach {
@Suppress("UNCHECKED_CAST")
builder.put(it.key as AttributeKey<Any>, it.value)
}
return ModifiedSpanData(span, builder.build())
}

override fun flush(): CompletableResultCode = delegate.flush()

override fun shutdown(): CompletableResultCode = delegate.shutdown()
}

This file was deleted.

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

package io.opentelemetry.android.export

import io.opentelemetry.api.common.Attributes
import io.opentelemetry.sdk.trace.data.DelegatingSpanData
import io.opentelemetry.sdk.trace.data.SpanData

internal class ModifiedSpanData(
original: SpanData,
private val modifiedAttributes: Attributes,
) : DelegatingSpanData(original) {
override fun getAttributes(): Attributes = modifiedAttributes

override fun getTotalAttributeCount(): Int = modifiedAttributes.size()
}
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,18 @@ public SpanExporter build() {
if (!spanAttributeReplacements.isEmpty()) {
modifier =
new AttributeModifyingSpanExporter(
delegate, new HashMap<>(spanAttributeReplacements));
delegate,
(attributeKey, value) -> {
//noinspection unchecked
Function<Object, Object> function =
(Function<Object, Object>)
spanAttributeReplacements.get(attributeKey);
if (function != null) {
return function.apply(value);
} else {
return value;
Comment on lines +143 to +144
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: Prefer omitting the redundant else (and yeah, if it was kotlin it would be even cleaner to return from the if I suppose).

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't see how this else is redundant as the lambda requires a return statement if function is null. Have I missed some syntax that would simplify this in Java?

Copy link
Contributor

Choose a reason for hiding this comment

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

I think he's talking about writing it like this:

if (function != null) {
   return function.apply(value);
}
return value;

Maybe it sounded like omitting the "whole else", which might be confusing.

}
});
}
return FilteringSpanExporter.builder(modifier)
.rejectSpansWithAttributesMatching(new HashMap<>(rejectSpanAttributesPredicates))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,64 +10,44 @@ import io.opentelemetry.api.common.AttributeKey
import io.opentelemetry.api.common.Attributes
import io.opentelemetry.sdk.common.CompletableResultCode
import io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions
import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter
import io.opentelemetry.sdk.trace.data.SpanData
import io.opentelemetry.sdk.trace.export.SpanExporter
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.ThrowingConsumer
import org.junit.jupiter.api.Assertions.assertSame
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.mockito.ArgumentCaptor
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.mock
import org.mockito.Mockito.`when`
import org.mockito.junit.jupiter.MockitoExtension
import java.util.function.Function

@ExtendWith(MockitoExtension::class)
internal class AttributeModifyingSpanExporterTest {
@Mock
private lateinit var exporter: SpanExporter
private lateinit var exporter: InMemorySpanExporter

@Captor
private lateinit var spansCaptor: ArgumentCaptor<MutableCollection<SpanData>>
@BeforeEach
fun setUp() {
exporter = InMemorySpanExporter.create()
}

@Test
fun testEmptyMap() {
val span1 = span("span1")
val span2 = span("span2")
val span3 = span("span3")
val spans = listOf(span1, span2, span3)
val expectedResult: CompletableResultCode? = mock(CompletableResultCode::class.java)
`when`(exporter.export(spans)).thenReturn(expectedResult)

val underTest =
AttributeModifyingSpanExporter(exporter, emptyMap())
val underTest = AttributeModifyingSpanExporter(exporter) { _, value -> value }

val result = underTest.export(spans)
assertSame(expectedResult, result)
assertSame(CompletableResultCode.ofSuccess(), result)
}

@Test
fun testRemappedToNull() {
val key = AttributeKey.stringKey("foo")
val span1 = span("span1", Attributes.of(key, "bar"))
val originalSpans = listOf(span1)
val underTest = AttributeModifyingSpanExporter(exporter) { _, value -> null }
val result = underTest.export(originalSpans)

val remappers = mutableMapOf<AttributeKey<*>, Function<*, *>>()
remappers.put(key, Function { _: Any? -> null })

val expectedResult = Mockito.mock(CompletableResultCode::class.java)
`when`(exporter.export(spansCaptor.capture()))
.thenReturn(expectedResult)

val underTest =
AttributeModifyingSpanExporter(exporter, remappers)

underTest.export(originalSpans)
assertThat(spansCaptor.getValue())
assertSame(CompletableResultCode.ofSuccess(), result)
assertThat(exporter.finishedSpanItems)
.satisfiesExactly(
ThrowingConsumer { span: SpanData? ->
OpenTelemetryAssertions
Expand All @@ -87,23 +67,23 @@ internal class AttributeModifyingSpanExporterTest {
val attr3 = buildAttr(3)
val span3 = span("span3", attr3)
val spans = listOf(span1, span2, span3)
val modifiers = mutableMapOf<AttributeKey<*>, Function<*, *>>()
modifiers.put(AttributeKey.stringKey("foo1"), Function { x: Any? -> "" + x + x })
modifiers.put(AttributeKey.stringKey("foo3"), Function { x: Any? -> "3$x$x" })
modifiers.put(AttributeKey.stringKey("boop2"), Function { x: Any? -> "2$x$x" })

val expectedResult = Mockito.mock(CompletableResultCode::class.java)
`when`(exporter.export(spansCaptor.capture()))
.thenReturn(expectedResult)

val underTest = AttributeModifyingSpanExporter(exporter, modifiers)
val underTest =
AttributeModifyingSpanExporter(exporter) { key, value ->
when (key) {
AttributeKey.stringKey("foo1") -> "$value$value"
AttributeKey.stringKey("foo3") -> "3$value$value"
AttributeKey.stringKey("boop2") -> "2$value$value"
else -> value
}
}
val result = underTest.export(spans)
assertSame(expectedResult, result)
assertSame(CompletableResultCode.ofSuccess(), result)
assertAttributes()
}

private fun assertAttributes() {
assertThat(spansCaptor.getValue())
assertThat(exporter.finishedSpanItems)
.satisfiesExactly(
ThrowingConsumer {
OpenTelemetryAssertions
Expand Down