Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
1c0f03a
Implement RPC semantic convention stable opt-in
zeitlinger Jan 15, 2026
a67aef8
rpc test
zeitlinger Jan 16, 2026
6212e6d
rpc test
zeitlinger Jan 16, 2026
b0867a8
rpc method
zeitlinger Jan 16, 2026
869fded
rpc method
zeitlinger Jan 16, 2026
1c3dd54
rpc method
zeitlinger Jan 16, 2026
f5f8bd0
rpc method
zeitlinger Jan 16, 2026
7f2e9d2
network attributes
zeitlinger Jan 16, 2026
ffde54c
Revert "network attributes"
zeitlinger Jan 16, 2026
cd3293a
rpc method
zeitlinger Jan 16, 2026
8cb016d
remove rpc.method_original - until we know it's needed
zeitlinger Jan 16, 2026
cd9a76d
cleanup
zeitlinger Jan 16, 2026
b7f5ec4
cleanup
zeitlinger Jan 16, 2026
eb18a06
cleanup
zeitlinger Jan 16, 2026
99cce85
cleanup
zeitlinger Jan 16, 2026
1fa6ddc
grpc
zeitlinger Jan 16, 2026
f6d56f4
grpc
zeitlinger Jan 16, 2026
34e2c75
span tests
zeitlinger Jan 16, 2026
c63e51a
format
zeitlinger Jan 16, 2026
70e012c
add tests
zeitlinger Jan 16, 2026
02b652e
add tests
zeitlinger Jan 16, 2026
8c30ab4
fix
zeitlinger Jan 17, 2026
1067dee
fix
zeitlinger Jan 17, 2026
4657008
old method attribute should never be telemetry data
zeitlinger Jan 17, 2026
160ff44
old method attribute should never be telemetry data
zeitlinger Jan 17, 2026
8964862
fix
zeitlinger Jan 17, 2026
84a7dc0
fix
zeitlinger Jan 17, 2026
909c6f7
fix
zeitlinger Jan 19, 2026
3729c7c
fix
zeitlinger Jan 19, 2026
d47a380
fix
zeitlinger Jan 19, 2026
5420b3d
fix
zeitlinger Jan 19, 2026
419eddf
fix
zeitlinger Jan 19, 2026
553a041
fix
zeitlinger Jan 19, 2026
83715f9
fix
zeitlinger Jan 19, 2026
d2d445e
cleanup
zeitlinger Jan 19, 2026
e9c39af
cleanup
zeitlinger Jan 19, 2026
b1a25a4
cleanup
zeitlinger Jan 19, 2026
ae26805
fix
zeitlinger Jan 19, 2026
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 instrumentation-api-incubator/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -86,13 +86,13 @@ tasks {
val testStableSemconv by registering(Test::class) {
testClassesDirs = sourceSets.test.get().output.classesDirs
classpath = sourceSets.test.get().runtimeClasspath
jvmArgs("-Dotel.semconv-stability.opt-in=database,code")
jvmArgs("-Dotel.semconv-stability.opt-in=database,code,rpc")
}

val testBothSemconv by registering(Test::class) {
testClassesDirs = sourceSets.test.get().output.classesDirs
classpath = sourceSets.test.get().runtimeClasspath
jvmArgs("-Dotel.semconv-stability.opt-in=database/dup,code/dup")
jvmArgs("-Dotel.semconv-stability.opt-in=database/dup,code/dup,rpc/dup")
}

check {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,24 @@ default Long getRequestSize(REQUEST request) {
default Long getResponseSize(REQUEST request) {
return null;
}

/**
* Returns the fully-qualified RPC method name for stable semconv.
*
* <p>The default implementation concatenates service + "/" + method. Framework implementations
* can override for efficiency if they already have the fully-qualified name available.
*
* @param request the request object
* @return the fully-qualified RPC method name (e.g., "my.Service/Method"), or null if service or
* method is unavailable
*/
@Nullable
default String getFullMethod(REQUEST request) {
String service = getService(request);
String method = getMethod(request);
if (service == null || method == null) {
return null;
}
return service + "/" + method;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@
import io.opentelemetry.instrumentation.api.instrumenter.OperationListener;
import io.opentelemetry.instrumentation.api.instrumenter.OperationMetrics;
import io.opentelemetry.instrumentation.api.internal.OperationMetricsUtil;
import io.opentelemetry.instrumentation.api.internal.SemconvStability;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
import javax.annotation.Nullable;

/**
* {@link OperationListener} which keeps track of <a
Expand All @@ -30,42 +32,62 @@
public final class RpcClientMetrics implements OperationListener {

private static final double NANOS_PER_MS = TimeUnit.MILLISECONDS.toNanos(1);
private static final double NANOS_PER_S = TimeUnit.SECONDS.toNanos(1);

private static final ContextKey<RpcClientMetrics.State> RPC_CLIENT_REQUEST_METRICS_STATE =
ContextKey.named("rpc-client-request-metrics-state");

private static final Logger logger = Logger.getLogger(RpcClientMetrics.class.getName());

private final DoubleHistogram clientDurationHistogram;
private final LongHistogram clientRequestSize;
private final LongHistogram clientResponseSize;
@Nullable private final DoubleHistogram oldClientDurationHistogram;
@Nullable private final DoubleHistogram stableClientDurationHistogram;
private final LongHistogram oldClientRequestSize;
private final LongHistogram oldClientResponseSize;

private RpcClientMetrics(Meter meter) {
DoubleHistogramBuilder durationBuilder =
meter
.histogramBuilder("rpc.client.duration")
.setDescription("The duration of an outbound RPC invocation.")
.setUnit("ms");
RpcMetricsAdvice.applyClientDurationAdvice(durationBuilder);
clientDurationHistogram = durationBuilder.build();
// Old metric (milliseconds)
if (SemconvStability.emitOldRpcSemconv()) {
DoubleHistogramBuilder oldDurationBuilder =
meter
.histogramBuilder("rpc.client.duration")
.setDescription("The duration of an outbound RPC invocation.")
.setUnit("ms");
RpcMetricsAdvice.applyClientDurationAdvice(oldDurationBuilder, false);
oldClientDurationHistogram = oldDurationBuilder.build();
} else {
oldClientDurationHistogram = null;
}

// Stable metric (seconds)
if (SemconvStability.emitStableRpcSemconv()) {
DoubleHistogramBuilder stableDurationBuilder =
meter
.histogramBuilder("rpc.client.call.duration")
.setDescription("The duration of an outbound RPC invocation.")
.setUnit("s");
RpcMetricsAdvice.applyClientDurationAdvice(stableDurationBuilder, true);
stableClientDurationHistogram = stableDurationBuilder.build();
} else {
stableClientDurationHistogram = null;
}

LongHistogramBuilder requestSizeBuilder =
meter
.histogramBuilder("rpc.client.request.size")
.setUnit("By")
.setDescription("Measures the size of RPC request messages (uncompressed).")
.ofLongs();
RpcMetricsAdvice.applyClientRequestSizeAdvice(requestSizeBuilder);
clientRequestSize = requestSizeBuilder.build();
RpcMetricsAdvice.applyDeprecatedClientRequestSizeAdvice(requestSizeBuilder);
oldClientRequestSize = requestSizeBuilder.build();

LongHistogramBuilder responseSizeBuilder =
meter
.histogramBuilder("rpc.client.response.size")
.setUnit("By")
.setDescription("Measures the size of RPC response messages (uncompressed).")
.ofLongs();
RpcMetricsAdvice.applyClientRequestSizeAdvice(responseSizeBuilder);
clientResponseSize = responseSizeBuilder.build();
RpcMetricsAdvice.applyDeprecatedClientRequestSizeAdvice(responseSizeBuilder);
oldClientResponseSize = responseSizeBuilder.build();
}

/**
Expand Down Expand Up @@ -95,17 +117,37 @@ public void onEnd(Context context, Attributes endAttributes, long endNanos) {
return;
}
Attributes attributes = state.startAttributes().toBuilder().putAll(endAttributes).build();
clientDurationHistogram.record(
(endNanos - state.startTimeNanos()) / NANOS_PER_MS, attributes, context);
double durationNanos = (endNanos - state.startTimeNanos());

// Record to old histogram (milliseconds)
if (oldClientDurationHistogram != null) {
oldClientDurationHistogram.record(
durationNanos / NANOS_PER_MS,
SemconvStability.getOldRpcMetricAttributes(attributes),
context);
}

Long rpcClientRequestBodySize = attributes.get(RpcSizeAttributesExtractor.RPC_REQUEST_SIZE);
if (rpcClientRequestBodySize != null) {
clientRequestSize.record(rpcClientRequestBodySize, attributes, context);
// Record to stable histogram (seconds)
if (stableClientDurationHistogram != null) {
stableClientDurationHistogram.record(durationNanos / NANOS_PER_S, attributes, context);
}

Long rpcClientResponseBodySize = attributes.get(RpcSizeAttributesExtractor.RPC_RESPONSE_SIZE);
if (rpcClientResponseBodySize != null) {
clientResponseSize.record(rpcClientResponseBodySize, attributes, context);
if (SemconvStability.emitOldRpcSemconv()) {
Long rpcClientRequestBodySize = attributes.get(RpcSizeAttributesExtractor.RPC_REQUEST_SIZE);
if (rpcClientRequestBodySize != null) {
oldClientRequestSize.record(
rpcClientRequestBodySize,
SemconvStability.getOldRpcMetricAttributes(attributes),
context);
}

Long rpcClientResponseBodySize = attributes.get(RpcSizeAttributesExtractor.RPC_RESPONSE_SIZE);
if (rpcClientResponseBodySize != null) {
oldClientResponseSize.record(
rpcClientResponseBodySize,
SemconvStability.getOldRpcMetricAttributes(attributes),
context);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,27 @@
package io.opentelemetry.instrumentation.api.incubator.semconv.rpc;

import static io.opentelemetry.instrumentation.api.internal.AttributesExtractorUtil.internalSet;
import static io.opentelemetry.semconv.ErrorAttributes.ERROR_TYPE;

import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.context.Context;
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
import io.opentelemetry.instrumentation.api.internal.SemconvStability;
import javax.annotation.Nullable;

abstract class RpcCommonAttributesExtractor<REQUEST, RESPONSE>
implements AttributesExtractor<REQUEST, RESPONSE> {

// copied from RpcIncubatingAttributes
static final AttributeKey<String> RPC_METHOD = AttributeKey.stringKey("rpc.method");

// Stable semconv keys
static final AttributeKey<String> RPC_SYSTEM_NAME = AttributeKey.stringKey("rpc.system.name");

// removed in stable semconv (merged into rpc.method)
static final AttributeKey<String> RPC_SERVICE = AttributeKey.stringKey("rpc.service");

// use RPC_SYSTEM_NAME for stable semconv
static final AttributeKey<String> RPC_SYSTEM = AttributeKey.stringKey("rpc.system");

private final RpcAttributesGetter<REQUEST> getter;
Expand All @@ -29,9 +37,22 @@ abstract class RpcCommonAttributesExtractor<REQUEST, RESPONSE>

@Override
public final void onStart(AttributesBuilder attributes, Context parentContext, REQUEST request) {
internalSet(attributes, RPC_SYSTEM, getter.getSystem(request));
internalSet(attributes, RPC_SERVICE, getter.getService(request));
internalSet(attributes, RPC_METHOD, getter.getMethod(request));
String system = getter.getSystem(request);

if (SemconvStability.emitStableRpcSemconv()) {
internalSet(
attributes,
RPC_SYSTEM_NAME,
system == null ? null : SemconvStability.stableRpcSystemName(system));
internalSet(attributes, RPC_METHOD, getter.getFullMethod(request));
}

if (SemconvStability.emitOldRpcSemconv()) {
internalSet(attributes, RPC_SYSTEM, system);
internalSet(attributes, RPC_SERVICE, getter.getService(request));
internalSet(
attributes, SemconvStability.getOldRpcMethodAttributeKey(), getter.getMethod(request));
}
}

@Override
Expand All @@ -41,6 +62,10 @@ public final void onEnd(
REQUEST request,
@Nullable RESPONSE response,
@Nullable Throwable error) {
// No response attributes
if (SemconvStability.emitStableRpcSemconv()) {
if (error != null) {
internalSet(attributes, ERROR_TYPE, error.getClass().getName());
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,67 +5,111 @@

package io.opentelemetry.instrumentation.api.incubator.semconv.rpc;

import static java.util.Arrays.asList;

import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.incubator.metrics.ExtendedDoubleHistogramBuilder;
import io.opentelemetry.api.incubator.metrics.ExtendedLongHistogramBuilder;
import io.opentelemetry.api.metrics.DoubleHistogramBuilder;
import io.opentelemetry.api.metrics.LongHistogramBuilder;
import io.opentelemetry.instrumentation.api.internal.SemconvStability;
import io.opentelemetry.semconv.NetworkAttributes;
import io.opentelemetry.semconv.ServerAttributes;
import java.util.ArrayList;
import java.util.List;

final class RpcMetricsAdvice {

// Stable semconv key
private static final AttributeKey<String> RPC_RESPONSE_STATUS_CODE =
AttributeKey.stringKey("rpc.response.status_code");

// copied from RpcIncubatingAttributes
@Deprecated // use RPC_RESPONSE_STATUS_CODE for stable semconv
private static final AttributeKey<Long> RPC_GRPC_STATUS_CODE =
AttributeKey.longKey("rpc.grpc.status_code");
private static final List<AttributeKey<?>> RPC_METRICS_ATTRIBUTE_KEYS =
asList(
RpcCommonAttributesExtractor.RPC_SYSTEM,
RpcCommonAttributesExtractor.RPC_SERVICE,
RpcCommonAttributesExtractor.RPC_METHOD,
RPC_GRPC_STATUS_CODE,
NetworkAttributes.NETWORK_TYPE,
NetworkAttributes.NETWORK_TRANSPORT,
ServerAttributes.SERVER_ADDRESS,
ServerAttributes.SERVER_PORT);

static void applyClientDurationAdvice(DoubleHistogramBuilder builder) {

private static final List<AttributeKey<?>> RPC_METRICS_DEPRECATED_ATTRIBUTE_KEYS =
buildAttributeKeysList(false);
private static final List<AttributeKey<?>> RPC_METRICS_STABLE_ATTRIBUTE_KEYS =
buildAttributeKeysList(true);

@SuppressWarnings("deprecation") // until old rpc semconv are dropped
private static List<AttributeKey<?>> buildAttributeKeysList(boolean stable) {
List<AttributeKey<?>> keys = new ArrayList<>();

// Add stable or old RPC system key
if (stable) {
keys.add(RpcCommonAttributesExtractor.RPC_SYSTEM_NAME);
} else {
keys.add(RpcCommonAttributesExtractor.RPC_SYSTEM);
}

// Add RPC service (old only)
if (!stable) {
keys.add(RpcCommonAttributesExtractor.RPC_SERVICE);
}

keys.add(RpcCommonAttributesExtractor.RPC_METHOD);

// Add status code key
if (SemconvStability.emitStableRpcSemconv()) {
keys.add(RPC_RESPONSE_STATUS_CODE);
} else {
keys.add(RPC_GRPC_STATUS_CODE);
}

// Network type only for old semconv
if (!stable) {
keys.add(NetworkAttributes.NETWORK_TYPE);
}

// Common attributes
keys.add(NetworkAttributes.NETWORK_TRANSPORT);
keys.add(ServerAttributes.SERVER_ADDRESS);
keys.add(ServerAttributes.SERVER_PORT);

return keys;
}

private static List<AttributeKey<?>> getAttributeKeys(boolean stable) {
return stable ? RPC_METRICS_STABLE_ATTRIBUTE_KEYS : RPC_METRICS_DEPRECATED_ATTRIBUTE_KEYS;
}

static void applyClientDurationAdvice(DoubleHistogramBuilder builder, boolean stable) {
if (!(builder instanceof ExtendedDoubleHistogramBuilder)) {
return;
}
// the list of recommended metrics attributes is from
// https://github.com/open-telemetry/semantic-conventions/blob/main/docs/rpc/rpc-metrics.md
((ExtendedDoubleHistogramBuilder) builder).setAttributesAdvice(RPC_METRICS_ATTRIBUTE_KEYS);
((ExtendedDoubleHistogramBuilder) builder).setAttributesAdvice(getAttributeKeys(stable));
}

static void applyServerDurationAdvice(DoubleHistogramBuilder builder) {
static void applyServerDurationAdvice(DoubleHistogramBuilder builder, boolean stable) {
if (!(builder instanceof ExtendedDoubleHistogramBuilder)) {
return;
}
// the list of recommended metrics attributes is from
// https://github.com/open-telemetry/semantic-conventions/blob/main/docs/rpc/rpc-metrics.md
((ExtendedDoubleHistogramBuilder) builder).setAttributesAdvice(RPC_METRICS_ATTRIBUTE_KEYS);
((ExtendedDoubleHistogramBuilder) builder).setAttributesAdvice(getAttributeKeys(stable));
}

static void applyClientRequestSizeAdvice(LongHistogramBuilder builder) {
static void applyDeprecatedClientRequestSizeAdvice(LongHistogramBuilder builder) {
if (!(builder instanceof ExtendedLongHistogramBuilder)) {
return;
}
// the list of recommended metrics attributes is from
// https://github.com/open-telemetry/semantic-conventions/blob/main/docs/rpc/rpc-metrics.md
((ExtendedLongHistogramBuilder) builder).setAttributesAdvice(RPC_METRICS_ATTRIBUTE_KEYS);
((ExtendedLongHistogramBuilder) builder)
.setAttributesAdvice(RPC_METRICS_DEPRECATED_ATTRIBUTE_KEYS);
}

static void applyServerRequestSizeAdvice(LongHistogramBuilder builder) {
static void applyDeprecatedServerRequestSizeAdvice(LongHistogramBuilder builder) {
if (!(builder instanceof ExtendedLongHistogramBuilder)) {
return;
}
// the list of recommended metrics attributes is from
// https://github.com/open-telemetry/semantic-conventions/blob/main/docs/rpc/rpc-metrics.md
((ExtendedLongHistogramBuilder) builder).setAttributesAdvice(RPC_METRICS_ATTRIBUTE_KEYS);
((ExtendedLongHistogramBuilder) builder)
.setAttributesAdvice(RPC_METRICS_DEPRECATED_ATTRIBUTE_KEYS);
}

private RpcMetricsAdvice() {}
Expand Down
Loading
Loading