From 40c4edab4529b520e20b426f952b740b76ede325 Mon Sep 17 00:00:00 2001 From: Jack Berg <34418638+jack-berg@users.noreply.github.com> Date: Wed, 7 Oct 2026 14:00:36 -0500 Subject: [PATCH 1/2] Add null guards to 10 classes (opentelemetry-api) - io.opentelemetry.api.metrics.Meter - io.opentelemetry.api.metrics.MeterProvider - io.opentelemetry.api.trace.ArrayBasedTraceState - io.opentelemetry.api.trace.ArrayBasedTraceStateBuilder - io.opentelemetry.api.trace.DefaultTracer - io.opentelemetry.api.trace.DefaultTracerBuilder - io.opentelemetry.api.trace.DefaultTracerProvider - io.opentelemetry.api.trace.PropagatedSpan - io.opentelemetry.api.trace.SpanBuilder - io.opentelemetry.api.trace.SpanContext --- .../io/opentelemetry/api/metrics/Meter.java | 10 +++ .../api/metrics/MeterProvider.java | 4 ++ .../api/trace/ArrayBasedTraceState.java | 4 +- .../trace/ArrayBasedTraceStateBuilder.java | 9 ++- .../api/trace/DefaultTracer.java | 3 + .../api/trace/DefaultTracerBuilder.java | 9 +++ .../api/trace/DefaultTracerProvider.java | 10 +++ .../api/trace/PropagatedSpan.java | 70 ++++++++++++++++++- .../opentelemetry/api/trace/SpanBuilder.java | 9 ++- .../opentelemetry/api/trace/SpanContext.java | 33 +++++++++ docs/knowledge/api-design.md | 7 ++ 11 files changed, 164 insertions(+), 4 deletions(-) diff --git a/api/all/src/main/java/io/opentelemetry/api/metrics/Meter.java b/api/all/src/main/java/io/opentelemetry/api/metrics/Meter.java index d8566457d4c..617c3dd9668 100644 --- a/api/all/src/main/java/io/opentelemetry/api/metrics/Meter.java +++ b/api/all/src/main/java/io/opentelemetry/api/metrics/Meter.java @@ -5,6 +5,7 @@ package io.opentelemetry.api.metrics; +import io.opentelemetry.common.impl.ApiUsageLogger; import javax.annotation.concurrent.ThreadSafe; /** @@ -142,6 +143,15 @@ default BatchCallback batchCallback( Runnable callback, ObservableMeasurement observableMeasurement, ObservableMeasurement... additionalMeasurements) { + if (callback == null) { + ApiUsageLogger.logNullParam(Meter.class, "batchCallback", "callback"); + } + if (observableMeasurement == null) { + ApiUsageLogger.logNullParam(Meter.class, "batchCallback", "observableMeasurement"); + } + if (additionalMeasurements == null) { + ApiUsageLogger.logNullParam(Meter.class, "batchCallback", "additionalMeasurements"); + } return DefaultMeter.getInstance() .batchCallback(callback, observableMeasurement, additionalMeasurements); } diff --git a/api/all/src/main/java/io/opentelemetry/api/metrics/MeterProvider.java b/api/all/src/main/java/io/opentelemetry/api/metrics/MeterProvider.java index 5e8e51fbd36..c9c0f79f599 100644 --- a/api/all/src/main/java/io/opentelemetry/api/metrics/MeterProvider.java +++ b/api/all/src/main/java/io/opentelemetry/api/metrics/MeterProvider.java @@ -5,6 +5,7 @@ package io.opentelemetry.api.metrics; +import io.opentelemetry.common.impl.ApiUsageLogger; import javax.annotation.concurrent.ThreadSafe; /** @@ -26,6 +27,9 @@ public interface MeterProvider { * @return a Meter instance. */ default Meter get(String instrumentationScopeName) { + if (instrumentationScopeName == null) { + ApiUsageLogger.logNullParam(MeterProvider.class, "get", "instrumentationScopeName"); + } return meterBuilder(instrumentationScopeName).build(); } diff --git a/api/all/src/main/java/io/opentelemetry/api/trace/ArrayBasedTraceState.java b/api/all/src/main/java/io/opentelemetry/api/trace/ArrayBasedTraceState.java index 6da56f542f2..2b6e4850120 100644 --- a/api/all/src/main/java/io/opentelemetry/api/trace/ArrayBasedTraceState.java +++ b/api/all/src/main/java/io/opentelemetry/api/trace/ArrayBasedTraceState.java @@ -7,6 +7,7 @@ import com.google.auto.value.AutoValue; import io.opentelemetry.api.internal.ReadOnlyArrayMap; +import io.opentelemetry.common.impl.ApiUsageLogger; import java.util.List; import java.util.Map; import java.util.function.BiConsumer; @@ -21,7 +22,7 @@ abstract class ArrayBasedTraceState implements TraceState { @Nullable public String get(String key) { if (key == null) { - return null; + ApiUsageLogger.logNullParam(TraceState.class, "get", "key"); } List entries = getEntries(); for (int i = 0; i < entries.size(); i += 2) { @@ -45,6 +46,7 @@ public boolean isEmpty() { @Override public void forEach(BiConsumer consumer) { if (consumer == null) { + ApiUsageLogger.logNullParam(TraceState.class, "forEach", "consumer"); return; } List entries = getEntries(); diff --git a/api/all/src/main/java/io/opentelemetry/api/trace/ArrayBasedTraceStateBuilder.java b/api/all/src/main/java/io/opentelemetry/api/trace/ArrayBasedTraceStateBuilder.java index 17918342ece..4846c02880b 100644 --- a/api/all/src/main/java/io/opentelemetry/api/trace/ArrayBasedTraceStateBuilder.java +++ b/api/all/src/main/java/io/opentelemetry/api/trace/ArrayBasedTraceStateBuilder.java @@ -6,6 +6,7 @@ package io.opentelemetry.api.trace; import io.opentelemetry.api.internal.StringUtils; +import io.opentelemetry.common.impl.ApiUsageLogger; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -66,6 +67,12 @@ static TraceState empty() { */ @Override public TraceStateBuilder put(String key, String value) { + if (key == null) { + ApiUsageLogger.logNullParam(TraceStateBuilder.class, "put", "key"); + } + if (value == null) { + ApiUsageLogger.logNullParam(TraceStateBuilder.class, "put", "value"); + } if (!isKeyValid(key) || !isValueValid(value) || numEntries >= MAX_ENTRIES) { return this; } @@ -88,7 +95,7 @@ public TraceStateBuilder put(String key, String value) { @Override public TraceStateBuilder remove(String key) { if (key == null) { - return this; + ApiUsageLogger.logNullParam(TraceStateBuilder.class, "remove", "key"); } for (int i = 0; i < reversedEntries.size(); i += 2) { if (reversedEntries.get(i).equals(key)) { 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 6b9cff09e7b..6ad983f24df 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 @@ -30,6 +30,9 @@ public boolean isEnabled() { @Override public SpanBuilder spanBuilder(String spanName) { + if (spanName == null) { + ApiUsageLogger.logNullParam(Tracer.class, "spanBuilder", "spanName"); + } return NoopSpanBuilder.create(); } diff --git a/api/all/src/main/java/io/opentelemetry/api/trace/DefaultTracerBuilder.java b/api/all/src/main/java/io/opentelemetry/api/trace/DefaultTracerBuilder.java index 543d9a8cf95..d270c46595c 100644 --- a/api/all/src/main/java/io/opentelemetry/api/trace/DefaultTracerBuilder.java +++ b/api/all/src/main/java/io/opentelemetry/api/trace/DefaultTracerBuilder.java @@ -5,6 +5,8 @@ package io.opentelemetry.api.trace; +import io.opentelemetry.common.impl.ApiUsageLogger; + class DefaultTracerBuilder implements TracerBuilder { private static final DefaultTracerBuilder INSTANCE = new DefaultTracerBuilder(); @@ -14,11 +16,18 @@ static TracerBuilder getInstance() { @Override public TracerBuilder setSchemaUrl(String schemaUrl) { + if (schemaUrl == null) { + ApiUsageLogger.logNullParam(TracerBuilder.class, "setSchemaUrl", "schemaUrl"); + } return this; } @Override public TracerBuilder setInstrumentationVersion(String instrumentationScopeVersion) { + if (instrumentationScopeVersion == null) { + ApiUsageLogger.logNullParam( + TracerBuilder.class, "setInstrumentationVersion", "instrumentationScopeVersion"); + } return this; } diff --git a/api/all/src/main/java/io/opentelemetry/api/trace/DefaultTracerProvider.java b/api/all/src/main/java/io/opentelemetry/api/trace/DefaultTracerProvider.java index 9bef6cf9928..7f953c4b1bb 100644 --- a/api/all/src/main/java/io/opentelemetry/api/trace/DefaultTracerProvider.java +++ b/api/all/src/main/java/io/opentelemetry/api/trace/DefaultTracerProvider.java @@ -6,6 +6,7 @@ package io.opentelemetry.api.trace; import io.opentelemetry.api.internal.IncubatingUtil; +import io.opentelemetry.common.impl.ApiUsageLogger; import javax.annotation.concurrent.ThreadSafe; @ThreadSafe @@ -22,11 +23,20 @@ static TracerProvider getInstance() { @Override public Tracer get(String instrumentationScopeName) { + if (instrumentationScopeName == null) { + ApiUsageLogger.logNullParam(TracerProvider.class, "get", "instrumentationScopeName"); + } return DefaultTracer.getInstance(); } @Override public Tracer get(String instrumentationScopeName, String instrumentationScopeVersion) { + if (instrumentationScopeName == null) { + ApiUsageLogger.logNullParam(TracerProvider.class, "get", "instrumentationScopeName"); + } + if (instrumentationScopeVersion == null) { + ApiUsageLogger.logNullParam(TracerProvider.class, "get", "instrumentationScopeVersion"); + } return DefaultTracer.getInstance(); } 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 88396280043..d1e37b9543f 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 @@ -7,6 +7,7 @@ import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.common.impl.ApiUsageLogger; import java.util.concurrent.TimeUnit; import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; @@ -44,76 +45,139 @@ private PropagatedSpan(SpanContext spanContext) { @Override public Span setAttribute(String key, @Nullable String value) { + if (key == null) { + ApiUsageLogger.logNullParam(Span.class, "setAttribute", "key"); + } return this; } @Override public Span setAttribute(String key, long value) { + if (key == null) { + ApiUsageLogger.logNullParam(Span.class, "setAttribute", "key"); + } return this; } @Override public Span setAttribute(String key, double value) { + if (key == null) { + ApiUsageLogger.logNullParam(Span.class, "setAttribute", "key"); + } return this; } @Override public Span setAttribute(String key, boolean value) { + if (key == null) { + ApiUsageLogger.logNullParam(Span.class, "setAttribute", "key"); + } return this; } @Override public Span setAttribute(AttributeKey key, @Nullable T value) { + if (key == null) { + ApiUsageLogger.logNullParam(Span.class, "setAttribute", "key"); + } return this; } @Override public Span setAllAttributes(Attributes attributes) { + if (attributes == null) { + ApiUsageLogger.logNullParam(Span.class, "setAllAttributes", "attributes"); + } return this; } @Override public Span addEvent(String name) { + if (name == null) { + ApiUsageLogger.logNullParam(Span.class, "addEvent", "name"); + } return this; } @Override public Span addEvent(String name, long timestamp, TimeUnit unit) { + if (name == null) { + ApiUsageLogger.logNullParam(Span.class, "addEvent", "name"); + } + if (unit == null) { + ApiUsageLogger.logNullParam(Span.class, "addEvent", "unit"); + } return this; } @Override public Span addEvent(String name, Attributes attributes) { + if (name == null) { + ApiUsageLogger.logNullParam(Span.class, "addEvent", "name"); + } + if (attributes == null) { + ApiUsageLogger.logNullParam(Span.class, "addEvent", "attributes"); + } return this; } @Override public Span addEvent(String name, Attributes attributes, long timestamp, TimeUnit unit) { + if (name == null) { + ApiUsageLogger.logNullParam(Span.class, "addEvent", "name"); + } + if (attributes == null) { + ApiUsageLogger.logNullParam(Span.class, "addEvent", "attributes"); + } + if (unit == null) { + ApiUsageLogger.logNullParam(Span.class, "addEvent", "unit"); + } return this; } @Override public Span setStatus(StatusCode statusCode) { + if (statusCode == null) { + ApiUsageLogger.logNullParam(Span.class, "setStatus", "statusCode"); + } return this; } @Override public Span setStatus(StatusCode statusCode, String description) { + if (statusCode == null) { + ApiUsageLogger.logNullParam(Span.class, "setStatus", "statusCode"); + } + if (description == null) { + ApiUsageLogger.logNullParam(Span.class, "setStatus", "description"); + } return this; } @Override public Span recordException(Throwable exception) { + if (exception == null) { + ApiUsageLogger.logNullParam(Span.class, "recordException", "exception"); + } return this; } @Override public Span recordException(Throwable exception, Attributes additionalAttributes) { + if (exception == null) { + ApiUsageLogger.logNullParam(Span.class, "recordException", "exception"); + } + if (additionalAttributes == null) { + ApiUsageLogger.logNullParam(Span.class, "recordException", "additionalAttributes"); + } return this; } @Override public Span updateName(String name) { + if (name == null) { + ApiUsageLogger.logNullParam(Span.class, "updateName", "name"); + } return this; } @@ -121,7 +185,11 @@ public Span updateName(String name) { public void end() {} @Override - public void end(long timestamp, TimeUnit unit) {} + public void end(long timestamp, TimeUnit unit) { + if (unit == null) { + ApiUsageLogger.logNullParam(Span.class, "end", "unit"); + } + } @Override public SpanContext getSpanContext() { 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 28fae81f0dc..5738f8c1119 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 @@ -10,6 +10,7 @@ import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.common.impl.ApiUsageLogger; import io.opentelemetry.context.Context; import java.time.Instant; import java.util.concurrent.TimeUnit; @@ -248,6 +249,10 @@ public interface SpanBuilder { * @since 1.45.0 */ default SpanBuilder setAttribute(AttributeKey key, int value) { + if (key == null) { + ApiUsageLogger.logNullParam(AttributeKey.class, "setAttribute", "key"); + return this; + } return setAttribute(key, (long) value); } @@ -261,7 +266,8 @@ default SpanBuilder setAttribute(AttributeKey key, int value) { */ @SuppressWarnings("unchecked") default SpanBuilder setAllAttributes(Attributes attributes) { - if (attributes == null || attributes.isEmpty()) { + if (attributes == null) { + ApiUsageLogger.logNullParam(Attributes.class, "setAllAttributes", "attributes"); return this; } attributes.forEach( @@ -307,6 +313,7 @@ default SpanBuilder setAllAttributes(Attributes attributes) { */ default SpanBuilder setStartTimestamp(Instant startTimestamp) { if (startTimestamp == null) { + ApiUsageLogger.logNullParam(Instant.class, "setStartTimestamp", "startTimestamp"); return this; } return setStartTimestamp( diff --git a/api/all/src/main/java/io/opentelemetry/api/trace/SpanContext.java b/api/all/src/main/java/io/opentelemetry/api/trace/SpanContext.java index 684108b8315..a67b4118f82 100644 --- a/api/all/src/main/java/io/opentelemetry/api/trace/SpanContext.java +++ b/api/all/src/main/java/io/opentelemetry/api/trace/SpanContext.java @@ -7,6 +7,7 @@ import io.opentelemetry.api.internal.ImmutableSpanContext; import io.opentelemetry.api.internal.OtelEncodingUtils; +import io.opentelemetry.common.impl.ApiUsageLogger; import javax.annotation.concurrent.Immutable; /** @@ -50,6 +51,22 @@ static SpanContext getInvalid() { */ static SpanContext create( String traceIdHex, String spanIdHex, TraceFlags traceFlags, TraceState traceState) { + if (traceIdHex == null) { + ApiUsageLogger.logNullParam(SpanContext.class, "create", "traceIdHex"); + return SpanContext.getInvalid(); + } + if (spanIdHex == null) { + ApiUsageLogger.logNullParam(SpanContext.class, "create", "spanIdHex"); + return SpanContext.getInvalid(); + } + if (traceFlags == null) { + ApiUsageLogger.logNullParam(SpanContext.class, "create", "traceFlags"); + return SpanContext.getInvalid(); + } + if (traceState == null) { + ApiUsageLogger.logNullParam(SpanContext.class, "create", "traceState"); + return SpanContext.getInvalid(); + } return ImmutableSpanContext.create( traceIdHex, spanIdHex, @@ -76,6 +93,22 @@ static SpanContext create( */ static SpanContext createFromRemoteParent( String traceIdHex, String spanIdHex, TraceFlags traceFlags, TraceState traceState) { + if (traceIdHex == null) { + ApiUsageLogger.logNullParam(SpanContext.class, "createFromRemoteParent", "traceIdHex"); + return SpanContext.getInvalid(); + } + if (spanIdHex == null) { + ApiUsageLogger.logNullParam(SpanContext.class, "createFromRemoteParent", "spanIdHex"); + return SpanContext.getInvalid(); + } + if (traceFlags == null) { + ApiUsageLogger.logNullParam(SpanContext.class, "createFromRemoteParent", "traceFlags"); + return SpanContext.getInvalid(); + } + if (traceState == null) { + ApiUsageLogger.logNullParam(SpanContext.class, "createFromRemoteParent", "traceState"); + return SpanContext.getInvalid(); + } return ImmutableSpanContext.create( traceIdHex, spanIdHex, diff --git a/docs/knowledge/api-design.md b/docs/knowledge/api-design.md index 9b26f27d01f..1d851045482 100644 --- a/docs/knowledge/api-design.md +++ b/docs/knowledge/api-design.md @@ -138,6 +138,13 @@ public Span addEvent(String name) { } ``` +Log and fall through when the remaining code safely tolerates null and produces the intended +fallback without unwanted side effects. Otherwise, return early. Preserve existing null-tolerant +behavior. + +Check non-`@Nullable` reference arguments in declaration order, logging each null once unless an +early return ends the call. + The class and method arguments identify the problem immediately in the log message without requiring stack trace analysis. Use `ApiUsageLogger.log(...)` directly when the message is not simply "X is null" (e.g. `"spanIdBytes is null or too short"`). `FINEST` is silent by default, so there is no production noise. From 831a65d9e5eb2460df7dcf26ac372e4a78a2c559 Mon Sep 17 00:00:00 2001 From: Jack Berg <34418638+jack-berg@users.noreply.github.com> Date: Fri, 16 Oct 2026 14:26:41 -0500 Subject: [PATCH 2/2] PR feedback --- .../io/opentelemetry/api/trace/ArrayBasedTraceState.java | 1 + .../api/trace/ArrayBasedTraceStateBuilder.java | 1 + docs/knowledge/api-design.md | 6 +++--- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/api/all/src/main/java/io/opentelemetry/api/trace/ArrayBasedTraceState.java b/api/all/src/main/java/io/opentelemetry/api/trace/ArrayBasedTraceState.java index 2b6e4850120..2a74f1c157c 100644 --- a/api/all/src/main/java/io/opentelemetry/api/trace/ArrayBasedTraceState.java +++ b/api/all/src/main/java/io/opentelemetry/api/trace/ArrayBasedTraceState.java @@ -23,6 +23,7 @@ abstract class ArrayBasedTraceState implements TraceState { public String get(String key) { if (key == null) { ApiUsageLogger.logNullParam(TraceState.class, "get", "key"); + return null; } List entries = getEntries(); for (int i = 0; i < entries.size(); i += 2) { diff --git a/api/all/src/main/java/io/opentelemetry/api/trace/ArrayBasedTraceStateBuilder.java b/api/all/src/main/java/io/opentelemetry/api/trace/ArrayBasedTraceStateBuilder.java index 4846c02880b..9558fd765bf 100644 --- a/api/all/src/main/java/io/opentelemetry/api/trace/ArrayBasedTraceStateBuilder.java +++ b/api/all/src/main/java/io/opentelemetry/api/trace/ArrayBasedTraceStateBuilder.java @@ -96,6 +96,7 @@ public TraceStateBuilder put(String key, String value) { public TraceStateBuilder remove(String key) { if (key == null) { ApiUsageLogger.logNullParam(TraceStateBuilder.class, "remove", "key"); + return this; } for (int i = 0; i < reversedEntries.size(); i += 2) { if (reversedEntries.get(i).equals(key)) { diff --git a/docs/knowledge/api-design.md b/docs/knowledge/api-design.md index 1d851045482..fafef231108 100644 --- a/docs/knowledge/api-design.md +++ b/docs/knowledge/api-design.md @@ -138,9 +138,9 @@ public Span addEvent(String name) { } ``` -Log and fall through when the remaining code safely tolerates null and produces the intended -fallback without unwanted side effects. Otherwise, return early. Preserve existing null-tolerant -behavior. +Return after logging unless the remaining code is a noop or explicitly handles null (for example, by +substituting a default). Do not continue into a loop or other logic just because it happens to +tolerate null. Check non-`@Nullable` reference arguments in declaration order, logging each null once unless an early return ends the call.