Skip to content

Commit

Permalink
Add support for Grails (#2512)
Browse files Browse the repository at this point in the history
* Add support for Grails

* exclude bad version from muzzle

* Review fixes

* review fixes

* rebase

* Trigger Build
  • Loading branch information
laurit authored Mar 9, 2021
1 parent 0dde62b commit 7013376
Show file tree
Hide file tree
Showing 17 changed files with 579 additions and 66 deletions.
1 change: 1 addition & 0 deletions docs/supported-libraries.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ These are the supported libraries and frameworks:
| [Elasticsearch REST Client](https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/index.html) | 5.0+ |
| [Finatra](https://github.com/twitter/finatra) | 2.9+ |
| [Geode Client](https://geode.apache.org/) | 1.4+ |
| [Grails](https://grails.org/) | 3.0+ |
| [Google HTTP Client](https://github.com/googleapis/google-http-java-client) | 1.19+ |
| [Grizzly](https://javaee.github.io/grizzly/httpserverframework.html) | 2.0+ (disabled by default) |
| [gRPC](https://github.com/grpc/grpc-java) | 1.5+ |
Expand Down
44 changes: 44 additions & 0 deletions instrumentation/grails-3.0/javaagent/grails-3.0-javaagent.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
apply from: "$rootDir/gradle/instrumentation.gradle"

muzzle {
pass {
group = "org.grails"
module = "grails-web-url-mappings"
versions = "[3.0,)"
skipVersions += ['3.1.15']
assertInverse = true
}
}

repositories {
maven {
url "https://repo.grails.org/grails/core"
mavenContent {
releasesOnly()
}
}
}

// first version where our tests work
def grailsVersion = '3.0.6'
def springBootVersion = '1.2.5.RELEASE'

dependencies {
library ("org.grails:grails-plugin-url-mappings:$grailsVersion")

testInstrumentation project(':instrumentation:servlet:servlet-3.0:javaagent')
testInstrumentation project(':instrumentation:servlet:servlet-common:javaagent')
testInstrumentation project(':instrumentation:tomcat-7.0:javaagent')
testInstrumentation project(':instrumentation:spring:spring-webmvc-3.1:javaagent')

testImplementation "org.springframework.boot:spring-boot-autoconfigure:$springBootVersion"
testImplementation "org.springframework.boot:spring-boot-starter-tomcat:$springBootVersion"

testImplementation(project(':testing-common')) {
exclude group: 'org.eclipse.jetty', module: 'jetty-server'
}

latestDepTestLibrary ("org.grails:grails-plugin-url-mappings:4.0.+")
latestDepTestLibrary "org.springframework.boot:spring-boot-autoconfigure:2.1.17.RELEASE"
latestDepTestLibrary "org.springframework.boot:spring-boot-starter-tomcat:2.1.17.RELEASE"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.grails;

import static io.opentelemetry.javaagent.instrumentation.grails.GrailsTracer.tracer;
import static java.util.Collections.singletonMap;
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;

import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import io.opentelemetry.javaagent.tooling.TypeInstrumentation;
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;

public class DefaultGrailsControllerClassInstrumentation implements TypeInstrumentation {
@Override
public ElementMatcher<? super TypeDescription> typeMatcher() {
return named("org.grails.core.DefaultGrailsControllerClass");
}

@Override
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
return singletonMap(
isMethod()
.and(isPublic())
.and(named("invoke"))
.and(takesArgument(0, named(Object.class.getName())))
.and(takesArgument(1, named(String.class.getName())))
.and(takesArguments(2)),
DefaultGrailsControllerClassInstrumentation.class.getName() + "$ControllerAdvice");
}

public static class ControllerAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void startSpan(
@Advice.Argument(0) Object controller,
@Advice.Argument(1) String action,
@Advice.FieldValue("defaultActionName") String defaultActionName,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {

context = tracer().startSpan(controller, action != null ? action : defaultActionName);
scope = context.makeCurrent();
}

@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void stopSpan(
@Advice.Thrown Throwable throwable,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {
if (scope == null) {
return;
}
scope.close();
if (throwable == null) {
tracer().end(context);
} else {
tracer().endExceptionally(context, throwable);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.grails;

import static java.util.Arrays.asList;

import com.google.auto.service.AutoService;
import io.opentelemetry.javaagent.tooling.InstrumentationModule;
import io.opentelemetry.javaagent.tooling.TypeInstrumentation;
import java.util.List;

@AutoService(InstrumentationModule.class)
public class GrailsInstrumentationModule extends InstrumentationModule {
public GrailsInstrumentationModule() {
super("grails", "grails-3.0");
}

@Override
public List<TypeInstrumentation> typeInstrumentations() {
return asList(
new DefaultGrailsControllerClassInstrumentation(),
new UrlMappingsInfoHandlerAdapterInstrumentation());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.grails;

import io.opentelemetry.api.trace.Span;
import io.opentelemetry.context.Context;
import io.opentelemetry.instrumentation.api.servlet.ServletContextPath;
import io.opentelemetry.instrumentation.api.tracer.BaseTracer;
import org.grails.web.mapping.mvc.GrailsControllerUrlMappingInfo;

public class GrailsTracer extends BaseTracer {

private static final GrailsTracer TRACER = new GrailsTracer();

public static GrailsTracer tracer() {
return TRACER;
}

public Context startSpan(Object controller, String action) {
return startSpan(spanNameForClass(controller.getClass()) + "." + action);
}

public void nameServerSpan(
Context context, Span serverSpan, GrailsControllerUrlMappingInfo info) {
String action =
info.getActionName() != null
? info.getActionName()
: info.getControllerClass().getDefaultAction();
serverSpan.updateName(
ServletContextPath.prepend(context, "/" + info.getControllerName() + "/" + action));
}

@Override
protected String getInstrumentationName() {
return "io.opentelemetry.javaagent.grails-3.0";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.grails;

import static io.opentelemetry.javaagent.instrumentation.grails.GrailsTracer.tracer;
import static java.util.Collections.singletonMap;
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;

import io.opentelemetry.api.trace.Span;
import io.opentelemetry.context.Context;
import io.opentelemetry.instrumentation.api.tracer.ServerSpan;
import io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge;
import io.opentelemetry.javaagent.tooling.TypeInstrumentation;
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;
import org.grails.web.mapping.mvc.GrailsControllerUrlMappingInfo;

public class UrlMappingsInfoHandlerAdapterInstrumentation implements TypeInstrumentation {
@Override
public ElementMatcher<? super TypeDescription> typeMatcher() {
return named("org.grails.web.mapping.mvc.UrlMappingsInfoHandlerAdapter");
}

@Override
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
return singletonMap(
isMethod()
.and(isPublic())
.and(named("handle"))
.and(takesArgument(2, named(Object.class.getName())))
.and(takesArguments(3)),
UrlMappingsInfoHandlerAdapterInstrumentation.class.getName() + "$ServerSpanNameAdvice");
}

public static class ServerSpanNameAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void nameSpan(@Advice.Argument(2) Object handler) {

if (handler instanceof GrailsControllerUrlMappingInfo) {
Context parentContext = Java8BytecodeBridge.currentContext();
Span serverSpan = ServerSpan.fromContextOrNull(parentContext);
if (serverSpan != null) {
tracer()
.nameServerSpan(parentContext, serverSpan, (GrailsControllerUrlMappingInfo) handler);
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package test

import grails.artefact.Controller
import grails.web.Action

class ErrorController implements Controller {

@Action
def index() {
render "Error"
}
}
Loading

0 comments on commit 7013376

Please sign in to comment.