-
Notifications
You must be signed in to change notification settings - Fork 1k
Introduce BraveRpcService
#6115
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 7 commits
037585c
7f89976
45470ae
42fa304
8fc35e7
6d2fb8d
4fd2f99
df596b5
ca8b036
af242c3
15f5e09
23843d7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,91 @@ | ||
| /* | ||
| * 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.server.brave.ArmeriaServerParser.annotateWireSpan; | ||
| import static com.linecorp.armeria.server.brave.BraveService.SERVICE_REQUEST_DECORATING_SCOPE; | ||
|
|
||
| import com.linecorp.armeria.common.Request; | ||
| import com.linecorp.armeria.common.Response; | ||
| import com.linecorp.armeria.common.brave.RequestContextCurrentTraceContext; | ||
| import com.linecorp.armeria.common.logging.RequestLog; | ||
| import com.linecorp.armeria.internal.common.RequestContextExtension; | ||
| import com.linecorp.armeria.server.Service; | ||
| import com.linecorp.armeria.server.ServiceRequestContext; | ||
| import com.linecorp.armeria.server.SimpleDecoratingService; | ||
| import com.linecorp.armeria.server.TransientServiceOption; | ||
|
|
||
| import brave.Span; | ||
| import brave.Tracer; | ||
| import brave.Tracer.SpanInScope; | ||
|
|
||
| abstract class AbstractBraveService<BI extends brave.Request, BO extends brave.Response, | ||
| I extends Request, O extends Response> extends SimpleDecoratingService<I, O> { | ||
|
|
||
| /** | ||
| * Creates a new instance that decorates the specified {@link Service}. | ||
| */ | ||
| protected AbstractBraveService(Service<I, O> delegate) { | ||
| super(delegate); | ||
| } | ||
|
|
||
| abstract BI braveRequest(ServiceRequestContext ctx); | ||
|
|
||
| abstract BO braveResponse(ServiceRequestContext ctx, RequestLog log, BI braveReq); | ||
|
|
||
| abstract Span handleReceive(BI braveReq); | ||
|
|
||
| abstract void handleSend(BO response, Span span); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shall we move these abstract methods down so they are grouped? |
||
|
|
||
| @Override | ||
| public final O serve(ServiceRequestContext ctx, I req) throws Exception { | ||
| if (!ctx.config().transientServiceOptions().contains(TransientServiceOption.WITH_TRACING)) { | ||
| return unwrap().serve(ctx, req); | ||
| } | ||
| final BI braveReq = braveRequest(ctx); | ||
| final Span span = 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); | ||
| } | ||
| } | ||
|
|
||
| abstract Tracer tracer(); | ||
|
|
||
| abstract RequestContextCurrentTraceContext currentTraceContext(); | ||
|
|
||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| void maybeAddTagsToSpan(ServiceRequestContext ctx, BI braveReq, Span span) { | ||
| if (span.isNoop()) { | ||
| // For no-op spans, nothing special to do. | ||
| return; | ||
| } | ||
|
|
||
| ctx.log().whenComplete().thenAccept(log -> { | ||
| annotateWireSpan(log, span); | ||
| final BO braveRes = braveResponse(ctx, log, braveReq); | ||
| handleSend(braveRes, span); | ||
| }); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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: | ||
| * <pre>{@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); | ||
|
jrhee17 marked this conversation as resolved.
Outdated
|
||
| * }); | ||
| * BraveService | ||
| * .newDecorator(httpTracing) | ||
| * ... | ||
| * }</pre> | ||
| * The following tags will be available by default: | ||
| * <ul> | ||
| * <li>http.url</li> | ||
| * <li>http.host</li> | ||
|
|
@@ -41,53 +52,25 @@ | |
| * <li>address.local</li> | ||
| * </ul> | ||
| */ | ||
| final class ArmeriaHttpServerParser implements HttpRequestParser, HttpResponseParser { | ||
| public final class ArmeriaHttpServerParser { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Global comment: Could you add
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe just |
||
|
|
||
| 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() {} | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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: | ||
| * <pre>{@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 | ||
|
jrhee17 marked this conversation as resolved.
Outdated
|
||
| * ArmeriaRpcServerParser.responseParser().parse(res, ctx, span); | ||
| * }); | ||
| * BraveRpcService | ||
| * .newDecorator(rpcTracing) | ||
| * ... | ||
| * }</pre> | ||
| * The following tags will be available by default: | ||
| * <ul> | ||
| * <li>http.url</li> | ||
| * <li>http.host</li> | ||
| * <li>http.protocol</li> | ||
| * <li>http.serfmt</li> | ||
| * <li>address.remote</li> | ||
| * <li>address.local</li> | ||
| * </ul> | ||
| */ | ||
| public final class ArmeriaRpcServerParser { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I was assuming that most users will be interested in
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Just merged the class and added the dependency since it seems you prefer this way. |
||
|
|
||
| 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() {} | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| /* | ||
| * Copyright 2019 LINE Corporation | ||
| * | ||
| * LINE Corporation licenses this file to you under the Apache License, | ||
|
Comment on lines
+2
to
+4
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 2025 and LY |
||
| * 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. | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Time to update the template?