aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsavaki <matt.ho@gmail.com>2018-07-13 21:23:51 -0700
committersavaki <matt.ho@gmail.com>2018-07-21 07:44:40 -0700
commit6e885b6a0b2756676ec6443dedaeccfb4e8176dc (patch)
tree8ef18723f8f1367ad77339ff37842a191321569b
parent82e2988b912f8ec73f391f3ad5e9fe64dee2b5fe (diff)
downloadopencensus-java-6e885b6a0b2756676ec6443dedaeccfb4e8176dc.tar.gz
added initial support for spring annotations
-rw-r--r--all/build.gradle2
-rw-r--r--build.gradle8
-rw-r--r--contrib/spring/README.md94
-rw-r--r--contrib/spring/build.gradle21
-rw-r--r--contrib/spring/src/main/java/io/opencensus/contrib/spring/aop/CensusSpringAspect.java34
-rw-r--r--contrib/spring/src/main/java/io/opencensus/contrib/spring/aop/CensusSpringSQLAspect.java60
-rw-r--r--contrib/spring/src/main/java/io/opencensus/contrib/spring/aop/Handler.java35
-rw-r--r--contrib/spring/src/main/java/io/opencensus/contrib/spring/aop/Trace.java24
-rw-r--r--contrib/spring/src/test/java/io/opencensus/contrib/spring/aop/CensusSpringInterceptorTest.java131
-rw-r--r--contrib/spring/src/test/java/io/opencensus/contrib/spring/aop/Sample.java39
-rw-r--r--contrib/spring/src/test/resources/spring.xml16
-rw-r--r--gradle/wrapper/gradle-wrapper.properties3
-rw-r--r--settings.gradle2
13 files changed, 468 insertions, 1 deletions
diff --git a/all/build.gradle b/all/build.gradle
index 387efabb..f62ecab8 100644
--- a/all/build.gradle
+++ b/all/build.gradle
@@ -21,6 +21,7 @@ def subprojects = [
project(':opencensus-contrib-http-util'),
project(':opencensus-contrib-log-correlation-stackdriver'),
project(':opencensus-contrib-monitored-resource-util'),
+ project(':opencensus-contrib-spring'),
project(':opencensus-contrib-zpages'),
project(':opencensus-exporter-trace-logging'),
project(':opencensus-exporter-trace-stackdriver'),
@@ -43,6 +44,7 @@ def subprojects_javadoc = [
project(':opencensus-contrib-http-util'),
project(':opencensus-contrib-log-correlation-stackdriver'),
project(':opencensus-contrib-monitored-resource-util'),
+ project(':opencensus-contrib-spring'),
project(':opencensus-contrib-zpages'),
project(':opencensus-exporter-trace-logging'),
project(':opencensus-exporter-trace-stackdriver'),
diff --git a/build.gradle b/build.gradle
index 572bbcaf..c306f137 100644
--- a/build.gradle
+++ b/build.gradle
@@ -144,6 +144,7 @@ subprojects {
ext {
appengineVersion = '1.9.64'
+ aspectjVersion = '1.8.11'
autoValueVersion = '1.4'
findBugsVersion = '3.0.1'
errorProneVersion = '2.3.1'
@@ -153,6 +154,7 @@ subprojects {
googleCloudBetaVersion = '0.54.0-beta'
googleCloudGaVersion = '1.36.0'
signalfxVersion = '0.0.39'
+ springVersion = '4.3.12.RELEASE'
prometheusVersion = '0.4.0'
protobufVersion = '3.5.1'
zipkinReporterVersion = '2.3.2'
@@ -160,6 +162,7 @@ subprojects {
libraries = [
appengine_api: "com.google.appengine:appengine-api-1.0-sdk:${appengineVersion}",
+ aspectj: "org.aspectj:aspectjrt:${aspectjVersion}",
auto_value: "com.google.auto.value:auto-value:${autoValueVersion}",
auto_service: 'com.google.auto.service:auto-service:1.0-rc3',
byte_buddy: 'net.bytebuddy:byte-buddy:1.7.11',
@@ -179,6 +182,10 @@ subprojects {
guava: "com.google.guava:guava:${guavaVersion}",
jsr305: "com.google.code.findbugs:jsr305:${findBugsVersion}",
signalfx_java: "com.signalfx.public:signalfx-java:${signalfxVersion}",
+ spring_aspects: "org.springframework:spring-aspects:${springVersion}",
+ spring_context: "org.springframework:spring-context:${springVersion}",
+ spring_context_support: "org.springframework:spring-context-support:${springVersion}",
+ spring_test: "org.springframework:spring-test:${springVersion}",
prometheus_simpleclient: "io.prometheus:simpleclient:${prometheusVersion}",
protobuf: "com.google.protobuf:protobuf-java:${protobufVersion}",
@@ -365,6 +372,7 @@ subprojects {
'opencensus-contrib-http-util',
'opencensus-contrib-log-correlation-stackdriver',
'opencensus-contrib-monitored-resource-util',
+ 'opencensus-contrib-spring',
'opencensus-contrib-zpages',
'opencensus-exporter-stats-prometheus',
'opencensus-exporter-stats-signalfx',
diff --git a/contrib/spring/README.md b/contrib/spring/README.md
new file mode 100644
index 00000000..1394049e
--- /dev/null
+++ b/contrib/spring/README.md
@@ -0,0 +1,94 @@
+# spring
+[![Build Status][travis-image]][travis-url]
+[![Windows Build Status][appveyor-image]][appveyor-url]
+[![Maven Central][maven-image]][maven-url]
+
+Provides annotation support for projects that use Spring.
+
+## Quickstart
+
+### Add the dependencies to your project.
+
+For Maven add to your `pom.xml`:
+```xml
+<dependencies>
+ <!-- census -->
+ <dependency>
+ <groupId>io.opencensus</groupId>
+ <artifactId>opencensus-api</artifactId>
+ <version>0.15.0</version>
+ </dependency>
+ <dependency>
+ <groupId>io.opencensus</groupId>
+ <artifactId>opencensus-contrib-spring</artifactId>
+ <version>0.15.0</version>
+ </dependency>
+ <dependency>
+ <groupId>io.opencensus</groupId>
+ <artifactId>opencensus-impl</artifactId>
+ <version>0.15.0</version>
+ <scope>runtime</scope>
+ </dependency>
+
+ <!-- spring aspects -->
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-aspects</artifactId>
+ <version>SPRING_VERSION</version>
+ <scope>runtime</scope>
+ </dependency>
+
+</dependencies>
+```
+
+For Gradle add to your dependencies:
+```gradle
+compile 'io.opencensus:opencensus-api:0.15.0'
+compile 'io.opencensus:opencensus-contrib-spring:0.15.0'
+runtime 'io.opencensus:opencensus-impl:0.15.0'
+runtime 'org.springframework:spring-aspects:SPRING_VERSION'
+```
+
+### Configure Spring
+
+To configure annotation support within Spring, include the following with your
+spring xml configuration.
+
+```xml
+ <!-- Enable @AspectJ annotation support -->
+ <aop:aspectj-autoproxy/>
+
+ <!-- traces explicit calls to @Trace -->
+ <bean id="censusAspect" class="io.opencensus.contrib.spring.aop.CensusSpringAspect"/>
+
+ <!-- traces all SQL calls e.g. New Relic -->
+ <bean id="censusSQLAspect" class="io.opencensus.contrib.spring.aop.CensusSpringSQLAspect"/>
+```
+
+### Usage
+
+Once configured, you can use the `@Trace` annotation to indicate that a method should be traces.
+
+```java
+ @Trace()
+ void example1() {
+ // do work
+ }
+
+ // a custom span name can also be provided to @Trace
+ @Trace(name = "custom-label")
+ void example2() {
+ // do moar work
+ }
+```
+
+#### Notes
+
+Spring support only enables annotations. You'll still need to configure opencensus and register exporters / views.
+
+[travis-image]: https://travis-ci.org/census-instrumentation/opencensus-java.svg?branch=master
+[travis-url]: https://travis-ci.org/census-instrumentation/opencensus-java
+[appveyor-image]: https://ci.appveyor.com/api/projects/status/hxthmpkxar4jq4be/branch/master?svg=true
+[appveyor-url]: https://ci.appveyor.com/project/opencensusjavateam/opencensus-java/branch/master
+[maven-image]: https://maven-badges.herokuapp.com/maven-central/io.opencensus/opencensus-contrib-spring/badge.svg
+[maven-url]: https://maven-badges.herokuapp.com/maven-central/io.opencensus/opencensus-contrib-spring
diff --git a/contrib/spring/build.gradle b/contrib/spring/build.gradle
new file mode 100644
index 00000000..a01ccf24
--- /dev/null
+++ b/contrib/spring/build.gradle
@@ -0,0 +1,21 @@
+description = 'OpenCensus Spring'
+
+apply plugin: 'java'
+
+[compileJava, compileTestJava].each() {
+ it.sourceCompatibility = 1.8
+ it.targetCompatibility = 1.8
+}
+
+dependencies {
+ compile project(':opencensus-api'),
+ project(':opencensus-impl'),
+ project(':opencensus-testing'),
+ libraries.aspectj,
+ libraries.spring_aspects,
+ libraries.spring_context,
+ libraries.spring_context_support,
+ libraries.spring_test
+
+ signature "org.codehaus.mojo.signature:java18:+@signature"
+}
diff --git a/contrib/spring/src/main/java/io/opencensus/contrib/spring/aop/CensusSpringAspect.java b/contrib/spring/src/main/java/io/opencensus/contrib/spring/aop/CensusSpringAspect.java
new file mode 100644
index 00000000..7baaa590
--- /dev/null
+++ b/contrib/spring/src/main/java/io/opencensus/contrib/spring/aop/CensusSpringAspect.java
@@ -0,0 +1,34 @@
+package io.opencensus.contrib.spring.aop;
+
+import io.opencensus.trace.SpanBuilder;
+import io.opencensus.trace.Tracing;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.springframework.beans.factory.annotation.Configurable;
+
+import java.lang.reflect.Method;
+
+/**
+ * CensusSpringAspect handles logic for the @Trace annotation
+ */
+@Aspect
+@Configurable
+public class CensusSpringAspect {
+ @Around("@annotation(Trace)")
+ public Object trace(ProceedingJoinPoint call) throws Throwable {
+ MethodSignature signature = (MethodSignature) call.getSignature();
+ Method method = signature.getMethod();
+
+ Trace annotation = method.getAnnotation(Trace.class);
+ String spanName = annotation.name();
+ if ("".equals(spanName)) {
+ spanName = method.getName();
+ }
+
+ SpanBuilder builder = Tracing.getTracer().spanBuilder(spanName);
+
+ return Handler.proceed(call, builder);
+ }
+}
diff --git a/contrib/spring/src/main/java/io/opencensus/contrib/spring/aop/CensusSpringSQLAspect.java b/contrib/spring/src/main/java/io/opencensus/contrib/spring/aop/CensusSpringSQLAspect.java
new file mode 100644
index 00000000..d13172d7
--- /dev/null
+++ b/contrib/spring/src/main/java/io/opencensus/contrib/spring/aop/CensusSpringSQLAspect.java
@@ -0,0 +1,60 @@
+package io.opencensus.contrib.spring.aop;
+
+import io.opencensus.trace.SpanBuilder;
+import io.opencensus.trace.Tracer;
+import io.opencensus.trace.Tracing;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Pointcut;
+import org.springframework.beans.factory.annotation.Configurable;
+
+/**
+ */
+@Aspect
+@Configurable
+public class CensusSpringSQLAspect {
+ private static final Tracer tracer = Tracing.getTracer();
+
+ @Around("execute() || testing()")
+ public Object trace(ProceedingJoinPoint call) throws Throwable {
+ if (call.getArgs().length == 0 || call.getArgs()[0] == null) {
+ return call.proceed();
+ }
+
+ String sql = (String) call.getArgs()[0];
+ String spanName = makeSpanName(call, sql);
+ SpanBuilder builder = tracer.spanBuilder(spanName);
+
+ return Handler.proceed(call, builder, sql);
+ }
+
+ /**
+ * execute creates spans around all invocations of Statement.execute*. The raw SQL
+ * will be stored in an annotation associated with the Span
+ */
+ @Pointcut("execution(public !void java.sql.Statement.execute*(java.lang.String))")
+ protected void execute() {
+ }
+
+ @Pointcut("execution(public void io.opencensus.contrib.spring.aop.Sample.execute*(java.lang.String))")
+ protected void testing() {
+ }
+
+ private static String makeSpanName(ProceedingJoinPoint call, String sql) {
+ String hash = Integer.toHexString(hashCode(sql.toCharArray()));
+ return call.getSignature().getName() + "-" + hash;
+ }
+
+ private static int hashCode(char[] seq) {
+ if (seq == null) {
+ return 0;
+ }
+
+ int hash = 0;
+ for (char c : seq) {
+ hash = 31 * hash + c;
+ }
+ return hash;
+ }
+}
diff --git a/contrib/spring/src/main/java/io/opencensus/contrib/spring/aop/Handler.java b/contrib/spring/src/main/java/io/opencensus/contrib/spring/aop/Handler.java
new file mode 100644
index 00000000..2bb622f3
--- /dev/null
+++ b/contrib/spring/src/main/java/io/opencensus/contrib/spring/aop/Handler.java
@@ -0,0 +1,35 @@
+package io.opencensus.contrib.spring.aop;
+
+import io.opencensus.common.Scope;
+import io.opencensus.trace.SpanBuilder;
+import io.opencensus.trace.Status;
+import io.opencensus.trace.Tracer;
+import io.opencensus.trace.Tracing;
+import org.aspectj.lang.ProceedingJoinPoint;
+
+/**
+ * Handler defines common logic for wrapping a span around the specified JoinPoint.
+ */
+final class Handler {
+ private static final Tracer tracer = Tracing.getTracer();
+
+ private Handler() {
+ }
+
+ static Object proceed(ProceedingJoinPoint call, SpanBuilder builder, String... annotations) throws Throwable {
+ try (Scope scope = builder.startScopedSpan()) {
+
+ for (String annotation : annotations) {
+ tracer.getCurrentSpan().addAnnotation(annotation);
+ }
+
+ return call.proceed();
+
+ } catch (Throwable t) {
+ io.opencensus.trace.Span span = tracer.getCurrentSpan();
+ span.addAnnotation(t.getMessage());
+ span.setStatus(Status.UNKNOWN);
+ throw t;
+ }
+ }
+}
diff --git a/contrib/spring/src/main/java/io/opencensus/contrib/spring/aop/Trace.java b/contrib/spring/src/main/java/io/opencensus/contrib/spring/aop/Trace.java
new file mode 100644
index 00000000..e022c576
--- /dev/null
+++ b/contrib/spring/src/main/java/io/opencensus/contrib/spring/aop/Trace.java
@@ -0,0 +1,24 @@
+package io.opencensus.contrib.spring.aop;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Trace specifies the annotated method should be included in the Trace.
+ *
+ * <p>
+ * By default, the name of the method will be used for the span name. However, the
+ * span name can be explicitly set via the name interface.
+ * </p>
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Trace {
+ /**
+ * @return the optional custom span name; if not specified the method name will be
+ * used as the span name
+ */
+ String name() default "";
+}
diff --git a/contrib/spring/src/test/java/io/opencensus/contrib/spring/aop/CensusSpringInterceptorTest.java b/contrib/spring/src/test/java/io/opencensus/contrib/spring/aop/CensusSpringInterceptorTest.java
new file mode 100644
index 00000000..0513c942
--- /dev/null
+++ b/contrib/spring/src/test/java/io/opencensus/contrib/spring/aop/CensusSpringInterceptorTest.java
@@ -0,0 +1,131 @@
+package io.opencensus.contrib.spring.aop;
+
+import io.opencensus.testing.export.TestHandler;
+import io.opencensus.trace.Annotation;
+import io.opencensus.trace.Tracing;
+import io.opencensus.trace.config.TraceParams;
+import io.opencensus.trace.export.SpanData;
+import io.opencensus.trace.export.SpanExporter;
+import io.opencensus.trace.samplers.Samplers;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.support.ClassPathXmlApplicationContext;
+
+import java.util.List;
+
+import static com.google.common.truth.Truth.assertThat;
+
+/**
+ */
+@RunWith(JUnit4.class)
+public class CensusSpringInterceptorTest {
+ ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
+
+ private TestHandler handler;
+
+ @Before
+ public void setup() {
+ handler = new TestHandler();
+
+ SpanExporter exporter = Tracing.getExportComponent().getSpanExporter();
+ exporter.registerHandler("testing", handler);
+
+ TraceParams params = Tracing
+ .getTraceConfig()
+ .getActiveTraceParams()
+ .toBuilder()
+ .setSampler(Samplers.alwaysSample())
+ .build();
+ Tracing.getTraceConfig().updateActiveTraceParams(params);
+ }
+
+ @After
+ public void teardown() {
+ SpanExporter exporter = Tracing.getExportComponent().getSpanExporter();
+ exporter.unregisterHandler("testing");
+ }
+
+ @Test
+ public void testTraceUsesMethodAsSpanName() throws Exception {
+ // When
+ Sample sample = (Sample) context.getBean("sample");
+ sample.call(100);
+
+ // Then
+ List<SpanData> data = handler.waitForExport(1);
+ assertThat(data).isNotNull();
+ assertThat(data.size()).isEqualTo(1);
+ assertThat(data.get(0).getName()).isEqualTo("call");
+ }
+
+ @Test
+ public void testTraceAcceptsCustomSpanName() throws Exception {
+ // When
+ Sample sample = (Sample) context.getBean("sample");
+ sample.custom(100);
+
+ // Then
+ List<SpanData> data = handler.waitForExport(1);
+ assertThat(data).isNotNull();
+ assertThat(data.size()).isEqualTo(1);
+ assertThat(data.get(0).getName()).isEqualTo("blah");
+ }
+
+ @Test
+ public void testSQLExecute() throws Exception {
+ // When
+ String sql = "select 1";
+ Sample sample = (Sample) context.getBean("sample");
+ sample.execute(sql);
+
+ // Then
+ List<SpanData> data = handler.waitForExport(1);
+ assertThat(data).isNotNull();
+ assertThat(data.size()).isEqualTo(1);
+ assertThat(data.get(0).getName()).isEqualTo("execute-4705ea0d"); // sql-{hash of sql statement}
+
+ List<SpanData.TimedEvent<Annotation>> events = data.get(0).getAnnotations().getEvents();
+ assertThat(events.size()).isEqualTo(1);
+ assertThat(events.get(0).getEvent().getDescription()).isEqualTo(sql);
+ }
+
+ @Test
+ public void testSQLQuery() throws Exception {
+ // When
+ String sql = "select 2";
+ Sample sample = (Sample) context.getBean("sample");
+ sample.executeQuery(sql);
+
+ // Then
+ List<SpanData> data = handler.waitForExport(1);
+ assertThat(data).isNotNull();
+ assertThat(data.size()).isEqualTo(1);
+ assertThat(data.get(0).getName()).isEqualTo("executeQuery-4705ea0e"); // sql-{hash of sql statement}
+
+ List<SpanData.TimedEvent<Annotation>> events = data.get(0).getAnnotations().getEvents();
+ assertThat(events.size()).isEqualTo(1);
+ assertThat(events.get(0).getEvent().getDescription()).isEqualTo(sql);
+ }
+
+ @Test
+ public void testSQLUpdate() throws Exception {
+ // When
+ String sql = "update content set value = 1";
+ Sample sample = (Sample) context.getBean("sample");
+ sample.executeUpdate(sql);
+
+ // Then
+ List<SpanData> data = handler.waitForExport(1);
+ assertThat(data).isNotNull();
+ assertThat(data.size()).isEqualTo(1);
+ assertThat(data.get(0).getName()).isEqualTo("executeUpdate-acaeb423"); // sql-{hash of sql statement}
+
+ List<SpanData.TimedEvent<Annotation>> events = data.get(0).getAnnotations().getEvents();
+ assertThat(events.size()).isEqualTo(1);
+ assertThat(events.get(0).getEvent().getDescription()).isEqualTo(sql);
+ }
+} \ No newline at end of file
diff --git a/contrib/spring/src/test/java/io/opencensus/contrib/spring/aop/Sample.java b/contrib/spring/src/test/java/io/opencensus/contrib/spring/aop/Sample.java
new file mode 100644
index 00000000..7c1e1594
--- /dev/null
+++ b/contrib/spring/src/test/java/io/opencensus/contrib/spring/aop/Sample.java
@@ -0,0 +1,39 @@
+package io.opencensus.contrib.spring.aop;
+
+import java.sql.SQLException;
+
+/**
+ */
+public class Sample {
+ @Trace()
+ void example1() {
+ // do work
+ }
+
+ @Trace(name = "custom-label")
+ void example2() {
+ // do moar work
+ }
+
+ @Trace()
+ void call(long delay) throws Exception {
+ Thread.sleep(delay);
+ }
+
+ @Trace(name = "blah")
+ void custom(long delay) throws Exception {
+ Thread.sleep(delay);
+ }
+
+ public void execute(String sql) throws SQLException {
+ }
+
+ public void executeQuery(String sql) throws SQLException {
+ }
+
+ public void executeUpdate(String sql) throws SQLException {
+ }
+
+ public void executeLargeUpdate(String sql) throws SQLException {
+ }
+}
diff --git a/contrib/spring/src/test/resources/spring.xml b/contrib/spring/src/test/resources/spring.xml
new file mode 100644
index 00000000..4185a919
--- /dev/null
+++ b/contrib/spring/src/test/resources/spring.xml
@@ -0,0 +1,16 @@
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:aop="http://www.springframework.org/schema/aop"
+ xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">
+
+ <bean id="sample" class="io.opencensus.contrib.spring.aop.Sample"/>
+
+ <!-- Enable @AspectJ annotation support -->
+ <aop:aspectj-autoproxy/>
+
+ <!-- traces explicit calls to @Trace -->
+ <bean id="censusAspect" class="io.opencensus.contrib.spring.aop.CensusSpringAspect"/>
+
+ <!-- traces all SQL calls e.g. New Relic -->
+ <bean id="censusSQLAspect" class="io.opencensus.contrib.spring.aop.CensusSpringSQLAspect"/>
+</beans> \ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 16d28051..046d2e4f 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,6 @@
+#Wed Jul 11 18:13:40 PDT 2018
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-4.7-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.7-all.zip
diff --git a/settings.gradle b/settings.gradle
index 036df52d..509f7e78 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -57,9 +57,11 @@ project(':opencensus-metrics').projectDir = "$rootDir/metrics" as File
if (JavaVersion.current().isJava8Compatible()) {
include ":opencensus-all"
include ":opencensus-benchmarks"
+ include ":opencensus-contrib-spring"
include ":opencensus-contrib-zpages"
project(':opencensus-all').projectDir = "$rootDir/all" as File
project(':opencensus-benchmarks').projectDir = "$rootDir/benchmarks" as File
+ project(':opencensus-contrib-spring').projectDir = "$rootDir/contrib/spring" as File
project(':opencensus-contrib-zpages').projectDir = "$rootDir/contrib/zpages" as File
}