diff --git a/brave/brave5/build.gradle b/brave/brave5/build.gradle
index 36f2b5886c2..eff6f826f3d 100644
--- a/brave/brave5/build.gradle
+++ b/brave/brave5/build.gradle
@@ -1,6 +1,7 @@
dependencies {
api libs.brave5
api libs.brave5.instrumentation.http
+ optionalImplementation libs.brave5.instrumentation.rpc
if (project.ext.targetJavaVersion >= 11) {
testImplementation project(':thrift0.18')
diff --git a/brave/brave6/build.gradle b/brave/brave6/build.gradle
index cd50152bf1d..4a3fd59883c 100644
--- a/brave/brave6/build.gradle
+++ b/brave/brave6/build.gradle
@@ -1,6 +1,7 @@
dependencies {
api libs.brave6
api libs.brave6.instrumentation.http
+ optionalImplementation libs.brave6.instrumentation.rpc
if (project.ext.targetJavaVersion >= 11) {
testImplementation project(':thrift0.18')
diff --git a/brave/brave6/src/main/java/com/linecorp/armeria/server/brave/ArmeriaHttpServerParser.java b/brave/brave6/src/main/java/com/linecorp/armeria/server/brave/ArmeriaHttpServerParser.java
index a43069b252c..1e05279f222 100644
--- a/brave/brave6/src/main/java/com/linecorp/armeria/server/brave/ArmeriaHttpServerParser.java
+++ b/brave/brave6/src/main/java/com/linecorp/armeria/server/brave/ArmeriaHttpServerParser.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 LINE Corporation
+ * Copyright 2025 LINE Corporation
*
* LINE Corporation licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
@@ -16,22 +16,33 @@
package com.linecorp.armeria.server.brave;
-import com.linecorp.armeria.common.RpcRequest;
-import com.linecorp.armeria.common.logging.RequestLog;
-import com.linecorp.armeria.internal.common.brave.SpanTags;
-import com.linecorp.armeria.server.ServiceRequestContext;
-
-import brave.SpanCustomizer;
import brave.http.HttpRequestParser;
-import brave.http.HttpResponse;
import brave.http.HttpResponseParser;
-import brave.propagation.TraceContext;
/**
- * Default implementation of {@link HttpRequestParser} and {@link HttpResponseParser} for servers.
- * This parser adds some custom tags and overwrites the name of span if {@link RequestLog#requestContent()}
- * is {@link RpcRequest}.
- * The following tags become available:
+ * Provides Armeria's default implementation for server-side HTTP request and response parsers.
+ * Users may use the default parser like the following:
+ *
{@code
+ * Tracing tracing = ...
+ * HttpTracing httpTracing =
+ * HttpTracing.newBuilder(tracing)
+ * .serverRequestParser((req, ctx, span) -> {
+ * // apply brave's default request parser
+ * HttpRequestParser.DEFAULT.parse(req, ctx, span);
+ * // apply armeria's default request parser
+ * ArmeriaHttpServerParser.requestParser().parse(req, ctx, span);
+ * })
+ * .serverResponseParser((res, ctx, span) -> {
+ * // apply brave's default response parser
+ * HttpResponseParser.DEFAULT.parse(res, ctx, span);
+ * // apply armeria's default response parser
+ * ArmeriaHttpServerParser.responseParser().parse(res, ctx, span);
+ * });
+ * BraveService
+ * .newDecorator(httpTracing)
+ * ...
+ * }
+ * The following tags will be available by default:
*
* - http.url
* - http.host
@@ -41,53 +52,25 @@
* - address.local
*
*/
-final class ArmeriaHttpServerParser implements HttpRequestParser, HttpResponseParser {
+public final class ArmeriaHttpServerParser {
- private static final ArmeriaHttpServerParser INSTANCE = new ArmeriaHttpServerParser();
+ private static final HttpRequestParser defaultRequestParser = ArmeriaServerParser::parseRequest;
- static ArmeriaHttpServerParser get() {
- return INSTANCE;
- }
+ private static final HttpResponseParser defaultResponseParser = ArmeriaServerParser::parseResponse;
- private ArmeriaHttpServerParser() {
+ /**
+ * Returns the default {@link HttpRequestParser}.
+ */
+ public static HttpRequestParser requestParser() {
+ return defaultRequestParser;
}
- @Override
- public void parse(brave.http.HttpRequest request, TraceContext context, SpanCustomizer span) {
- HttpRequestParser.DEFAULT.parse(request, context, span);
-
- final Object unwrapped = request.unwrap();
- if (!(unwrapped instanceof ServiceRequestContext)) {
- return;
- }
-
- final ServiceRequestContext ctx = (ServiceRequestContext) unwrapped;
- span.tag(SpanTags.TAG_HTTP_HOST, ctx.request().authority())
- .tag(SpanTags.TAG_HTTP_URL, ctx.request().uri().toString())
- .tag(SpanTags.TAG_HTTP_PROTOCOL, ctx.sessionProtocol().uriText())
- .tag(SpanTags.TAG_ADDRESS_REMOTE, ctx.remoteAddress().toString())
- .tag(SpanTags.TAG_ADDRESS_LOCAL, ctx.localAddress().toString());
+ /**
+ * Returns the default {@link HttpResponseParser}.
+ */
+ public static HttpResponseParser responseParser() {
+ return defaultResponseParser;
}
- @Override
- public void parse(HttpResponse response, TraceContext context, SpanCustomizer span) {
- HttpResponseParser.DEFAULT.parse(response, context, span);
-
- final Object res = response.unwrap();
- if (!(res instanceof ServiceRequestContext)) {
- return;
- }
-
- final ServiceRequestContext ctx = (ServiceRequestContext) res;
- final RequestLog requestLog = ctx.log().ensureComplete();
- final String serFmt = ServiceRequestContextAdapter.serializationFormat(requestLog);
- if (serFmt != null) {
- span.tag(SpanTags.TAG_HTTP_SERIALIZATION_FORMAT, serFmt);
- }
-
- final String name = requestLog.name();
- if (name != null) {
- span.name(name);
- }
- }
+ private ArmeriaHttpServerParser() {}
}
diff --git a/brave/brave6/src/main/java/com/linecorp/armeria/server/brave/ArmeriaRpcServerParser.java b/brave/brave6/src/main/java/com/linecorp/armeria/server/brave/ArmeriaRpcServerParser.java
new file mode 100644
index 00000000000..125252c94a3
--- /dev/null
+++ b/brave/brave6/src/main/java/com/linecorp/armeria/server/brave/ArmeriaRpcServerParser.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2025 LINE Corporation
+ *
+ * LINE Corporation licenses this file to you 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 com.linecorp.armeria.server.brave;
+
+import brave.rpc.RpcRequestParser;
+import brave.rpc.RpcResponseParser;
+
+/**
+ * Provides Armeria's default implementation for server-side RPC request and response parsers.
+ * Users may use the default parser like the following:
+ * {@code
+ * Tracing tracing = ...
+ * RpcTracing rpcTracing =
+ * RpcTracing.newBuilder(tracing)
+ * .serverRequestParser((req, ctx, span) -> {
+ * // apply brave's default request parser
+ * RpcRequestParser.DEFAULT.parse(req, ctx, span);
+ * // apply armeria's default request parser
+ * ArmeriaRpcServerParser.requestParser().parse(req, ctx, span);
+ * })
+ * .serverResponseParser((res, ctx, span) -> {
+ * // apply brave's default response parser
+ * RpcResponseParser.DEFAULT.parse(res, ctx, span);
+ * // apply armeria's default response parser
+ * ArmeriaRpcServerParser.responseParser().parse(res, ctx, span);
+ * });
+ * BraveRpcService
+ * .newDecorator(rpcTracing)
+ * ...
+ * }
+ * The following tags will be available by default:
+ *
+ * - http.url
+ * - http.host
+ * - http.protocol
+ * - http.serfmt
+ * - address.remote
+ * - address.local
+ *
+ */
+public final class ArmeriaRpcServerParser {
+
+ private static final RpcRequestParser defaultRequestParser = ArmeriaServerParser::parseRequest;
+
+ private static final RpcResponseParser defaultResponseParser = ArmeriaServerParser::parseResponse;
+
+ /**
+ * Returns the default {@link RpcRequestParser}.
+ */
+ public static RpcRequestParser requestParser() {
+ return defaultRequestParser;
+ }
+
+ /**
+ * Returns the default {@link RpcResponseParser}.
+ */
+ public static RpcResponseParser responseParser() {
+ return defaultResponseParser;
+ }
+
+ private ArmeriaRpcServerParser() {}
+}
diff --git a/brave/brave6/src/main/java/com/linecorp/armeria/server/brave/ArmeriaServerParser.java b/brave/brave6/src/main/java/com/linecorp/armeria/server/brave/ArmeriaServerParser.java
new file mode 100644
index 00000000000..9048f53a660
--- /dev/null
+++ b/brave/brave6/src/main/java/com/linecorp/armeria/server/brave/ArmeriaServerParser.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2019 LINE Corporation
+ *
+ * LINE Corporation licenses this file to you 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 com.linecorp.armeria.server.brave;
+
+import com.linecorp.armeria.common.logging.RequestLog;
+import com.linecorp.armeria.internal.common.brave.SpanTags;
+import com.linecorp.armeria.server.ServiceRequestContext;
+
+import brave.Request;
+import brave.Response;
+import brave.Span;
+import brave.SpanCustomizer;
+import brave.propagation.TraceContext;
+
+final class ArmeriaServerParser {
+
+ private ArmeriaServerParser() {
+ }
+
+ static void parseRequest(Request req, TraceContext context, SpanCustomizer span) {
+ final Object unwrapped = req.unwrap();
+ if (!(unwrapped instanceof ServiceRequestContext)) {
+ return;
+ }
+ final ServiceRequestContext ctx = (ServiceRequestContext) unwrapped;
+ span.tag(SpanTags.TAG_HTTP_HOST, ctx.request().authority())
+ .tag(SpanTags.TAG_HTTP_URL, ctx.request().uri().toString())
+ .tag(SpanTags.TAG_HTTP_PROTOCOL, ctx.sessionProtocol().uriText())
+ .tag(SpanTags.TAG_ADDRESS_REMOTE, ctx.remoteAddress().toString())
+ .tag(SpanTags.TAG_ADDRESS_LOCAL, ctx.localAddress().toString());
+ }
+
+ static void parseResponse(Response res, TraceContext context, SpanCustomizer span) {
+ final Object unwrapped = res.unwrap();
+ if (!(unwrapped instanceof ServiceRequestContext)) {
+ return;
+ }
+ final ServiceRequestContext ctx = (ServiceRequestContext) unwrapped;
+ final RequestLog requestLog = ctx.log().ensureComplete();
+ final String serFmt = ServiceRequestContextAdapter.serializationFormat(requestLog);
+ if (serFmt != null) {
+ span.tag(SpanTags.TAG_HTTP_SERIALIZATION_FORMAT, serFmt);
+ }
+
+ final String name = requestLog.name();
+ if (name != null) {
+ span.name(name);
+ }
+ }
+
+ static void annotateWireSpan(RequestLog log, Span span) {
+ span.start(log.requestStartTimeMicros());
+ final Long wireReceiveTimeNanos = log.requestFirstBytesTransferredTimeNanos();
+ assert wireReceiveTimeNanos != null;
+ SpanTags.logWireReceive(span, wireReceiveTimeNanos, log);
+
+ final Long wireSendTimeNanos = log.responseFirstBytesTransferredTimeNanos();
+ if (wireSendTimeNanos != null) {
+ SpanTags.logWireSend(span, wireSendTimeNanos, log);
+ } else {
+ // If the client timed-out the request, we will have never sent any response data at all.
+ }
+ }
+}
diff --git a/brave/brave6/src/main/java/com/linecorp/armeria/server/brave/BraveRpcService.java b/brave/brave6/src/main/java/com/linecorp/armeria/server/brave/BraveRpcService.java
new file mode 100644
index 00000000000..d5c510ac6f8
--- /dev/null
+++ b/brave/brave6/src/main/java/com/linecorp/armeria/server/brave/BraveRpcService.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2025 LINE Corporation
+ *
+ * LINE Corporation licenses this file to you 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 com.linecorp.armeria.server.brave;
+
+import static com.linecorp.armeria.internal.common.brave.TraceContextUtil.ensureScopeUsesRequestContext;
+import static com.linecorp.armeria.server.brave.ArmeriaServerParser.annotateWireSpan;
+import static com.linecorp.armeria.server.brave.BraveService.SERVICE_REQUEST_DECORATING_SCOPE;
+
+import java.util.function.Function;
+
+import com.linecorp.armeria.common.RpcRequest;
+import com.linecorp.armeria.common.RpcResponse;
+import com.linecorp.armeria.common.brave.RequestContextCurrentTraceContext;
+import com.linecorp.armeria.internal.common.RequestContextExtension;
+import com.linecorp.armeria.server.RpcService;
+import com.linecorp.armeria.server.ServiceRequestContext;
+import com.linecorp.armeria.server.SimpleDecoratingRpcService;
+import com.linecorp.armeria.server.TransientServiceOption;
+
+import brave.Span;
+import brave.Tracer;
+import brave.Tracer.SpanInScope;
+import brave.Tracing;
+import brave.rpc.RpcRequestParser;
+import brave.rpc.RpcResponseParser;
+import brave.rpc.RpcServerHandler;
+import brave.rpc.RpcServerRequest;
+import brave.rpc.RpcServerResponse;
+import brave.rpc.RpcTracing;
+
+/**
+ * Decorates an {@link RpcService} to trace inbound {@link RpcRequest}s using
+ * Brave.
+ */
+public final class BraveRpcService extends SimpleDecoratingRpcService {
+
+ private static final RpcRequestParser defaultRequestParser = (request, context, span) -> {
+ RpcRequestParser.DEFAULT.parse(request, context, span);
+ ArmeriaRpcServerParser.requestParser().parse(request, context, span);
+ };
+
+ private static final RpcResponseParser defaultResponseParser = (response, context, span) -> {
+ RpcResponseParser.DEFAULT.parse(response, context, span);
+ ArmeriaRpcServerParser.responseParser().parse(response, context, span);
+ };
+
+ /**
+ * Creates a new tracing {@link RpcService} decorator using the specified {@link Tracing} instance.
+ */
+ public static Function super RpcService, BraveRpcService>
+ newDecorator(Tracing tracing) {
+ return newDecorator(RpcTracing.newBuilder(tracing)
+ .serverRequestParser(defaultRequestParser)
+ .serverResponseParser(defaultResponseParser)
+ .build());
+ }
+
+ /**
+ * Creates a new tracing {@link RpcService} decorator using the specified {@link RpcTracing} instance.
+ */
+ public static Function super RpcService, BraveRpcService>
+ newDecorator(RpcTracing rpcTracing) {
+ ensureScopeUsesRequestContext(rpcTracing.tracing());
+ return service -> new BraveRpcService(service, rpcTracing);
+ }
+
+ private final Tracer tracer;
+ private final RpcServerHandler handler;
+ private final RequestContextCurrentTraceContext currentTraceContext;
+
+ private BraveRpcService(RpcService delegate, RpcTracing rpcTracing) {
+ super(delegate);
+ final Tracing tracing = rpcTracing.tracing();
+ tracer = tracing.tracer();
+ handler = RpcServerHandler.create(rpcTracing);
+ currentTraceContext = (RequestContextCurrentTraceContext) tracing.currentTraceContext();
+ }
+
+ @Override
+ public RpcResponse serve(ServiceRequestContext ctx, RpcRequest req) throws Exception {
+ if (!ctx.config().transientServiceOptions().contains(TransientServiceOption.WITH_TRACING)) {
+ return unwrap().serve(ctx, req);
+ }
+
+ final RpcServerRequest braveReq = ServiceRequestContextAdapter.asRpcServerRequest(ctx);
+ final Span span = handler.handleReceive(braveReq);
+
+ final RequestContextExtension ctxExtension = ctx.as(RequestContextExtension.class);
+ if (currentTraceContext.scopeDecoratorAdded() && !span.isNoop() && ctxExtension != null) {
+ // Run the scope decorators when the ctx is pushed to the thread local.
+ ctxExtension.hook(() -> currentTraceContext.decorateScope(span.context(),
+ SERVICE_REQUEST_DECORATING_SCOPE));
+ }
+
+ maybeAddTagsToSpan(ctx, braveReq, span);
+ try (SpanInScope ignored = tracer.withSpanInScope(span)) {
+ return unwrap().serve(ctx, req);
+ }
+ }
+
+ private void maybeAddTagsToSpan(ServiceRequestContext ctx, RpcServerRequest braveReq, Span span) {
+ if (span.isNoop()) {
+ // For no-op spans, nothing special to do.
+ return;
+ }
+
+ ctx.log().whenComplete().thenAccept(log -> {
+ annotateWireSpan(log, span);
+ final RpcServerResponse braveRes =
+ ServiceRequestContextAdapter.asRpcServerResponse(ctx, log, braveReq);
+ handler.handleSend(braveRes, span);
+ });
+ }
+}
diff --git a/brave/brave6/src/main/java/com/linecorp/armeria/server/brave/BraveService.java b/brave/brave6/src/main/java/com/linecorp/armeria/server/brave/BraveService.java
index 2fbd4d905d3..77e0c5617a7 100644
--- a/brave/brave6/src/main/java/com/linecorp/armeria/server/brave/BraveService.java
+++ b/brave/brave6/src/main/java/com/linecorp/armeria/server/brave/BraveService.java
@@ -17,14 +17,16 @@
package com.linecorp.armeria.server.brave;
import static com.linecorp.armeria.internal.common.brave.TraceContextUtil.ensureScopeUsesRequestContext;
+import static com.linecorp.armeria.server.brave.ArmeriaServerParser.annotateWireSpan;
import java.util.function.Function;
+import com.google.common.annotations.VisibleForTesting;
+
import com.linecorp.armeria.common.HttpRequest;
import com.linecorp.armeria.common.HttpResponse;
import com.linecorp.armeria.common.brave.RequestContextCurrentTraceContext;
import com.linecorp.armeria.internal.common.RequestContextExtension;
-import com.linecorp.armeria.internal.common.brave.SpanTags;
import com.linecorp.armeria.server.HttpService;
import com.linecorp.armeria.server.ServiceRequestContext;
import com.linecorp.armeria.server.SimpleDecoratingHttpService;
@@ -34,6 +36,8 @@
import brave.Tracer;
import brave.Tracer.SpanInScope;
import brave.Tracing;
+import brave.http.HttpRequestParser;
+import brave.http.HttpResponseParser;
import brave.http.HttpServerHandler;
import brave.http.HttpServerRequest;
import brave.http.HttpServerResponse;
@@ -46,7 +50,19 @@
*/
public final class BraveService extends SimpleDecoratingHttpService {
- private static final Scope SERVICE_REQUEST_DECORATING_SCOPE = new Scope() {
+ @VisibleForTesting
+ static final HttpRequestParser defaultRequestParser = (request, context, span) -> {
+ HttpRequestParser.DEFAULT.parse(request, context, span);
+ ArmeriaHttpServerParser.requestParser().parse(request, context, span);
+ };
+
+ @VisibleForTesting
+ static final HttpResponseParser defaultResponseParser = (response, context, span) -> {
+ HttpResponseParser.DEFAULT.parse(response, context, span);
+ ArmeriaHttpServerParser.responseParser().parse(response, context, span);
+ };
+
+ static final Scope SERVICE_REQUEST_DECORATING_SCOPE = new Scope() {
@Override
public void close() {}
@@ -62,8 +78,8 @@ public String toString() {
public static Function super HttpService, BraveService>
newDecorator(Tracing tracing) {
return newDecorator(HttpTracing.newBuilder(tracing)
- .serverRequestParser(ArmeriaHttpServerParser.get())
- .serverResponseParser(ArmeriaHttpServerParser.get())
+ .serverRequestParser(defaultRequestParser)
+ .serverResponseParser(defaultResponseParser)
.build());
}
@@ -120,19 +136,7 @@ private void maybeAddTagsToSpan(ServiceRequestContext ctx, HttpServerRequest bra
}
ctx.log().whenComplete().thenAccept(log -> {
- span.start(log.requestStartTimeMicros());
-
- final Long wireReceiveTimeNanos = log.requestFirstBytesTransferredTimeNanos();
- assert wireReceiveTimeNanos != null;
- SpanTags.logWireReceive(span, wireReceiveTimeNanos, log);
-
- final Long wireSendTimeNanos = log.responseFirstBytesTransferredTimeNanos();
- if (wireSendTimeNanos != null) {
- SpanTags.logWireSend(span, wireSendTimeNanos, log);
- } else {
- // If the client timed-out the request, we will have never sent any response data at all.
- }
-
+ annotateWireSpan(log, span);
final HttpServerResponse braveRes =
ServiceRequestContextAdapter.asHttpServerResponse(log, braveReq);
handler.handleSend(braveRes, span);
diff --git a/brave/brave6/src/main/java/com/linecorp/armeria/server/brave/ServiceRequestContextAdapter.java b/brave/brave6/src/main/java/com/linecorp/armeria/server/brave/ServiceRequestContextAdapter.java
index 6381712bcb4..57925cc411b 100644
--- a/brave/brave6/src/main/java/com/linecorp/armeria/server/brave/ServiceRequestContextAdapter.java
+++ b/brave/brave6/src/main/java/com/linecorp/armeria/server/brave/ServiceRequestContextAdapter.java
@@ -17,6 +17,8 @@
package com.linecorp.armeria.server.brave;
import com.linecorp.armeria.common.RequestContext;
+import com.linecorp.armeria.common.RpcRequest;
+import com.linecorp.armeria.common.RpcResponse;
import com.linecorp.armeria.common.SerializationFormat;
import com.linecorp.armeria.common.annotation.Nullable;
import com.linecorp.armeria.common.logging.RequestLog;
@@ -27,6 +29,8 @@
import brave.Span;
import brave.http.HttpServerHandler;
+import brave.rpc.RpcServerRequest;
+import brave.rpc.RpcServerResponse;
/**
* Wraps {@link ServiceRequestContext} in an {@link brave.http.HttpServerRequest}, for use in
@@ -147,5 +151,104 @@ static String serializationFormat(RequestLog requestLog) {
return serFmt == SerializationFormat.NONE ? null : serFmt.uriText();
}
+ static RpcServerRequest asRpcServerRequest(ServiceRequestContext ctx) {
+ return new ArmeriaRpcServerRequest(ctx);
+ }
+
+ private static class ArmeriaRpcServerRequest extends RpcServerRequest {
+
+ private final ServiceRequestContext ctx;
+
+ ArmeriaRpcServerRequest(ServiceRequestContext ctx) {
+ this.ctx = ctx;
+ }
+
+ @Override
+ public long startTimestamp() {
+ return ctx.log().ensureAvailable(RequestLogProperty.REQUEST_START_TIME).requestStartTimeMicros();
+ }
+
+ @Nullable
+ @Override
+ public String method() {
+ final RpcRequest rpcRequest = ctx.rpcRequest();
+ if (rpcRequest == null) {
+ return null;
+ }
+ return rpcRequest.method();
+ }
+
+ @Nullable
+ @Override
+ public String service() {
+ final RpcRequest rpcRequest = ctx.rpcRequest();
+ if (rpcRequest == null) {
+ return null;
+ }
+ return rpcRequest.serviceName();
+ }
+
+ @Nullable
+ @Override
+ protected String propagationField(String keyName) {
+ return ctx.request().headers().get(keyName);
+ }
+
+ @Override
+ public Object unwrap() {
+ return ctx;
+ }
+ }
+
+ static RpcServerResponse asRpcServerResponse(ServiceRequestContext ctx, RequestLog log,
+ RpcServerRequest braveReq) {
+ return new ArmeriaRpcServerResponse(ctx, log, braveReq);
+ }
+
+ private static class ArmeriaRpcServerResponse extends RpcServerResponse {
+
+ private final ServiceRequestContext ctx;
+ private final RequestLog log;
+ private final RpcServerRequest braveReq;
+
+ ArmeriaRpcServerResponse(ServiceRequestContext ctx, RequestLog log, RpcServerRequest braveReq) {
+ this.ctx = ctx;
+ this.log = log;
+ this.braveReq = braveReq;
+ }
+
+ @Override
+ public brave.rpc.RpcRequest request() {
+ return braveReq;
+ }
+
+ @Nullable
+ @Override
+ public String errorCode() {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public Throwable error() {
+ final Object content = log.responseContent();
+ if (!(content instanceof RpcResponse)) {
+ return null;
+ }
+ final RpcResponse response = (RpcResponse) content;
+ return response.cause();
+ }
+
+ @Override
+ public long finishTimestamp() {
+ return SpanContextUtil.wallTimeMicros(log, log.responseEndTimeNanos());
+ }
+
+ @Override
+ public Object unwrap() {
+ return ctx;
+ }
+ }
+
private ServiceRequestContextAdapter() {}
}
diff --git a/brave/brave6/src/test/java/com/linecorp/armeria/it/brave/BraveIntegrationTest.java b/brave/brave6/src/test/java/com/linecorp/armeria/it/brave/BraveIntegrationTest.java
index 1b9efe9c341..358e663b0c7 100644
--- a/brave/brave6/src/test/java/com/linecorp/armeria/it/brave/BraveIntegrationTest.java
+++ b/brave/brave6/src/test/java/com/linecorp/armeria/it/brave/BraveIntegrationTest.java
@@ -22,6 +22,7 @@
import static com.linecorp.armeria.common.SessionProtocol.H1C;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.awaitility.Awaitility.await;
import java.time.Duration;
import java.util.ArrayList;
@@ -40,10 +41,12 @@
import org.apache.thrift.async.AsyncMethodCallback;
import org.apache.thrift.transport.TTransportException;
+import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
@@ -56,11 +59,14 @@
import com.linecorp.armeria.client.WebClient;
import com.linecorp.armeria.client.brave.BraveClient;
import com.linecorp.armeria.client.thrift.ThriftClients;
+import com.linecorp.armeria.common.AggregatedHttpResponse;
import com.linecorp.armeria.common.HttpRequest;
import com.linecorp.armeria.common.HttpResponse;
import com.linecorp.armeria.common.HttpStatus;
import com.linecorp.armeria.common.MediaType;
import com.linecorp.armeria.common.RequestContext;
+import com.linecorp.armeria.common.RpcRequest;
+import com.linecorp.armeria.common.RpcResponse;
import com.linecorp.armeria.common.brave.RequestContextCurrentTraceContext;
import com.linecorp.armeria.common.thrift.ThriftFuture;
import com.linecorp.armeria.common.util.ThreadFactories;
@@ -68,8 +74,11 @@
import com.linecorp.armeria.internal.testing.GenerateNativeImageTrace;
import com.linecorp.armeria.server.AbstractHttpService;
import com.linecorp.armeria.server.HttpService;
+import com.linecorp.armeria.server.RpcService;
import com.linecorp.armeria.server.ServerBuilder;
import com.linecorp.armeria.server.ServiceRequestContext;
+import com.linecorp.armeria.server.SimpleDecoratingRpcService;
+import com.linecorp.armeria.server.brave.BraveRpcService;
import com.linecorp.armeria.server.brave.BraveService;
import com.linecorp.armeria.server.thrift.THttpService;
import com.linecorp.armeria.testing.junit5.server.ServerExtension;
@@ -90,18 +99,10 @@
@GenerateNativeImageTrace
class BraveIntegrationTest {
+ private static final String CLIENT_TYPE_HEADER = "x-client-type";
+ private static final String TIMEOUT_HEADER = "x-timeout";
private static final SpanHandlerImpl spanHandler = new SpanHandlerImpl();
- private static TestService.Iface fooClient;
- private static TestService.Iface fooClientWithoutTracing;
- private static TestService.Iface timeoutClient;
- private static TestService.Iface timeoutClientClientTimesOut;
- private static TestService.Iface http1TimeoutClientClientTimesOut;
- private static TestService.AsyncIface barClient;
- private static TestService.AsyncIface quxClient;
- private static TestService.Iface zipClient;
- private static WebClient poolWebClient;
-
@RegisterExtension
static ServerExtension server = new ServerExtension(true) {
@Override
@@ -110,33 +111,31 @@ protected void configure(ServerBuilder sb) throws Exception {
// that a client cancels a request before a server receives it.
sb.requestTimeout(Duration.ofSeconds(10));
- sb.service("/foo", decorate("service/foo", THttpService.of(
- (AsyncIface) (name, resultHandler) ->
- barClient.hello("Miss. " + name, new DelegatingCallback(resultHandler)))));
-
- sb.service("/bar", decorate("service/bar", THttpService.of(
- (AsyncIface) (name, resultHandler) -> {
- if (name.startsWith("Miss. ")) {
- name = "Ms. " + name.substring(6);
- }
- quxClient.hello(name, new DelegatingCallback(resultHandler));
- })));
-
- sb.service("/zip", decorate("service/zip", THttpService.of(
- (AsyncIface) (name, resultHandler) -> {
- final ThriftFuture f1 = new ThriftFuture<>();
- final ThriftFuture f2 = new ThriftFuture<>();
- quxClient.hello(name, f1);
- quxClient.hello(name, f2);
- CompletableFuture.allOf(f1, f2).whenCompleteAsync((aVoid, throwable) -> {
- resultHandler.onComplete(f1.getNow(null) + ", and " + f2.getNow(null));
- }, RequestContext.current().eventLoop());
- })));
-
- sb.service("/qux", decorate("service/qux", THttpService.of(
- (AsyncIface) (name, resultHandler) -> resultHandler.onComplete("Hello, " + name + '!'))));
-
- sb.service("/pool", decorate("service/pool", new AbstractHttpService() {
+ sb.service("/foo", tHttpDecorate("service/foo", (name, resultHandler) ->
+ newClient("/bar").hello("Miss. " + name, new DelegatingCallback(resultHandler))));
+
+ sb.service("/bar", tHttpDecorate("service/bar", (name, resultHandler) -> {
+ if (name.startsWith("Miss. ")) {
+ name = "Ms. " + name.substring(6);
+ }
+ newClient("/qux").hello(name, new DelegatingCallback(resultHandler));
+ }));
+
+ sb.service("/zip", tHttpDecorate("service/zip", (name, resultHandler) -> {
+ final ThriftFuture f1 = new ThriftFuture<>();
+ final ThriftFuture f2 = new ThriftFuture<>();
+ newClient("/qux").hello(name, f1);
+ newClient("/qux").hello(name, f2);
+ CompletableFuture.allOf(f1, f2).whenCompleteAsync((aVoid, throwable) -> {
+ resultHandler.onComplete(f1.getNow(null) + ", and " + f2.getNow(null));
+ }, RequestContext.current().eventLoop());
+ }));
+
+ sb.service("/qux", tHttpDecorate("service/qux", (name, resultHandler) ->
+ resultHandler.onComplete("Hello, " + name + '!')));
+
+ final Tracing servicePoolTracing = newTracing("service/pool");
+ sb.service("/pool", httpDecorate(servicePoolTracing, new AbstractHttpService() {
@Override
protected HttpResponse doGet(ServiceRequestContext ctx, HttpRequest req)
throws Exception {
@@ -151,7 +150,7 @@ protected HttpResponse doGet(ServiceRequestContext ctx, HttpRequest req)
countDownLatch.countDown();
countDownLatch.await();
}
- final Span span = Tracing.currentTracer().nextSpan().start();
+ final Span span = servicePoolTracing.tracer().nextSpan().start();
try (SpanInScope unused =
Tracing.currentTracer().withSpanInScope(span)) {
if (i == 1) {
@@ -172,8 +171,8 @@ protected HttpResponse doGet(ServiceRequestContext ctx, HttpRequest req)
result -> allAsList(IntStream.range(1, 3).mapToObj(
i -> executorService.submit(
RequestContext.current().makeContextAware(() -> {
- final ScopedSpan span = Tracing.currentTracer()
- .startScopedSpan("aloha");
+ final ScopedSpan span = servicePoolTracing
+ .tracer().startScopedSpan("aloha");
try {
return null;
} finally {
@@ -191,64 +190,55 @@ protected HttpResponse doGet(ServiceRequestContext ctx, HttpRequest req)
}
}));
- sb.service("/timeout", decorate("service/timeout", THttpService.of(
- // This service never calls the handler and will timeout.
- (AsyncIface) (name, resultHandler) -> {
- })));
+ sb.service("/timeout",
+ tHttpDecorate("service/timeout",
+ // This service never calls the handler and will timeout.
+ (name, resultHandler) -> {
+ final ServiceRequestContext ctx = ServiceRequestContext.current();
+ if (ctx.request().headers().contains(TIMEOUT_HEADER)) {
+ ctx.timeoutNow();
+ }
+ }));
sb.service("/http", (req, ctx) -> HttpResponse.of(HttpStatus.OK));
}
};
- @BeforeEach
- void setupClients() {
- fooClient = ThriftClients.builder(server.httpUri())
- .path("/foo")
- .decorator(BraveClient.newDecorator(newTracing("client/foo")))
- .build(TestService.Iface.class);
- zipClient = ThriftClients.builder(server.httpUri())
- .path("/zip")
- .decorator(BraveClient.newDecorator(newTracing("client/zip")))
- .build(TestService.Iface.class);
- fooClientWithoutTracing = ThriftClients.newClient(server.httpUri() + "/foo", TestService.Iface.class);
- barClient = newClient("/bar");
- quxClient = newClient("/qux");
- poolWebClient = WebClient.of(server.httpUri());
- timeoutClient = ThriftClients.builder(server.httpUri())
- .path("/timeout")
- .decorator(BraveClient.newDecorator(newTracing("client/timeout")))
- .build(TestService.Iface.class);
- timeoutClientClientTimesOut =
- ThriftClients.builder(server.httpUri())
- .path("/timeout")
- .decorator(BraveClient.newDecorator(newTracing("client/timeout")))
- .responseTimeout(Duration.ofSeconds(3))
- .build(TestService.Iface.class);
- http1TimeoutClientClientTimesOut =
- ThriftClients.builder(server.uri(H1C))
- .path("/timeout")
- .decorator(BraveClient.newDecorator(newTracing("client/timeout")))
- .responseTimeout(Duration.ofSeconds(3))
- .build(TestService.Iface.class);
+ @AfterEach
+ void afterEach() {
+ assertThat(spanHandler.spans).isEmpty();
}
- @AfterEach
- void tearDown() {
+ @AfterAll
+ static void afterAll() throws Exception {
Tracing.current().close();
}
- @AfterEach
- void shouldHaveNoExtraSpans() {
- assertThat(spanHandler.spans).isEmpty();
+ private static HttpService tHttpDecorate(String name, AsyncIface asyncIface) {
+ final THttpService service =
+ THttpService.builder()
+ .addService(asyncIface)
+ .decorate(delegate -> new HeaderBasedBraveRpcService(delegate, name))
+ .build();
+ return (ctx, req) -> {
+ final String braveServiceType = ctx.request().headers().get(CLIENT_TYPE_HEADER);
+ if ("http".equals(braveServiceType)) {
+ return BraveService.newDecorator(newTracing(name)).apply(service).serve(ctx, req);
+ }
+ return service.serve(ctx, req);
+ };
}
- private static BraveService decorate(String name, HttpService service) {
- return BraveService.newDecorator(newTracing(name)).apply(service);
+ private static HttpService httpDecorate(Tracing tracing, HttpService service) {
+ return BraveService.newDecorator(tracing).apply(service);
}
private static TestService.AsyncIface newClient(String path) {
+ final ServiceRequestContext ctx = ServiceRequestContext.current();
+ final String braveServiceType = ctx.request().headers().get(CLIENT_TYPE_HEADER);
return ThriftClients.builder(server.httpUri())
.path(path)
+ .addHeader(CLIENT_TYPE_HEADER, braveServiceType)
.decorator(BraveClient.newDecorator(newTracing("client" + path)))
.build(TestService.AsyncIface.class);
}
@@ -299,8 +289,15 @@ void testTimingAnnotations() {
}
}
- @Test
- void testServiceHasMultipleClientRequests() throws Exception {
+ @ParameterizedTest
+ @ValueSource(strings = {"http", "rpc"})
+ void testServiceHasMultipleClientRequests(String type) throws Exception {
+ final TestService.Iface zipClient =
+ ThriftClients.builder(server.httpUri())
+ .addHeader(CLIENT_TYPE_HEADER, type)
+ .path("/zip")
+ .decorator(BraveClient.newDecorator(newTracing("client/zip")))
+ .build(TestService.Iface.class);
assertThat(zipClient.hello("Lee")).isEqualTo("Hello, Lee!, and Hello, Lee!");
final MutableSpan[] spans = spanHandler.take(6);
@@ -308,8 +305,15 @@ void testServiceHasMultipleClientRequests() throws Exception {
assertThat(spans).allMatch(s -> s.traceId().equals(traceId));
}
- @Test
- void testClientInitiatedTrace() throws Exception {
+ @ParameterizedTest
+ @ValueSource(strings = {"http", "rpc"})
+ void testClientInitiatedTrace(String type) throws Exception {
+ final TestService.Iface fooClient =
+ ThriftClients.builder(server.httpUri())
+ .path("/foo")
+ .addHeader(CLIENT_TYPE_HEADER, type)
+ .decorator(BraveClient.newDecorator(newTracing("client/foo")))
+ .build(TestService.Iface.class);
assertThat(fooClient.hello("Lee")).isEqualTo("Hello, Ms. Lee!");
final MutableSpan[] spans = spanHandler.take(6);
@@ -397,8 +401,13 @@ void testClientInitiatedTrace() throws Exception {
assertThat(serverEndTime).isGreaterThanOrEqualTo(serverWireSendTime);
}
- @Test
- void testServiceInitiatedTrace() throws Exception {
+ @ParameterizedTest
+ @ValueSource(strings = {"http", "rpc"})
+ void testServiceInitiatedTrace(String type) throws Exception {
+ final TestService.Iface fooClientWithoutTracing =
+ ThriftClients.builder(server.httpUri() + "/foo")
+ .addHeader(CLIENT_TYPE_HEADER, type)
+ .build(TestService.Iface.class);
assertThat(fooClientWithoutTracing.hello("Lee")).isEqualTo("Hello, Ms. Lee!");
final MutableSpan[] spans = spanHandler.take(5);
@@ -441,7 +450,9 @@ void testServiceInitiatedTrace() throws Exception {
@Test
void testSpanInThreadPoolHasSameTraceId() throws Exception {
- poolWebClient.get("pool").aggregate().get();
+ final AggregatedHttpResponse res = server.blockingWebClient().get("pool");
+ assertThat(res.contentUtf8()).isEqualTo("Lee");
+ await().untilAsserted(() -> assertThat(spanHandler.spans).hasSize(5));
final MutableSpan[] spans = spanHandler.take(5);
assertThat(Arrays.stream(spans).map(MutableSpan::traceId).collect(toImmutableSet())).hasSize(1);
assertThat(Arrays.stream(spans).map(MutableSpan::parentId)
@@ -449,29 +460,57 @@ void testSpanInThreadPoolHasSameTraceId() throws Exception {
.collect(toImmutableSet())).hasSize(1);
}
- @Test
- void testServerTimesOut() throws Exception {
- assertThatThrownBy(() -> timeoutClient.hello("name"))
- .isInstanceOf(TTransportException.class)
- .hasCauseInstanceOf(InvalidResponseHeadersException.class);
- final MutableSpan[] spans = spanHandler.take(2);
-
- final MutableSpan serverSpan = findSpan(spans, "service/timeout");
- final MutableSpan clientSpan = findSpan(spans, "client/timeout");
-
- // Server timed out meaning it did still send a timeout response to the client and we have all
- // annotations.
- assertThat(serverSpan.annotations()).hasSize(2);
- assertThat(clientSpan.annotations()).hasSize(2);
+ @ParameterizedTest
+ @ValueSource(strings = {"http", "rpc"})
+ void testServerTimesOut(String type) throws Exception {
+ try (ClientFactory cf = ClientFactory.builder().build()) {
+ final TestService.Iface timeoutClient =
+ ThriftClients.builder(server.httpUri())
+ .path("/timeout")
+ .factory(cf)
+ .addHeader(CLIENT_TYPE_HEADER, type)
+ .addHeader(TIMEOUT_HEADER, true)
+ .decorator(BraveClient.newDecorator(newTracing("client/timeout")))
+ .build(TestService.Iface.class);
+ assertThatThrownBy(() -> timeoutClient.hello("name"))
+ .isInstanceOf(TTransportException.class)
+ .hasCauseInstanceOf(InvalidResponseHeadersException.class);
+ final MutableSpan[] spans = spanHandler.take(2);
+
+ final MutableSpan serverSpan = findSpan(spans, "service/timeout");
+ final MutableSpan clientSpan = findSpan(spans, "client/timeout");
+
+ // Server timed out meaning it did still send a timeout response to the client and we have all
+ // annotations. A separate client factory is used to guarantee that client span annotations
+ // always contain connection related extra annotations.
+ assertThat(serverSpan.annotations()).hasSize(2);
+ assertThat(clientSpan.annotations()).hasSize(6);
+ }
}
- @Test
- void testHttp2ClientTimesOut() throws Exception {
+ @ParameterizedTest
+ @ValueSource(strings = {"http", "rpc"})
+ void testHttp2ClientTimesOut(String type) throws Exception {
+ final TestService.Iface timeoutClientClientTimesOut =
+ ThriftClients.builder(server.httpUri())
+ .path("/timeout")
+ .addHeader(CLIENT_TYPE_HEADER, type)
+ .decorator(BraveClient.newDecorator(newTracing("client/timeout")))
+ .responseTimeout(Duration.ofSeconds(1))
+ .build(TestService.Iface.class);
testClientTimesOut(timeoutClientClientTimesOut);
}
- @Test
- void testHttp1ClientTimesOut() throws Exception {
+ @ParameterizedTest
+ @ValueSource(strings = {"http", "rpc"})
+ void testHttp1ClientTimesOut(String type) throws Exception {
+ final TestService.Iface http1TimeoutClientClientTimesOut =
+ ThriftClients.builder(server.uri(H1C))
+ .path("/timeout")
+ .addHeader(CLIENT_TYPE_HEADER, type)
+ .decorator(BraveClient.newDecorator(newTracing("client/timeout")))
+ .responseTimeout(Duration.ofSeconds(3))
+ .build(TestService.Iface.class);
testClientTimesOut(http1TimeoutClientClientTimesOut);
}
@@ -566,8 +605,8 @@ public void onError(Exception exception) {
}
}
- private static class SpanHandlerImpl extends SpanHandler {
- private final BlockingQueue spans = new LinkedBlockingQueue<>();
+ static final class SpanHandlerImpl extends SpanHandler {
+ final BlockingQueue spans = new LinkedBlockingQueue<>();
@Override
public boolean end(TraceContext context, MutableSpan span, Cause cause) {
@@ -585,4 +624,26 @@ MutableSpan[] take(int numSpans) {
return taken.toArray(new MutableSpan[numSpans]);
}
}
+
+ private static class HeaderBasedBraveRpcService extends SimpleDecoratingRpcService {
+
+ private final RpcService delegate;
+ private final String name;
+
+ HeaderBasedBraveRpcService(RpcService delegate, String name) {
+ super(delegate);
+ this.delegate = delegate;
+ this.name = name;
+ }
+
+ @Override
+ public RpcResponse serve(ServiceRequestContext ctx,
+ RpcRequest req) throws Exception {
+ final String braveServiceType = ctx.request().headers().get(CLIENT_TYPE_HEADER);
+ if ("rpc".equals(braveServiceType)) {
+ return BraveRpcService.newDecorator(newTracing(name)).apply(delegate).serve(ctx, req);
+ }
+ return delegate.serve(ctx, req);
+ }
+ }
}
diff --git a/brave/brave6/src/test/java/com/linecorp/armeria/server/brave/BraveRpcServiceTest.java b/brave/brave6/src/test/java/com/linecorp/armeria/server/brave/BraveRpcServiceTest.java
new file mode 100644
index 00000000000..283a96d0a34
--- /dev/null
+++ b/brave/brave6/src/test/java/com/linecorp/armeria/server/brave/BraveRpcServiceTest.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright 2025 LINE Corporation
+ *
+ * LINE Corporation licenses this file to you 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 com.linecorp.armeria.server.brave;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.awaitility.Awaitility.await;
+
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import com.google.common.collect.ImmutableMap;
+
+import com.linecorp.armeria.client.thrift.ThriftClients;
+import com.linecorp.armeria.common.brave.RequestContextCurrentTraceContext;
+import com.linecorp.armeria.common.brave.TestSpanCollector;
+import com.linecorp.armeria.common.logging.RequestLog;
+import com.linecorp.armeria.internal.common.brave.SpanTags;
+import com.linecorp.armeria.server.ServerBuilder;
+import com.linecorp.armeria.server.ServiceRequestContext;
+import com.linecorp.armeria.server.thrift.THttpService;
+import com.linecorp.armeria.testing.junit5.server.ServerExtension;
+
+import brave.Tracing;
+import brave.handler.MutableSpan;
+import brave.rpc.RpcRequestParser;
+import brave.rpc.RpcResponseParser;
+import brave.rpc.RpcTags;
+import brave.rpc.RpcTracing;
+import testing.brave.TestService;
+import testing.brave.TestService.Iface;
+
+class BraveRpcServiceTest {
+
+ private static final String SAMPLE_HEADER = "x-should-sample";
+ private static final TestSpanCollector spanHandler = new TestSpanCollector();
+ private static final Tracing tracing =
+ Tracing.newBuilder()
+ .currentTraceContext(RequestContextCurrentTraceContext.ofDefault())
+ .addSpanHandler(spanHandler)
+ .build();
+
+ @RegisterExtension
+ static final ServerExtension server = new ServerExtension() {
+ @Override
+ protected void configure(ServerBuilder sb) throws Exception {
+ final RpcTracing defaultParserTracing =
+ RpcTracing.newBuilder(tracing)
+ .serverRequestParser((request, context, span) -> {
+ RpcRequestParser.DEFAULT.parse(request, context, span);
+ ArmeriaRpcServerParser.requestParser().parse(request, context, span);
+ })
+ .serverResponseParser((response, context, span) -> {
+ RpcResponseParser.DEFAULT.parse(response, context, span);
+ ArmeriaRpcServerParser.responseParser().parse(response, context, span);
+ })
+ .serverSampler(req -> {
+ final ServiceRequestContext ctx = (ServiceRequestContext) req.unwrap();
+ return ctx.request().headers().contains(SAMPLE_HEADER);
+ })
+ .build();
+
+ sb.service("/default-parser",
+ THttpService.builder()
+ .decorate(BraveRpcService.newDecorator(defaultParserTracing))
+ .addService((Iface) name -> "world")
+ .build());
+
+ final RpcTracing braveParserTracing = RpcTracing.newBuilder(tracing).build();
+ sb.service("/brave-parser",
+ THttpService.builder()
+ .decorate(BraveRpcService.newDecorator(braveParserTracing))
+ .addService((Iface) name -> "world")
+ .build());
+ }
+ };
+
+ @AfterEach
+ void afterEach() {
+ assertThat(spanHandler.spans()).isEmpty();
+ }
+
+ @AfterAll
+ static void afterAll() throws Exception {
+ tracing.close();
+ }
+
+ @Test
+ void rpcSampling() throws Exception {
+ final TestService.Iface samplingIface =
+ ThriftClients.builder(server.httpUri().resolve("/default-parser"))
+ .addHeader(SAMPLE_HEADER, true)
+ .build(Iface.class);
+ assertThat(samplingIface.hello("/")).isEqualTo("world");
+ await().untilAsserted(() -> assertThat(spanHandler.spans()).hasSize(1));
+ final MutableSpan span = spanHandler.spans().take();
+ assertThat(span.tag(RpcTags.SERVICE.key())).isEqualTo("testing.brave.TestService$Iface");
+ assertThat(span.tag(RpcTags.METHOD.key())).isEqualTo("hello");
+ }
+
+ @Test
+ void rpcNoSampling() throws Exception {
+ final TestService.Iface iface =
+ ThriftClients.newClient(server.httpUri().resolve("/default-parser"), Iface.class);
+ assertThat(iface.hello("/")).isEqualTo("world");
+ await().pollDelay(1, TimeUnit.SECONDS).untilAsserted(() -> assertThat(spanHandler.spans()).isEmpty());
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {"/default-parser", "/brave-parser"})
+ void parserBehavior(String path) throws Exception {
+ final TestService.Iface iface =
+ ThriftClients.builder(server.httpUri().resolve(path))
+ .addHeader(SAMPLE_HEADER, true)
+ .build(Iface.class);
+ assertThat(iface.hello("/")).isEqualTo("world");
+
+ await().untilAsserted(() -> assertThat(spanHandler.spans()).hasSize(1));
+ final MutableSpan span = spanHandler.spans().take();
+ final ServiceRequestContext sctx = server.requestContextCaptor().poll();
+ final RequestLog slog = sctx.log().whenComplete().join();
+
+ // wire annotations are recorded
+ assertThat(span.startTimestamp()).isEqualTo(slog.requestStartTimeMicros());
+ final Map annotations = ImmutableMap.copyOf(span.annotations());
+ assertThat(annotations).containsValues("wr", "ws");
+
+ // brave default tags are recorded
+ assertThat(span.tag(RpcTags.SERVICE.key())).isEqualTo("testing.brave.TestService$Iface");
+ assertThat(span.tag(RpcTags.METHOD.key())).isEqualTo("hello");
+
+ if ("/brave-parser".equals(path)) {
+ // armeria default tags are not recorded
+ assertThat(span.tags()).doesNotContainKey(SpanTags.TAG_HTTP_SERIALIZATION_FORMAT);
+ } else {
+ // armeria default tags are recorded
+ assertThat(span.tag(SpanTags.TAG_HTTP_SERIALIZATION_FORMAT)).isEqualTo("tbinary");
+ }
+ }
+}
diff --git a/brave/brave6/src/test/java/com/linecorp/armeria/server/brave/BraveServiceTest.java b/brave/brave6/src/test/java/com/linecorp/armeria/server/brave/BraveServiceTest.java
index 505e6b7be10..989644adc75 100644
--- a/brave/brave6/src/test/java/com/linecorp/armeria/server/brave/BraveServiceTest.java
+++ b/brave/brave6/src/test/java/com/linecorp/armeria/server/brave/BraveServiceTest.java
@@ -215,8 +215,8 @@ private static RequestLog testServiceInvocation(SpanHandler spanHandler,
.build();
final HttpTracing httpTracing = HttpTracing.newBuilder(tracing)
- .serverRequestParser(ArmeriaHttpServerParser.get())
- .serverResponseParser(ArmeriaHttpServerParser.get())
+ .serverRequestParser(BraveService.defaultRequestParser)
+ .serverResponseParser(BraveService.defaultResponseParser)
.build();
final HttpRequest req = HttpRequest.of(RequestHeaders.of(HttpMethod.POST, "/hello/trustin",
diff --git a/dependencies.toml b/dependencies.toml
index a0b8b740fcf..83f7d008ee2 100644
--- a/dependencies.toml
+++ b/dependencies.toml
@@ -256,6 +256,9 @@ version.ref = "brave5"
[libraries.brave5-context-slf4j]
module = 'io.zipkin.brave:brave-context-slf4j'
version.ref = "brave5"
+[libraries.brave5-instrumentation-rpc]
+module = "io.zipkin.brave:brave-instrumentation-rpc"
+version.ref = "brave5"
[libraries.brave5-instrumentation-http]
module = "io.zipkin.brave:brave-instrumentation-http"
version.ref = "brave5"
@@ -272,6 +275,9 @@ javadocs = [
[libraries.brave6-context-slf4j]
module = 'io.zipkin.brave:brave-context-slf4j'
version.ref = "brave6"
+[libraries.brave6-instrumentation-rpc]
+module = "io.zipkin.brave:brave-instrumentation-rpc"
+version.ref = "brave6"
[libraries.brave6-instrumentation-http]
module = "io.zipkin.brave:brave-instrumentation-http"
version.ref = "brave6"