diff --git a/api/all/src/main/java/io/opentelemetry/api/trace/DefaultTracer.java b/api/all/src/main/java/io/opentelemetry/api/trace/DefaultTracer.java index fc1150c6af2..7463d30a153 100644 --- a/api/all/src/main/java/io/opentelemetry/api/trace/DefaultTracer.java +++ b/api/all/src/main/java/io/opentelemetry/api/trace/DefaultTracer.java @@ -96,6 +96,11 @@ public NoopSpanBuilder setAttribute(AttributeKey key, T value) { return this; } + @Override + public NoopSpanBuilder setAllAttributes(Attributes attributes) { + return this; + } + @Override public NoopSpanBuilder setSpanKind(SpanKind spanKind) { return this; diff --git a/api/all/src/main/java/io/opentelemetry/api/trace/PropagatedSpan.java b/api/all/src/main/java/io/opentelemetry/api/trace/PropagatedSpan.java index c571f4e130b..24c145b05b5 100644 --- a/api/all/src/main/java/io/opentelemetry/api/trace/PropagatedSpan.java +++ b/api/all/src/main/java/io/opentelemetry/api/trace/PropagatedSpan.java @@ -66,6 +66,11 @@ public Span setAttribute(AttributeKey key, T value) { return this; } + @Override + public Span setAllAttributes(Attributes attributes) { + return this; + } + @Override public Span addEvent(String name) { return this; diff --git a/api/all/src/main/java/io/opentelemetry/api/trace/Span.java b/api/all/src/main/java/io/opentelemetry/api/trace/Span.java index 5a70d1ac979..033f561f8e0 100644 --- a/api/all/src/main/java/io/opentelemetry/api/trace/Span.java +++ b/api/all/src/main/java/io/opentelemetry/api/trace/Span.java @@ -167,6 +167,23 @@ default Span setAttribute(AttributeKey key, int value) { return setAttribute(key, (long) value); } + /** + * Sets attributes to the {@link Span}. If the {@link Span} previously contained a mapping for any + * of the keys, the old values are replaced by the specified values. + * + * @param attributes the attributes + * @return this. + */ + @SuppressWarnings("unchecked") + default Span setAllAttributes(Attributes attributes) { + if (attributes == null || attributes.isEmpty()) { + return this; + } + attributes.forEach( + (attributeKey, value) -> this.setAttribute((AttributeKey) attributeKey, value)); + return this; + } + /** * Adds an event to the {@link Span}. The timestamp of the event will be the current time. * diff --git a/api/all/src/main/java/io/opentelemetry/api/trace/SpanBuilder.java b/api/all/src/main/java/io/opentelemetry/api/trace/SpanBuilder.java index e4d56db7eb0..e0af5f2d85b 100644 --- a/api/all/src/main/java/io/opentelemetry/api/trace/SpanBuilder.java +++ b/api/all/src/main/java/io/opentelemetry/api/trace/SpanBuilder.java @@ -228,6 +228,23 @@ public interface SpanBuilder { */ SpanBuilder setAttribute(AttributeKey key, T value); + /** + * Sets attributes to the {@link SpanBuilder}. If the {@link SpanBuilder} previously contained a + * mapping for any of the keys, the old values are replaced by the specified values. + * + * @param attributes the attributes + * @return this. + */ + @SuppressWarnings("unchecked") + default SpanBuilder setAllAttributes(Attributes attributes) { + if (attributes == null || attributes.isEmpty()) { + return this; + } + attributes.forEach( + (attributeKey, value) -> setAttribute((AttributeKey) attributeKey, value)); + return this; + } + /** * Sets the {@link SpanKind} for the newly created {@code Span}. If not called, the implementation * will provide a default value {@link SpanKind#INTERNAL}. diff --git a/api/all/src/test/java/io/opentelemetry/api/trace/PropagatedSpanTest.java b/api/all/src/test/java/io/opentelemetry/api/trace/PropagatedSpanTest.java index 4e8ce8cbfa0..7c3807775c7 100644 --- a/api/all/src/test/java/io/opentelemetry/api/trace/PropagatedSpanTest.java +++ b/api/all/src/test/java/io/opentelemetry/api/trace/PropagatedSpanTest.java @@ -50,6 +50,10 @@ void doNotCrash() { span.setAttribute(longArrayKey("NullArrayLong"), null); span.setAttribute(doubleArrayKey("NullArrayDouble"), null); span.setAttribute((String) null, null); + span.setAllAttributes(null); + span.setAllAttributes(Attributes.empty()); + span.setAllAttributes( + Attributes.of(stringKey("MyStringAttributeKey"), "MyStringAttributeValue")); span.addEvent("event"); span.addEvent("event", 0, TimeUnit.NANOSECONDS); span.addEvent("event", Instant.EPOCH); diff --git a/api/all/src/test/java/io/opentelemetry/api/trace/SpanBuilderTest.java b/api/all/src/test/java/io/opentelemetry/api/trace/SpanBuilderTest.java index 0461e86d401..a7ed2dc27cb 100644 --- a/api/all/src/test/java/io/opentelemetry/api/trace/SpanBuilderTest.java +++ b/api/all/src/test/java/io/opentelemetry/api/trace/SpanBuilderTest.java @@ -52,6 +52,9 @@ void doNotCrash_NoopImplementation() { spanBuilder.setAttribute("key", .12345); spanBuilder.setAttribute("key", true); spanBuilder.setAttribute(stringKey("key"), "value"); + spanBuilder.setAllAttributes(Attributes.of(stringKey("key"), "value")); + spanBuilder.setAllAttributes(Attributes.empty()); + spanBuilder.setAllAttributes(null); spanBuilder.setStartTimestamp(12345L, TimeUnit.NANOSECONDS); spanBuilder.setStartTimestamp(Instant.EPOCH); spanBuilder.setStartTimestamp(null); diff --git a/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/RecordEventsReadableSpanTest.java b/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/RecordEventsReadableSpanTest.java index cab9e878016..866b872e2f7 100644 --- a/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/RecordEventsReadableSpanTest.java +++ b/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/RecordEventsReadableSpanTest.java @@ -474,6 +474,118 @@ void setAttribute_nullAttributeValue() { assertThat(span.toSpanData().getAttributes().size()).isEqualTo(9); } + @Test + void setAllAttributes() { + RecordEventsReadableSpan span = createTestRootSpan(); + Attributes attributes = + Attributes.builder() + .put("StringKey", "StringVal") + .put("NullStringKey", (String) null) + .put("EmptyStringKey", "") + .put(stringKey("NullStringAttributeValue"), null) + .put(stringKey("EmptyStringAttributeValue"), "") + .put("LongKey", 1000L) + .put(longKey("LongKey2"), 5) + .put(longKey("LongKey3"), 6L) + .put("DoubleKey", 10.0) + .put("BooleanKey", false) + .put( + stringArrayKey("ArrayStringKey"), + Arrays.asList("StringVal", null, "", "StringVal2")) + .put(longArrayKey("ArrayLongKey"), Arrays.asList(1L, 2L, 3L, 4L, 5L)) + .put(doubleArrayKey("ArrayDoubleKey"), Arrays.asList(0.1, 2.3, 4.5, 6.7, 8.9)) + .put(booleanArrayKey("ArrayBooleanKey"), Arrays.asList(true, false, false, true)) + // These should be dropped + .put(stringArrayKey("NullArrayStringKey"), null) + .put(longArrayKey("NullArrayLongKey"), null) + .put(doubleArrayKey("NullArrayDoubleKey"), null) + .put(booleanArrayKey("NullArrayBooleanKey"), null) + // These should be maintained + .put(longArrayKey("ArrayWithNullLongKey"), Arrays.asList(new Long[] {null})) + .put(stringArrayKey("ArrayWithNullStringKey"), Arrays.asList(new String[] {null})) + .put(doubleArrayKey("ArrayWithNullDoubleKey"), Arrays.asList(new Double[] {null})) + .put(booleanArrayKey("ArrayWithNullBooleanKey"), Arrays.asList(new Boolean[] {null})) + .build(); + + try { + span.setAllAttributes(attributes); + } finally { + span.end(); + } + + SpanData spanData = span.toSpanData(); + assertThat(spanData.getAttributes().size()).isEqualTo(16); + assertThat(spanData.getAttributes().get(stringKey("StringKey"))).isNotNull(); + assertThat(spanData.getAttributes().get(stringKey("EmptyStringKey"))).isNotNull(); + assertThat(spanData.getAttributes().get(stringKey("EmptyStringAttributeValue"))).isNotNull(); + assertThat(spanData.getAttributes().get(longKey("LongKey"))).isNotNull(); + assertThat(spanData.getAttributes().get(longKey("LongKey2"))).isEqualTo(5L); + assertThat(spanData.getAttributes().get(longKey("LongKey3"))).isEqualTo(6L); + assertThat(spanData.getAttributes().get(doubleKey("DoubleKey"))).isNotNull(); + assertThat(spanData.getAttributes().get(booleanKey("BooleanKey"))).isNotNull(); + assertThat(spanData.getAttributes().get(stringArrayKey("ArrayStringKey"))).isNotNull(); + assertThat(spanData.getAttributes().get(longArrayKey("ArrayLongKey"))).isNotNull(); + assertThat(spanData.getAttributes().get(doubleArrayKey("ArrayDoubleKey"))).isNotNull(); + assertThat(spanData.getAttributes().get(booleanArrayKey("ArrayBooleanKey"))).isNotNull(); + assertThat(spanData.getAttributes().get(longArrayKey("ArrayWithNullLongKey"))).isNotNull(); + assertThat(spanData.getAttributes().get(stringArrayKey("ArrayWithNullStringKey"))).isNotNull(); + assertThat(spanData.getAttributes().get(doubleArrayKey("ArrayWithNullDoubleKey"))).isNotNull(); + assertThat(spanData.getAttributes().get(booleanArrayKey("ArrayWithNullBooleanKey"))) + .isNotNull(); + assertThat(spanData.getAttributes().get(stringArrayKey("ArrayStringKey")).size()).isEqualTo(4); + assertThat(spanData.getAttributes().get(longArrayKey("ArrayLongKey")).size()).isEqualTo(5); + assertThat(spanData.getAttributes().get(doubleArrayKey("ArrayDoubleKey")).size()).isEqualTo(5); + assertThat(spanData.getAttributes().get(booleanArrayKey("ArrayBooleanKey")).size()) + .isEqualTo(4); + } + + @Test + void setAllAttributes_mergesAttributes() { + RecordEventsReadableSpan span = createTestRootSpan(); + Attributes attributes = + Attributes.builder() + .put("StringKey", "StringVal") + .put("LongKey", 1000L) + .put("DoubleKey", 10.0) + .put("BooleanKey", false) + .build(); + + try { + span.setAttribute("StringKey", "OtherStringVal") + .setAttribute("ExistingStringKey", "ExistingStringVal") + .setAttribute("LongKey", 2000L) + .setAllAttributes(attributes); + } finally { + span.end(); + } + + SpanData spanData = span.toSpanData(); + assertThat(spanData.getAttributes().size()).isEqualTo(5); + assertThat(spanData.getAttributes().get(stringKey("StringKey"))) + .isNotNull() + .isEqualTo("StringVal"); + assertThat(spanData.getAttributes().get(stringKey("ExistingStringKey"))) + .isNotNull() + .isEqualTo("ExistingStringVal"); + assertThat(spanData.getAttributes().get(longKey("LongKey"))).isNotNull().isEqualTo(1000L); + assertThat(spanData.getAttributes().get(doubleKey("DoubleKey"))).isNotNull().isEqualTo(10.0); + assertThat(spanData.getAttributes().get(booleanKey("BooleanKey"))).isNotNull().isEqualTo(false); + } + + @Test + void setAllAttributes_nullAttributes() { + RecordEventsReadableSpan span = createTestRootSpan(); + span.setAllAttributes(null); + assertThat(span.toSpanData().getAttributes().size()).isEqualTo(0); + } + + @Test + void setAllAttributes_emptyAttributes() { + RecordEventsReadableSpan span = createTestRootSpan(); + span.setAllAttributes(Attributes.empty()); + assertThat(span.toSpanData().getAttributes().size()).isEqualTo(0); + } + @Test void addEvent() { RecordEventsReadableSpan span = createTestRootSpan(); diff --git a/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkSpanBuilderTest.java b/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkSpanBuilderTest.java index eea69447240..bcb695bbd1f 100644 --- a/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkSpanBuilderTest.java +++ b/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkSpanBuilderTest.java @@ -418,6 +418,101 @@ public String getDescription() { assertThat(span.toSpanData().getAttributes().get(stringKey("cat"))).isEqualTo("meow"); } + @Test + void setAllAttributes() { + Attributes attributes = + Attributes.builder() + .put("string", "value") + .put("long", 12345L) + .put("double", .12345) + .put("boolean", true) + .put(stringKey("stringAttribute"), "attrvalue") + .build(); + + SpanBuilder spanBuilder = sdkTracer.spanBuilder(SPAN_NAME).setAllAttributes(attributes); + + RecordEventsReadableSpan span = (RecordEventsReadableSpan) spanBuilder.startSpan(); + try { + SpanData spanData = span.toSpanData(); + Attributes attrs = spanData.getAttributes(); + assertThat(attrs.size()).isEqualTo(5); + assertThat(attrs.get(stringKey("string"))).isEqualTo("value"); + assertThat(attrs.get(longKey("long"))).isEqualTo(12345L); + assertThat(attrs.get(doubleKey("double"))).isEqualTo(0.12345); + assertThat(attrs.get(booleanKey("boolean"))).isEqualTo(true); + assertThat(attrs.get(stringKey("stringAttribute"))).isEqualTo("attrvalue"); + assertThat(spanData.getTotalAttributeCount()).isEqualTo(5); + } finally { + span.end(); + } + } + + @Test + void setAllAttributes_mergesAttributes() { + Attributes attributes = + Attributes.builder() + .put("string", "value") + .put("long", 12345L) + .put("double", .12345) + .put("boolean", true) + .put(stringKey("stringAttribute"), "attrvalue") + .build(); + + SpanBuilder spanBuilder = + sdkTracer + .spanBuilder(SPAN_NAME) + .setAttribute("string", "otherValue") + .setAttribute("boolean", false) + .setAttribute("existingString", "existingValue") + .setAllAttributes(attributes); + + RecordEventsReadableSpan span = (RecordEventsReadableSpan) spanBuilder.startSpan(); + try { + SpanData spanData = span.toSpanData(); + Attributes attrs = spanData.getAttributes(); + assertThat(attrs.size()).isEqualTo(6); + assertThat(attrs.get(stringKey("string"))).isEqualTo("value"); + assertThat(attrs.get(stringKey("existingString"))).isEqualTo("existingValue"); + assertThat(attrs.get(longKey("long"))).isEqualTo(12345L); + assertThat(attrs.get(doubleKey("double"))).isEqualTo(0.12345); + assertThat(attrs.get(booleanKey("boolean"))).isEqualTo(true); + assertThat(attrs.get(stringKey("stringAttribute"))).isEqualTo("attrvalue"); + assertThat(spanData.getTotalAttributeCount()).isEqualTo(8); + } finally { + span.end(); + } + } + + @Test + void setAllAttributes_nullAttributes() { + SpanBuilder spanBuilder = sdkTracer.spanBuilder(SPAN_NAME).setAllAttributes(null); + + RecordEventsReadableSpan span = (RecordEventsReadableSpan) spanBuilder.startSpan(); + try { + SpanData spanData = span.toSpanData(); + Attributes attrs = spanData.getAttributes(); + assertThat(attrs.size()).isEqualTo(0); + assertThat(spanData.getTotalAttributeCount()).isEqualTo(0); + } finally { + span.end(); + } + } + + @Test + void setAllAttributes_emptyAttributes() { + SpanBuilder spanBuilder = sdkTracer.spanBuilder(SPAN_NAME).setAllAttributes(Attributes.empty()); + + RecordEventsReadableSpan span = (RecordEventsReadableSpan) spanBuilder.startSpan(); + try { + SpanData spanData = span.toSpanData(); + Attributes attrs = spanData.getAttributes(); + assertThat(attrs.size()).isEqualTo(0); + assertThat(spanData.getTotalAttributeCount()).isEqualTo(0); + } finally { + span.end(); + } + } + @Test void recordEvents_default() { Span span = sdkTracer.spanBuilder(SPAN_NAME).startSpan();