Skip to content
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

Fix armeria server instrumentation for http2 #9723

Merged
merged 2 commits into from
Oct 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions instrumentation/armeria-1.3/javaagent/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ dependencies {
testInstrumentation(project(":instrumentation:netty:netty-4.1:javaagent"))

library("com.linecorp.armeria:armeria:1.3.0")
testLibrary("com.linecorp.armeria:armeria-junit5:1.3.0")

testImplementation(project(":instrumentation:armeria-1.3:testing"))
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.armeria.v1_3;

import com.linecorp.armeria.common.ResponseHeadersBuilder;
import io.opentelemetry.javaagent.bootstrap.http.HttpServerResponseMutator;

enum ArmeriaHttpResponseMutator implements HttpServerResponseMutator<ResponseHeadersBuilder> {
INSTANCE;

@Override
public void appendHeader(ResponseHeadersBuilder response, String name, String value) {
response.add(name, value);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,11 @@ public final class ArmeriaSingletons {
.build();

CLIENT_DECORATOR = telemetry.newClientDecorator();
SERVER_DECORATOR = service -> new ServerDecorator(service);
Function<? super HttpService, ? extends HttpService> libraryDecorator =
telemetry
.newServiceDecorator()
.compose(service -> new ResponseCustomizingDecorator(service));
SERVER_DECORATOR = service -> new ServerDecorator(service, libraryDecorator.apply(service));
}

private ArmeriaSingletons() {}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.armeria.v1_3;

import com.linecorp.armeria.common.FilteredHttpResponse;
import com.linecorp.armeria.common.HttpObject;
import com.linecorp.armeria.common.HttpRequest;
import com.linecorp.armeria.common.HttpResponse;
import com.linecorp.armeria.common.ResponseHeaders;
import com.linecorp.armeria.common.ResponseHeadersBuilder;
import com.linecorp.armeria.server.HttpService;
import com.linecorp.armeria.server.ServiceRequestContext;
import com.linecorp.armeria.server.SimpleDecoratingHttpService;
import io.opentelemetry.context.Context;
import io.opentelemetry.javaagent.bootstrap.http.HttpServerResponseCustomizerHolder;

class ResponseCustomizingDecorator extends SimpleDecoratingHttpService {

ResponseCustomizingDecorator(HttpService delegate) {
super(delegate);
}

@Override
public HttpResponse serve(ServiceRequestContext ctx, HttpRequest req) throws Exception {
HttpResponse response = unwrap().serve(ctx, req);
Context context = Context.current();
return new FilteredHttpResponse(response) {
@Override
public HttpObject filter(HttpObject obj) {
// Ignore other objects like HttpData.
if (!(obj instanceof ResponseHeaders)) {
return obj;
}

ResponseHeaders headers = (ResponseHeaders) obj;
ResponseHeadersBuilder headersBuilder = headers.toBuilder();
HttpServerResponseCustomizerHolder.getCustomizer()
.customize(context, headersBuilder, ArmeriaHttpResponseMutator.INSTANCE);

return headersBuilder.build();
}
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,27 @@
import io.opentelemetry.api.trace.StatusCode;
import io.opentelemetry.context.Context;
import io.opentelemetry.instrumentation.api.instrumenter.ErrorCauseExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.LocalRootSpan;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerRoute;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerRouteSource;

class ServerDecorator extends SimpleDecoratingHttpService {
private final HttpService libraryDelegate;

ServerDecorator(HttpService delegate) {
ServerDecorator(HttpService delegate, HttpService libraryDelegate) {
super(delegate);
this.libraryDelegate = libraryDelegate;
}

@Override
public HttpResponse serve(ServiceRequestContext ctx, HttpRequest req) throws Exception {
// If there is no server span fall back to armeria liberary instrumentation. Server span is
// usually created by netty instrumentation, it can be missing when netty instrumentation is
// disabled or when http2 is used (netty instrumentation does not support http2).
if (!LocalRootSpan.current().getSpanContext().isValid()) {
return libraryDelegate.serve(ctx, req);
}

String matchedRoute = ctx.config().route().patternString();
if (matchedRoute == null || matchedRoute.isEmpty()) {
matchedRoute = "/";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.armeria.v1_3;

import static org.assertj.core.api.Assertions.assertThat;

import com.linecorp.armeria.client.WebClient;
import com.linecorp.armeria.common.AggregatedHttpResponse;
import com.linecorp.armeria.common.HttpResponse;
import com.linecorp.armeria.server.ServerBuilder;
import com.linecorp.armeria.testing.junit5.server.ServerExtension;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

class ArmeriaHttp2Test {
@RegisterExtension
static final AgentInstrumentationExtension testing = AgentInstrumentationExtension.create();

@RegisterExtension
static ServerExtension server1 =
new ServerExtension() {
@Override
protected void configure(ServerBuilder sb) {
sb.service("/", (ctx, req) -> HttpResponse.of("hello"));
}
};

@RegisterExtension
static ServerExtension server2 =
new ServerExtension() {
@Override
protected void configure(ServerBuilder sb) {
sb.service("/", (ctx, req) -> createWebClient(server1).execute(req));
}
};

private static WebClient createWebClient(ServerExtension server) {
return WebClient.builder(server.httpUri()).build();
}

@Test
void testHello() throws Exception {
// verify that spans are created and context is propagated
AggregatedHttpResponse result = createWebClient(server2).get("/").aggregate().get();
assertThat(result.contentAscii()).isEqualTo("hello");

testing.waitAndAssertTraces(
trace ->
trace.hasSpansSatisfyingExactly(
span -> span.hasName("GET").hasKind(SpanKind.CLIENT).hasNoParent(),
span -> span.hasName("GET /").hasKind(SpanKind.SERVER).hasParent(trace.getSpan(0)),
span -> span.hasName("GET").hasKind(SpanKind.CLIENT).hasParent(trace.getSpan(1)),
span ->
span.hasName("GET /").hasKind(SpanKind.SERVER).hasParent(trace.getSpan(2))));
}
}
Loading