Skip to content

Commit

Permalink
Add instrumentation for Armeria (server) (#756)
Browse files Browse the repository at this point in the history
* Add instrumentation for Armeria (server)

* Spotless

* Use end timestamp too and fix auto test by not using ClassRule

* Reduce number of less useful lambdas and add reference to useful lambda.

* Cleanup

* README

* Move package to v1_0

* No storage
  • Loading branch information
Anuraag Agrawal committed Jul 27, 2020
1 parent 010252c commit c3299fa
Show file tree
Hide file tree
Showing 16 changed files with 638 additions and 2 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ provide the path to a JAR file including an SPI implementation using the system
| [Akka HTTP](https://doc.akka.io/docs/akka-http/current/index.html) | 10.0+ |
| [Apache HttpAsyncClient](https://hc.apache.org/index.html) | 4.0+ |
| [Apache HttpClient](https://hc.apache.org/index.html) | 2.0+ |
| [Armeria](https://armeria.dev) | 0.99.8+ |
| [AWS SDK](https://aws.amazon.com/sdk-for-java/) | 1.11.x and 2.2.0+ |
| [Cassandra Driver](https://github.com/datastax/java-driver) | 3.0+ |
| [Couchbase Client](https://github.com/couchbase/couchbase-java-client) | 2.0+ (not including 3.x yet) |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import io.opentelemetry.auto.instrumentation.api.MoreAttributes;
import io.opentelemetry.context.Scope;
import io.opentelemetry.context.propagation.HttpTextFormat;
import io.opentelemetry.trace.EndSpanOptions;
import io.opentelemetry.trace.Span;
import io.opentelemetry.trace.SpanContext;
import io.opentelemetry.trace.Tracer;
Expand Down Expand Up @@ -58,9 +59,18 @@ public Span startSpan(REQUEST request, CONNECTION connection, Method origin) {
}

public Span startSpan(REQUEST request, CONNECTION connection, String spanName) {
return startSpan(request, connection, spanName, -1);
}

public Span startSpan(
REQUEST request, CONNECTION connection, String spanName, long startTimestamp) {
Span.Builder builder =
tracer.spanBuilder(spanName).setSpanKind(SERVER).setParent(extract(request, getGetter()));

if (startTimestamp >= 0) {
builder.setStartTimestamp(startTimestamp);
}

Span span = builder.startSpan();
onConnection(span, connection);
onRequest(span, request);
Expand All @@ -82,8 +92,17 @@ public Scope startScope(Span span, STORAGE storage) {

// TODO should end methods remove SPAN attribute from request as well?
public void end(Span span, int responseStatus) {
end(span, responseStatus, -1);
}

// TODO should end methods remove SPAN attribute from request as well?
public void end(Span span, int responseStatus, long timestamp) {
setStatus(span, responseStatus);
span.end();
if (timestamp >= 0) {
span.end(EndSpanOptions.builder().setEndTimestamp(timestamp).build());
} else {
span.end();
}
}

/** Ends given span exceptionally with default response status code 500. */
Expand All @@ -92,14 +111,18 @@ public void endExceptionally(Span span, Throwable throwable) {
}

public void endExceptionally(Span span, Throwable throwable, int responseStatus) {
endExceptionally(span, throwable, responseStatus, -1);
}

public void endExceptionally(Span span, Throwable throwable, int responseStatus, long timestamp) {
if (responseStatus == 200) {
// TODO I think this is wrong.
// We must report that response status that was actually sent to end user
// We may change span status, but not http_status attribute
responseStatus = 500;
}
onError(span, unwrapThrowable(throwable));
end(span, responseStatus);
end(span, responseStatus, timestamp);
}

public Span getServerSpan(STORAGE storage) {
Expand Down
24 changes: 24 additions & 0 deletions instrumentation/armeria-1.0/auto/armeria-1.0-auto.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
ext {
minJavaVersionForTests = JavaVersion.VERSION_1_8
}

apply from: "$rootDir/gradle/instrumentation.gradle"

// TODO(anuraaga): Move into instrumentation.gradle
archivesBaseName = projectDir.parentFile.name

muzzle {
pass {
group = "com.linecorp.armeria"
module = "armeria"
versions = "[0.99.8,)"
}
}

dependencies {
implementation project(path: ':instrumentation:armeria-1.0:library', configuration: 'shadow')

compileOnly group: 'com.linecorp.armeria', name: 'armeria', version: '0.99.8'

testImplementation project(':instrumentation:armeria-1.0:testing')
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed 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 io.opentelemetry.auto.instrumentation.armeria.v1_0;

import io.opentelemetry.auto.tooling.Instrumenter;

public abstract class AbstractArmeriaInstrumentation extends Instrumenter.Default {

private static final String INSTRUMENTATION_NAME = "armeria";

public AbstractArmeriaInstrumentation() {
super(INSTRUMENTATION_NAME);
}

@Override
public String[] helperClassNames() {
return new String[] {
packageName + ".shaded.internal.ContextUtil",
packageName + ".shaded.server.ArmeriaServerTracer",
packageName + ".shaded.server.ArmeriaServerTracer$ArmeriaGetter",
packageName + ".shaded.server.OpenTelemetryService",
packageName + ".shaded.server.OpenTelemetryService$Decorator",
// .thenAccept(log -> lambda
packageName + ".shaded.server.OpenTelemetryService$1",
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed 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 io.opentelemetry.auto.instrumentation.armeria.v1_0;

import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
import static net.bytebuddy.matcher.ElementMatchers.isStatic;
import static net.bytebuddy.matcher.ElementMatchers.named;

import com.google.auto.service.AutoService;
import com.linecorp.armeria.server.ServerBuilder;
import io.opentelemetry.auto.instrumentation.armeria.v1_0.shaded.server.OpenTelemetryService;
import io.opentelemetry.auto.tooling.Instrumenter;
import java.util.Collections;
import java.util.Map;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;

@AutoService(Instrumenter.class)
public class ArmeriaServerInstrumentation extends AbstractArmeriaInstrumentation {
@Override
public ElementMatcher<? super TypeDescription> typeMatcher() {
return named("com.linecorp.armeria.server.Server");
}

@Override
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
return Collections.singletonMap(
isMethod().and(isPublic()).and(isStatic()).and(named("builder")),
ArmeriaServerInstrumentation.class.getName() + "$AddDecoratorAdvice");
}

public static class AddDecoratorAdvice {
@Advice.OnMethodExit
public static void addDecorator(@Advice.Return ServerBuilder sb) {
sb.decorator(OpenTelemetryService.newDecorator());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed 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 io.opentelemetry.auto.instrumentation.armeria.v1_0

import com.linecorp.armeria.server.ServerBuilder
import io.opentelemetry.auto.test.AgentTestTrait
import io.opentelemetry.auto.test.SpockRunner
import io.opentelemetry.instrumentation.armeria.v1_0.AbstractArmeriaServerTest
import org.junit.runner.RunWith

@RunWith(SpockRunner)
class ArmeriaServerTest extends AbstractArmeriaServerTest implements AgentTestTrait {
@Override
void configureServer(ServerBuilder sb) {}

def childSetupSpec() {
server.before()
}

def childCleanupSpec() {
server.after()
}
}
12 changes: 12 additions & 0 deletions instrumentation/armeria-1.0/library/armeria-1.0-library.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
ext {
minJavaVersionForTests = JavaVersion.VERSION_1_8
javaSubPackage = 'armeria.v1_0'
}

apply from: "$rootDir/gradle/instrumentation-library.gradle"

dependencies {
compileOnly group: 'com.linecorp.armeria', name: 'armeria', version: '0.99.8'

testImplementation project(':instrumentation:armeria-1.0:testing')
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed 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 io.opentelemetry.instrumentation.armeria.v1_0.internal;

import com.linecorp.armeria.common.RequestContext;
import io.grpc.Context;
import io.netty.util.AttributeKey;

public final class ContextUtil {

private static final AttributeKey<Context> CONTEXT_KEY =
AttributeKey.valueOf(Context.class, "CONTEXT");

public static void attachContext(Context context, RequestContext armeriaCtx) {
armeriaCtx.setAttr(CONTEXT_KEY, context);
}

public static Context getContext(RequestContext armeriaCtx) {
return armeriaCtx.attr(CONTEXT_KEY);
}

private ContextUtil() {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed 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 io.opentelemetry.instrumentation.armeria.v1_0.server;

import com.linecorp.armeria.common.HttpRequest;
import com.linecorp.armeria.server.ServiceRequestContext;
import io.grpc.Context;
import io.opentelemetry.auto.bootstrap.instrumentation.decorator.HttpServerTracer;
import io.opentelemetry.context.propagation.HttpTextFormat.Getter;
import io.opentelemetry.trace.Tracer;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.URI;

public class ArmeriaServerTracer
extends HttpServerTracer<HttpRequest, ServiceRequestContext, Void> {

ArmeriaServerTracer() {}

ArmeriaServerTracer(Tracer tracer) {
super(tracer);
}

@Override
public Context getServerContext(Void ctx) {
return null;
}

@Override
protected String getInstrumentationName() {
return "io.opentelemetry.armeria-1.0";
}

@Override
protected String getVersion() {
return null;
}

@Override
protected Integer peerPort(ServiceRequestContext ctx) {
SocketAddress socketAddress = ctx.remoteAddress();
if (socketAddress instanceof InetSocketAddress) {
InetSocketAddress inetAddress = (InetSocketAddress) socketAddress;
return inetAddress.getPort();
}
return null;
}

@Override
protected String peerHostIP(ServiceRequestContext ctx) {
SocketAddress socketAddress = ctx.remoteAddress();
if (socketAddress instanceof InetSocketAddress) {
InetSocketAddress inetAddress = (InetSocketAddress) socketAddress;
return inetAddress.getAddress().getHostAddress();
}
return null;
}

@Override
protected Getter<HttpRequest> getGetter() {
return ArmeriaGetter.INSTANCE;
}

@Override
protected URI url(HttpRequest req) {
return req.uri();
}

@Override
protected String method(HttpRequest req) {
return req.method().name();
}

@Override
protected void attachServerContext(Context context, Void ctx) {}

private enum ArmeriaGetter implements Getter<HttpRequest> {
INSTANCE;

@Override
public String get(HttpRequest carrier, String key) {
return carrier.headers().get(key);
}
}
}
Loading

0 comments on commit c3299fa

Please sign in to comment.