Skip to content

Commit

Permalink
Adds opencensus-shim instrumentation
Browse files Browse the repository at this point in the history
  • Loading branch information
dmarkwat committed Oct 13, 2022
1 parent 7104290 commit d09b7e7
Show file tree
Hide file tree
Showing 9 changed files with 364 additions and 0 deletions.
20 changes: 20 additions & 0 deletions instrumentation/opencensus-shim/javaagent/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
plugins {
id("otel.javaagent-instrumentation")
}

muzzle {
pass {
group.set("io.opentelemetry")
module.set("opentelemetry-opencensus-shim")
versions.set("[1.18.0-alpha,)")
}
}

dependencies {
compileOnly("io.opentelemetry:opentelemetry-opencensus-shim")
compileOnly(project(":opentelemetry-api-shaded-for-instrumenting", configuration = "shadow"))

testImplementation(project(":instrumentation:opencensus-shim:testing"))
testInstrumentation(project(":instrumentation:opencensus-shim:testing"))
testImplementation(project(":opentelemetry-api-shaded-for-instrumenting", configuration = "shadow"))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package application.io.opentelemetry.api.trace;

import application.io.opentelemetry.context.ContextKey;

public class ExternSpanContextKey {
public static final ContextKey<Span> KEY = SpanContextKey.KEY;

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

package io.opentelemetry.opencensusshim;

import application.io.opentelemetry.api.trace.Span;
import application.io.opentelemetry.context.Context;
import application.io.opentelemetry.context.ContextKey;
import application.io.opentelemetry.context.ImplicitContextKeyed;
import application.io.opentelemetry.context.Scope;
import com.google.errorprone.annotations.MustBeClosed;
import java.lang.reflect.Field;
import java.util.concurrent.Callable;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ScheduledExecutorService;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import javax.annotation.Nullable;

// I can't begin to describe how much I dislike all this :(
public final class ContextExtractor implements Context {

private static final Field otelSpanField;

static {
try {
otelSpanField = OpenTelemetrySpanImpl.class.getDeclaredField("otelSpan");
} catch (NoSuchFieldException e) {
throw new IllegalStateException(e);
}
otelSpanField.setAccessible(true);
}

private final Context ref;

public ContextExtractor(Context appCtx) {
this.ref = appCtx;
}

@Override
public <V> Context with(ContextKey<V> k1, V v1) {
if (v1 instanceof OpenTelemetrySpanImpl) {
Span otelSpan = null;
try {
otelSpan = (Span) otelSpanField.get(v1);
} catch (IllegalAccessException e) {
throw new IllegalStateException(e);
}
return ref.with(otelSpan);
}
return ref.with(k1, v1);
}

@Override
public Context with(ImplicitContextKeyed value) {
if (value instanceof OpenTelemetrySpanImpl) {
Span otelSpan = null;
try {
otelSpan = (Span) otelSpanField.get(value);
} catch (IllegalAccessException e) {
throw new IllegalStateException(e);
}
return ref.with(otelSpan);
}
return ref.with(value);
}

//
// delegates
//

@Nullable
@Override
public <V> V get(ContextKey<V> key) {
return ref.get(key);
}

@Override
@MustBeClosed
public Scope makeCurrent() {
return ref.makeCurrent();
}

@Override
public Runnable wrap(Runnable runnable) {
return ref.wrap(runnable);
}

@Override
public <T> Callable<T> wrap(Callable<T> callable) {
return ref.wrap(callable);
}

@Override
public Executor wrap(Executor executor) {
return ref.wrap(executor);
}

@Override
public ExecutorService wrap(ExecutorService executor) {
return ref.wrap(executor);
}

@Override
public ScheduledExecutorService wrap(ScheduledExecutorService executor) {
return ref.wrap(executor);
}

@Override
public <T, U> Function<T, U> wrapFunction(Function<T, U> function) {
return ref.wrapFunction(function);
}

@Override
public <T, U, V> BiFunction<T, U, V> wrapFunction(BiFunction<T, U, V> function) {
return ref.wrapFunction(function);
}

@Override
public <T> Consumer<T> wrapConsumer(Consumer<T> consumer) {
return ref.wrapConsumer(consumer);
}

@Override
public <T, U> BiConsumer<T, U> wrapConsumer(BiConsumer<T, U> consumer) {
return ref.wrapConsumer(consumer);
}

@Override
public <T> Supplier<T> wrapSupplier(Supplier<T> supplier) {
return ref.wrapSupplier(supplier);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.opencensusshim;

import static net.bytebuddy.matcher.ElementMatchers.isConstructor;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;

import application.io.opentelemetry.context.Context;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;

/**
* Doctors the {@link OpenTelemetryCtx} constructor to provide a delegating wrapper for the provided
* {@link Context} which very specifically singles out the {@link OpenTelemetrySpanImpl} shortcoming
* upon calling the {@link Context#with} methods.
*
* <p>The "with" methods then extract the internal otel span and pass it along for processing as
* normal as, sadly, the Java Agent instrumentations all <i>require</i> the agent-generated
* <i>instance</i>: interface conformance is simply not enough when the java agent is in the mix as
* it uses data stored in specialized instances of {@link
* application.io.opentelemetry.api.trace.Span} to perform its duties.
*/
public class OpenTelemetryCtxInstrumentation implements TypeInstrumentation {
@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return named("io.opentelemetry.opencensusshim.OpenTelemetryCtx");
}

@Override
public void transform(TypeTransformer transformer) {
transformer.applyAdviceToMethod(
isConstructor()
.and(takesArgument(0, named("application.io.opentelemetry.context.Context"))),
OpenTelemetryCtxInstrumentation.class.getName() + "$HandleConstruction");
}

@SuppressWarnings({"unused", "OtelPrivateConstructorForUtilityClass"})
public static class HandleConstruction {

@Advice.OnMethodEnter(suppress = Throwable.class)
public static void constructor(@Advice.Argument(value = 0, readOnly = false) Context context) {
context = new ContextExtractor(context);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.opencensusshim;

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

@AutoService(InstrumentationModule.class)
public class OpencensusShimInstrumentationModule extends InstrumentationModule {

public OpencensusShimInstrumentationModule() {
super("opencensusshim", "io.opentelemetry.opencensusshim");
}

@Override
public List<TypeInstrumentation> typeInstrumentations() {
return Arrays.asList(new OpenTelemetryCtxInstrumentation());
}

@Override
public boolean isHelperClass(String className) {
return className.equals("io.opentelemetry.opencensusshim.ContextExtractor");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.opencensusshim;

import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension;
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
import org.junit.jupiter.api.extension.RegisterExtension;

public class OpenCensusShimJavaAgentTest extends AbstractOpenCensusShimTest {

@RegisterExtension
static final InstrumentationExtension testing = AgentInstrumentationExtension.create();

@Override
protected InstrumentationExtension testing() {
return testing;
}
}
10 changes: 10 additions & 0 deletions instrumentation/opencensus-shim/testing/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
plugins {
id("otel.library-instrumentation")
id("otel.nullaway-conventions")
}

dependencies {
api("io.opentelemetry:opentelemetry-opencensus-shim")

api(project(":testing-common"))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.opencensusshim;

import io.opencensus.trace.AttributeValue;
import io.opencensus.trace.Tracing;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.context.Scope;
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
import org.junit.jupiter.api.Test;

public abstract class AbstractOpenCensusShimTest {

protected abstract InstrumentationExtension testing();

@Test
void testCrossOtelOcBoundary() {
Tracer tracer = testing().getOpenTelemetry().getTracer("unidentified");
Span span = tracer.spanBuilder("test-span").setSpanKind(SpanKind.INTERNAL).startSpan();
Scope scope = span.makeCurrent();
try {
io.opencensus.trace.Tracer ocTracer = Tracing.getTracer();
io.opencensus.trace.Span internal = ocTracer.spanBuilder("internal").startSpan();
io.opencensus.common.Scope ocScope = ocTracer.withSpan(internal);
try {
ocTracer
.getCurrentSpan()
.putAttribute("internal-only", AttributeValue.booleanAttributeValue(true));
} finally {
ocScope.close();
}
internal.end();
} finally {
scope.close();
}
span.end();

testing()
.waitAndAssertTraces(
traceAssert ->
traceAssert.hasSpansSatisfyingExactly(
spanAssert -> spanAssert.hasName("test-span").hasNoParent(),
spanAssert ->
spanAssert
.hasName("internal")
.hasParentSpanId(span.getSpanContext().getSpanId())
.hasAttributes(
Attributes.of(AttributeKey.booleanKey("internal-only"), true))));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.opencensusshim;

import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension;
import org.junit.jupiter.api.extension.RegisterExtension;

public class OpenCensusShimRegressionTest extends AbstractOpenCensusShimTest {

@RegisterExtension
static final InstrumentationExtension testing = LibraryInstrumentationExtension.create();

@Override
protected InstrumentationExtension testing() {
return testing;
}
}

0 comments on commit d09b7e7

Please sign in to comment.