From e60084112e176a93a1496aa8ed04ce2ddf860b64 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Thu, 10 Nov 2022 10:28:45 +0000 Subject: [PATCH] Avoid NPE when TraceContext has deferred sampling Closes gh-33093 --- .../PrometheusExemplarsAutoConfiguration.java | 6 +- .../LazyTracingSpanContextSupplierTests.java | 151 ++++++++++++++++++ 2 files changed, 156 insertions(+), 1 deletion(-) create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/prometheus/LazyTracingSpanContextSupplierTests.java diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/prometheus/PrometheusExemplarsAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/prometheus/PrometheusExemplarsAutoConfiguration.java index e579160d11..9032a0712a 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/prometheus/PrometheusExemplarsAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/prometheus/PrometheusExemplarsAutoConfiguration.java @@ -80,7 +80,11 @@ public class PrometheusExemplarsAutoConfiguration { @Override public boolean isSampled() { Span currentSpan = currentSpan(); - return currentSpan != null && currentSpan.context().sampled(); + if (currentSpan == null) { + return false; + } + Boolean sampled = currentSpan.context().sampled(); + return sampled != null && sampled; } private Span currentSpan() { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/prometheus/LazyTracingSpanContextSupplierTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/prometheus/LazyTracingSpanContextSupplierTests.java new file mode 100644 index 0000000000..34cc42b7cd --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/prometheus/LazyTracingSpanContextSupplierTests.java @@ -0,0 +1,151 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.tracing.prometheus; + +import io.micrometer.tracing.Span; +import io.micrometer.tracing.TraceContext; +import io.micrometer.tracing.Tracer; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.actuate.autoconfigure.tracing.prometheus.PrometheusExemplarsAutoConfiguration.LazyTracingSpanContextSupplier; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link LazyTracingSpanContextSupplier}. + * + * @author Andy Wilkinson + */ +class LazyTracingSpanContextSupplierTests { + + private final Tracer tracer = mock(Tracer.class); + + private final ObjectProvider objectProvider = new ObjectProvider<>() { + + @Override + public Tracer getObject() throws BeansException { + return LazyTracingSpanContextSupplierTests.this.tracer; + } + + @Override + public Tracer getObject(Object... args) throws BeansException { + return LazyTracingSpanContextSupplierTests.this.tracer; + } + + @Override + public Tracer getIfAvailable() throws BeansException { + return LazyTracingSpanContextSupplierTests.this.tracer; + } + + @Override + public Tracer getIfUnique() throws BeansException { + return LazyTracingSpanContextSupplierTests.this.tracer; + } + + }; + + private final LazyTracingSpanContextSupplier spanContextSupplier = new LazyTracingSpanContextSupplier( + this.objectProvider); + + @Test + void whenCurrentSpanIsNullThenSpanIdIsNull() { + assertThat(this.spanContextSupplier.getSpanId()).isNull(); + } + + @Test + void whenCurrentSpanIsNullThenTraceIdIsNull() { + assertThat(this.spanContextSupplier.getTraceId()).isNull(); + } + + @Test + void whenCurrentSpanIsNullThenSampledIsFalse() { + assertThat(this.spanContextSupplier.isSampled()).isFalse(); + } + + @Test + void whenCurrentSpanHasSpanIdThenSpanIdIsFromSpan() { + Span span = mock(Span.class); + given(this.tracer.currentSpan()).willReturn(span); + TraceContext traceContext = mock(TraceContext.class); + given(traceContext.spanId()).willReturn("span-id"); + given(span.context()).willReturn(traceContext); + assertThat(this.spanContextSupplier.getSpanId()).isEqualTo("span-id"); + } + + @Test + void whenCurrentSpanHasTraceIdThenTraceIdIsFromSpan() { + Span span = mock(Span.class); + given(this.tracer.currentSpan()).willReturn(span); + TraceContext traceContext = mock(TraceContext.class); + given(traceContext.traceId()).willReturn("trace-id"); + given(span.context()).willReturn(traceContext); + assertThat(this.spanContextSupplier.getTraceId()).isEqualTo("trace-id"); + } + + @Test + void whenCurrentSpanHasNoSpanIdThenSpanIdIsNull() { + Span span = mock(Span.class); + given(this.tracer.currentSpan()).willReturn(span); + TraceContext traceContext = mock(TraceContext.class); + given(span.context()).willReturn(traceContext); + assertThat(this.spanContextSupplier.getSpanId()).isNull(); + } + + @Test + void whenCurrentSpanHasNoTraceIdThenTraceIdIsFNull() { + Span span = mock(Span.class); + given(this.tracer.currentSpan()).willReturn(span); + TraceContext traceContext = mock(TraceContext.class); + given(span.context()).willReturn(traceContext); + assertThat(this.spanContextSupplier.getTraceId()).isNull(); + } + + @Test + void whenCurrentSpanIsSampledThenSampledIsTrue() { + Span span = mock(Span.class); + given(this.tracer.currentSpan()).willReturn(span); + TraceContext traceContext = mock(TraceContext.class); + given(traceContext.sampled()).willReturn(true); + given(span.context()).willReturn(traceContext); + assertThat(this.spanContextSupplier.isSampled()).isTrue(); + } + + @Test + void whenCurrentSpanIsNotSampledThenSampledIsFalse() { + Span span = mock(Span.class); + given(this.tracer.currentSpan()).willReturn(span); + TraceContext traceContext = mock(TraceContext.class); + given(traceContext.sampled()).willReturn(false); + given(span.context()).willReturn(traceContext); + assertThat(this.spanContextSupplier.isSampled()).isFalse(); + } + + @Test + void whenCurrentSpanHasDeferredSamplingThenSampledIsFalse() { + Span span = mock(Span.class); + given(this.tracer.currentSpan()).willReturn(span); + TraceContext traceContext = mock(TraceContext.class); + given(traceContext.sampled()).willReturn(null); + given(span.context()).willReturn(traceContext); + assertThat(this.spanContextSupplier.isSampled()).isFalse(); + } + +}