/* * Copyright 2016 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: * * http://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.tracing; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import com.github.kristofa.brave.Brave; import com.github.kristofa.brave.KeyValueAnnotation; import com.github.kristofa.brave.ServerRequestAdapter; import com.github.kristofa.brave.ServerResponseAdapter; import com.github.kristofa.brave.ServerSpan; import com.github.kristofa.brave.TraceData; import com.linecorp.armeria.common.Request; import com.linecorp.armeria.common.Response; import com.linecorp.armeria.common.RpcRequest; import com.linecorp.armeria.common.logging.RequestLog; import com.linecorp.armeria.common.logging.RequestLogAvailability; import com.linecorp.armeria.server.DecoratingService; import com.linecorp.armeria.server.Service; import com.linecorp.armeria.server.ServiceRequestContext; import com.linecorp.armeria.server.SimpleDecoratingService; /** * An abstract {@link DecoratingService} that traces incoming {@link Request}s. * * <p>This class depends on <a href="https://github.com/openzipkin/brave">Brave</a>, a distributed tracing * library. * * @param <I> the {@link Request} type * @param <O> the {@link Response} type */ public abstract class AbstractTracingService<I extends Request, O extends Response> extends SimpleDecoratingService<I, O> { private final ServerTracingInterceptor serverInterceptor; /** * Creates a new instance. */ protected AbstractTracingService(Service<? super I, ? extends O> delegate, Brave brave) { super(delegate); serverInterceptor = new ServerTracingInterceptor(brave); } @Override public O serve(ServiceRequestContext ctx, I req) throws Exception { final TraceData traceData = getTraceData(ctx, req); final String method = req instanceof RpcRequest ? ((RpcRequest) req).method() : ctx.method(); final ServerRequestAdapter requestAdapter = new InternalServerRequestAdapter(method, traceData); final ServerSpan serverSpan = serverInterceptor.openSpan(requestAdapter); try { if (serverSpan != null) { ctx.onEnter(unused -> serverInterceptor.setSpan(serverSpan)); ctx.onExit(unused -> serverInterceptor.clearSpan()); if (serverSpan.getSample()) { ctx.log().addListener(log -> closeSpan(ctx, serverSpan, log), RequestLogAvailability.COMPLETE); } } return delegate().serve(ctx, req); } finally { serverInterceptor.clearSpan(); } } /** * Gets a {@link TraceData} from the specified {@link Request} or {@link ServiceRequestContext}. * * @return the {@link TraceData}. */ protected abstract TraceData getTraceData(ServiceRequestContext ctx, I req); /** * Returns the server-side annotations that should be added to a Zipkin span. */ protected List<KeyValueAnnotation> annotations(ServiceRequestContext ctx, RequestLog log) { final List<KeyValueAnnotation> annotations = new ArrayList<>(5); final StringBuilder uriBuilder = new StringBuilder(); uriBuilder.append(log.scheme().uriText()); uriBuilder.append("://"); uriBuilder.append(log.host()); uriBuilder.append(ctx.path()); if (log.method() != null) { uriBuilder.append('#'); uriBuilder.append(log.method()); } annotations.add(KeyValueAnnotation.create("server.uri", uriBuilder.toString())); if (ctx.remoteAddress() != null) { annotations.add(KeyValueAnnotation.create("server.remote", ctx.remoteAddress().toString())); } if (ctx.localAddress() != null) { annotations.add(KeyValueAnnotation.create("server.local", ctx.localAddress().toString())); } final Throwable cause = log.responseCause(); final String resultText = cause == null ? "success" : "failure"; annotations.add(KeyValueAnnotation.create("server.result", resultText)); if (cause != null) { annotations.add(KeyValueAnnotation.create("server.cause", cause.toString())); } return annotations; } private void closeSpan(ServiceRequestContext ctx, ServerSpan serverSpan, RequestLog log) { final Object requestContent = log.requestContent(); if (requestContent instanceof RpcRequest) { serverSpan.getSpan().setName(((RpcRequest) requestContent).method()); } serverInterceptor.closeSpan(serverSpan, createResponseAdapter(ctx, log)); } /** * Creates a new {@link ServerResponseAdapter} from the specified request-response information. */ protected ServerResponseAdapter createResponseAdapter(ServiceRequestContext ctx, RequestLog log) { final List<KeyValueAnnotation> annotations = annotations(ctx, log); return () -> annotations; } /** * A {@link ServerRequestAdapter} holding span name and {@link TraceData} that will be passed to brave. */ private static class InternalServerRequestAdapter implements ServerRequestAdapter { private final String spanName; private final TraceData traceData; InternalServerRequestAdapter(String spanName, TraceData traceData) { this.spanName = spanName; this.traceData = traceData; } @Override public TraceData getTraceData() { return traceData; } @Override public String getSpanName() { return spanName; } @Override public Collection<KeyValueAnnotation> requestAnnotations() { return Collections.emptyList(); } } }