Skip to content

Commit 2bc4f17

Browse files
jrhee17trustin
andauthored
Introduce BraveRpcService (#6115)
Motivation: The motivation for this PR is better described in #6084 The changeset in this PR attempts to: - Expose `ArmeriaHttpServerParser` and `ArmeriaRpcServerParser` - By doing so, users can choose what information must be extracted from `BraveService` or `BraveRpcService`. This can provide more flexibility on whether to use only `BraveService`, only `BraveRpcService`, or both `BraveService` and `BraveRpcService` - Introduce `BraveRpcService` which allows users to perform sampling or request/response parsing based on `RpcRequest` Modifications: - Added `BraveRpcService`. By default, armeria-specific tags/annotations recorded by `BraveRpcService` is the same as `BraveService` - Exposed `ArmeriaHttpServerParser` and `ArmeriaRpcServerParser` to allow users to easily construct a `RpcTracing` or `HttpTracing` Result: - Users may use `RpcRequest`, `RpcResponse` to apply sampling/tags/annotations <!-- Visit this URL to learn more about how to write a pull request description: https://armeria.dev/community/developer-guide#how-to-write-pull-request-description --> --------- Co-authored-by: Trustin Lee <trustin@linecorp.com>
1 parent b9b81a6 commit 2bc4f17

13 files changed

Lines changed: 866 additions & 212 deletions

File tree

brave/brave5/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
dependencies {
22
api libs.brave5
33
api libs.brave5.instrumentation.http
4+
api libs.brave5.instrumentation.rpc
45

56
if (project.ext.targetJavaVersion >= 11) {
67
testImplementation project(':thrift0.18')

brave/brave6/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
dependencies {
22
api libs.brave6
33
api libs.brave6.instrumentation.http
4+
api libs.brave6.instrumentation.rpc
45

56
if (project.ext.targetJavaVersion >= 11) {
67
testImplementation project(':thrift0.18')
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/*
2+
* Copyright 2025 LY Corporation
3+
*
4+
* LY Corporation licenses this file to you under the Apache License,
5+
* version 2.0 (the "License"); you may not use this file except in compliance
6+
* with the License. You may obtain a copy of the License at:
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations
14+
* under the License.
15+
*/
16+
17+
package com.linecorp.armeria.server.brave;
18+
19+
import static com.linecorp.armeria.server.brave.ArmeriaServerParser.annotateWireSpan;
20+
import static com.linecorp.armeria.server.brave.BraveService.SERVICE_REQUEST_DECORATING_SCOPE;
21+
22+
import com.linecorp.armeria.common.Request;
23+
import com.linecorp.armeria.common.Response;
24+
import com.linecorp.armeria.common.brave.RequestContextCurrentTraceContext;
25+
import com.linecorp.armeria.common.logging.RequestLog;
26+
import com.linecorp.armeria.internal.common.RequestContextExtension;
27+
import com.linecorp.armeria.server.Service;
28+
import com.linecorp.armeria.server.ServiceRequestContext;
29+
import com.linecorp.armeria.server.SimpleDecoratingService;
30+
import com.linecorp.armeria.server.TransientServiceOption;
31+
32+
import brave.Span;
33+
import brave.Tracer;
34+
import brave.Tracer.SpanInScope;
35+
36+
abstract class AbstractBraveService<BI extends brave.Request, BO extends brave.Response,
37+
I extends Request, O extends Response> extends SimpleDecoratingService<I, O> {
38+
39+
private final Tracer tracer;
40+
private final RequestContextCurrentTraceContext currentTraceContext;
41+
42+
/**
43+
* Creates a new instance that decorates the specified {@link Service}.
44+
*/
45+
protected AbstractBraveService(Service<I, O> delegate, Tracer tracer,
46+
RequestContextCurrentTraceContext currentTraceContext) {
47+
super(delegate);
48+
this.tracer = tracer;
49+
this.currentTraceContext = currentTraceContext;
50+
}
51+
52+
@Override
53+
public final O serve(ServiceRequestContext ctx, I req) throws Exception {
54+
if (!ctx.config().transientServiceOptions().contains(TransientServiceOption.WITH_TRACING)) {
55+
return unwrap().serve(ctx, req);
56+
}
57+
final BI braveReq = braveRequest(ctx);
58+
final Span span = handleReceive(braveReq);
59+
60+
final RequestContextExtension ctxExtension = ctx.as(RequestContextExtension.class);
61+
if (currentTraceContext.scopeDecoratorAdded() && !span.isNoop() && ctxExtension != null) {
62+
// Run the scope decorators when the ctx is pushed to the thread local.
63+
ctxExtension.hook(() -> currentTraceContext.decorateScope(span.context(),
64+
SERVICE_REQUEST_DECORATING_SCOPE));
65+
}
66+
67+
maybeAddTagsToSpan(ctx, braveReq, span);
68+
try (SpanInScope ignored = tracer.withSpanInScope(span)) {
69+
return unwrap().serve(ctx, req);
70+
}
71+
}
72+
73+
abstract BI braveRequest(ServiceRequestContext ctx);
74+
75+
abstract BO braveResponse(ServiceRequestContext ctx, RequestLog log, BI braveReq);
76+
77+
abstract Span handleReceive(BI braveReq);
78+
79+
abstract void handleSend(BO response, Span span);
80+
81+
void maybeAddTagsToSpan(ServiceRequestContext ctx, BI braveReq, Span span) {
82+
if (span.isNoop()) {
83+
// For no-op spans, nothing special to do.
84+
return;
85+
}
86+
87+
ctx.log().whenComplete().thenAccept(log -> {
88+
annotateWireSpan(log, span);
89+
final BO braveRes = braveResponse(ctx, log, braveReq);
90+
handleSend(braveRes, span);
91+
});
92+
}
93+
}

brave/brave6/src/main/java/com/linecorp/armeria/server/brave/ArmeriaHttpServerParser.java renamed to brave/brave6/src/main/java/com/linecorp/armeria/server/brave/ArmeriaServerParser.java

Lines changed: 25 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -16,51 +16,26 @@
1616

1717
package com.linecorp.armeria.server.brave;
1818

19-
import com.linecorp.armeria.common.RpcRequest;
2019
import com.linecorp.armeria.common.logging.RequestLog;
2120
import com.linecorp.armeria.internal.common.brave.SpanTags;
2221
import com.linecorp.armeria.server.ServiceRequestContext;
2322

23+
import brave.Request;
24+
import brave.Response;
25+
import brave.Span;
2426
import brave.SpanCustomizer;
25-
import brave.http.HttpRequestParser;
26-
import brave.http.HttpResponse;
27-
import brave.http.HttpResponseParser;
2827
import brave.propagation.TraceContext;
2928

30-
/**
31-
* Default implementation of {@link HttpRequestParser} and {@link HttpResponseParser} for servers.
32-
* This parser adds some custom tags and overwrites the name of span if {@link RequestLog#requestContent()}
33-
* is {@link RpcRequest}.
34-
* The following tags become available:
35-
* <ul>
36-
* <li>http.url</li>
37-
* <li>http.host</li>
38-
* <li>http.protocol</li>
39-
* <li>http.serfmt</li>
40-
* <li>address.remote</li>
41-
* <li>address.local</li>
42-
* </ul>
43-
*/
44-
final class ArmeriaHttpServerParser implements HttpRequestParser, HttpResponseParser {
45-
46-
private static final ArmeriaHttpServerParser INSTANCE = new ArmeriaHttpServerParser();
47-
48-
static ArmeriaHttpServerParser get() {
49-
return INSTANCE;
50-
}
29+
final class ArmeriaServerParser {
5130

52-
private ArmeriaHttpServerParser() {
31+
private ArmeriaServerParser() {
5332
}
5433

55-
@Override
56-
public void parse(brave.http.HttpRequest request, TraceContext context, SpanCustomizer span) {
57-
HttpRequestParser.DEFAULT.parse(request, context, span);
58-
59-
final Object unwrapped = request.unwrap();
34+
static void parseRequest(Request req, TraceContext context, SpanCustomizer span) {
35+
final Object unwrapped = req.unwrap();
6036
if (!(unwrapped instanceof ServiceRequestContext)) {
6137
return;
6238
}
63-
6439
final ServiceRequestContext ctx = (ServiceRequestContext) unwrapped;
6540
span.tag(SpanTags.TAG_HTTP_HOST, ctx.request().authority())
6641
.tag(SpanTags.TAG_HTTP_URL, ctx.request().uri().toString())
@@ -69,16 +44,12 @@ public void parse(brave.http.HttpRequest request, TraceContext context, SpanCust
6944
.tag(SpanTags.TAG_ADDRESS_LOCAL, ctx.localAddress().toString());
7045
}
7146

72-
@Override
73-
public void parse(HttpResponse response, TraceContext context, SpanCustomizer span) {
74-
HttpResponseParser.DEFAULT.parse(response, context, span);
75-
76-
final Object res = response.unwrap();
77-
if (!(res instanceof ServiceRequestContext)) {
47+
static void parseResponse(Response res, TraceContext context, SpanCustomizer span) {
48+
final Object unwrapped = res.unwrap();
49+
if (!(unwrapped instanceof ServiceRequestContext)) {
7850
return;
7951
}
80-
81-
final ServiceRequestContext ctx = (ServiceRequestContext) res;
52+
final ServiceRequestContext ctx = (ServiceRequestContext) unwrapped;
8253
final RequestLog requestLog = ctx.log().ensureComplete();
8354
final String serFmt = ServiceRequestContextAdapter.serializationFormat(requestLog);
8455
if (serFmt != null) {
@@ -90,4 +61,18 @@ public void parse(HttpResponse response, TraceContext context, SpanCustomizer sp
9061
span.name(name);
9162
}
9263
}
64+
65+
static void annotateWireSpan(RequestLog log, Span span) {
66+
span.start(log.requestStartTimeMicros());
67+
final Long wireReceiveTimeNanos = log.requestFirstBytesTransferredTimeNanos();
68+
assert wireReceiveTimeNanos != null;
69+
SpanTags.logWireReceive(span, wireReceiveTimeNanos, log);
70+
71+
final Long wireSendTimeNanos = log.responseFirstBytesTransferredTimeNanos();
72+
if (wireSendTimeNanos != null) {
73+
SpanTags.logWireSend(span, wireSendTimeNanos, log);
74+
} else {
75+
// If the client timed-out the request, we will have never sent any response data at all.
76+
}
77+
}
9378
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/*
2+
* Copyright 2025 LY Corporation
3+
*
4+
* LY Corporation licenses this file to you under the Apache License,
5+
* version 2.0 (the "License"); you may not use this file except in compliance
6+
* with the License. You may obtain a copy of the License at:
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations
14+
* under the License.
15+
*/
16+
17+
package com.linecorp.armeria.server.brave;
18+
19+
import static com.linecorp.armeria.internal.common.brave.TraceContextUtil.ensureScopeUsesRequestContext;
20+
21+
import java.util.function.Function;
22+
23+
import com.linecorp.armeria.common.RpcRequest;
24+
import com.linecorp.armeria.common.RpcResponse;
25+
import com.linecorp.armeria.common.annotation.UnstableApi;
26+
import com.linecorp.armeria.common.brave.RequestContextCurrentTraceContext;
27+
import com.linecorp.armeria.common.logging.RequestLog;
28+
import com.linecorp.armeria.server.RpcService;
29+
import com.linecorp.armeria.server.ServiceRequestContext;
30+
31+
import brave.Span;
32+
import brave.Tracing;
33+
import brave.rpc.RpcRequestParser;
34+
import brave.rpc.RpcResponseParser;
35+
import brave.rpc.RpcServerHandler;
36+
import brave.rpc.RpcServerRequest;
37+
import brave.rpc.RpcServerResponse;
38+
import brave.rpc.RpcTracing;
39+
40+
/**
41+
* Decorates an {@link RpcService} to trace inbound {@link RpcRequest}s using
42+
* <a href="https://github.com/openzipkin/brave">Brave</a>.
43+
*/
44+
@UnstableApi
45+
public final class BraveRpcService extends AbstractBraveService<RpcServerRequest, RpcServerResponse,
46+
RpcRequest, RpcResponse> implements RpcService {
47+
48+
private static final RpcRequestParser defaultRequestParser = (request, context, span) -> {
49+
RpcRequestParser.DEFAULT.parse(request, context, span);
50+
BraveServerParsers.rpcRequestParser().parse(request, context, span);
51+
};
52+
53+
private static final RpcResponseParser defaultResponseParser = (response, context, span) -> {
54+
RpcResponseParser.DEFAULT.parse(response, context, span);
55+
BraveServerParsers.rpcResponseParser().parse(response, context, span);
56+
};
57+
58+
/**
59+
* Creates a new tracing {@link RpcService} decorator using the specified {@link Tracing} instance.
60+
*/
61+
public static Function<? super RpcService, BraveRpcService>
62+
newDecorator(Tracing tracing) {
63+
return newDecorator(RpcTracing.newBuilder(tracing)
64+
.serverRequestParser(defaultRequestParser)
65+
.serverResponseParser(defaultResponseParser)
66+
.build());
67+
}
68+
69+
/**
70+
* Creates a new tracing {@link RpcService} decorator using the specified {@link RpcTracing} instance.
71+
*/
72+
public static Function<? super RpcService, BraveRpcService>
73+
newDecorator(RpcTracing rpcTracing) {
74+
ensureScopeUsesRequestContext(rpcTracing.tracing());
75+
return service -> new BraveRpcService(service, rpcTracing);
76+
}
77+
78+
private final RpcServerHandler handler;
79+
80+
private BraveRpcService(RpcService delegate, RpcTracing rpcTracing) {
81+
super(delegate, rpcTracing.tracing().tracer(),
82+
(RequestContextCurrentTraceContext) rpcTracing.tracing().currentTraceContext());
83+
handler = RpcServerHandler.create(rpcTracing);
84+
}
85+
86+
@Override
87+
RpcServerRequest braveRequest(ServiceRequestContext ctx) {
88+
return RpcServiceRequestContextAdapter.asRpcServerRequest(ctx);
89+
}
90+
91+
@Override
92+
RpcServerResponse braveResponse(ServiceRequestContext ctx, RequestLog log, RpcServerRequest braveReq) {
93+
return RpcServiceRequestContextAdapter.asRpcServerResponse(ctx, log, braveReq);
94+
}
95+
96+
@Override
97+
Span handleReceive(RpcServerRequest braveReq) {
98+
return handler.handleReceive(braveReq);
99+
}
100+
101+
@Override
102+
void handleSend(RpcServerResponse response, Span span) {
103+
handler.handleSend(response, span);
104+
}
105+
}

0 commit comments

Comments
 (0)