-
Notifications
You must be signed in to change notification settings - Fork 939
Add cats-effect instrumentation #13576
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
Open
iRevive
wants to merge
7
commits into
open-telemetry:main
Choose a base branch
from
iRevive:feature/cats-effect-instrumentation
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 1 commit
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
65e7833
Add cats-effect instrumentation
iRevive 6c238a3
Fix muzzle rules
iRevive 9208afd
Instrument IOFiber constructor instead of IO#unsafeRunFiber
iRevive b45ecc5
Address feedback
iRevive be975e0
Disable thread propagation debugger in tests
iRevive 6816d6a
Remove redundant JVM flags
iRevive 8251c17
Load instrumentation in the otel-api-bridge group
iRevive File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
3 changes: 3 additions & 0 deletions
3
instrumentation/cats-effect/cats-effect-3.6/bootstrap/build.gradle.kts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
plugins { | ||
id("otel.javaagent-bootstrap") | ||
} |
95 changes: 95 additions & 0 deletions
95
...in/java/io/opentelemetry/javaagent/bootstrap/catseffect/v3_6/FiberLocalContextHelper.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
/* | ||
* Copyright The OpenTelemetry Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package io.opentelemetry.javaagent.bootstrap.catseffect.v3_6; | ||
|
||
import io.opentelemetry.context.Context; | ||
import io.opentelemetry.context.Scope; | ||
import java.util.concurrent.atomic.AtomicReference; | ||
import java.util.function.Supplier; | ||
import java.util.logging.Level; | ||
import java.util.logging.Logger; | ||
import javax.annotation.Nullable; | ||
|
||
/** The helper stores a reference to the IOLocal#unsafeThreadLocal. */ | ||
public final class FiberLocalContextHelper { | ||
|
||
private static final Logger logger = Logger.getLogger(FiberLocalContextHelper.class.getName()); | ||
|
||
private static final AtomicReference<ThreadLocal<Context>> fiberContextThreadLocal = | ||
new AtomicReference<>(); | ||
|
||
private static final AtomicReference<Supplier<Boolean>> isUnderFiberContextSupplier = | ||
new AtomicReference<>(() -> false); | ||
|
||
public static void initialize( | ||
ThreadLocal<Context> fiberThreadLocal, Supplier<Boolean> isUnderFiberContext) { | ||
if (fiberContextThreadLocal.get() == null) { | ||
fiberContextThreadLocal.set(fiberThreadLocal); | ||
isUnderFiberContextSupplier.set(isUnderFiberContext); | ||
logger.fine("The fiberThreadLocalContext is configured"); | ||
} else { | ||
if (!fiberContextThreadLocal.get().equals(fiberThreadLocal)) { | ||
logger.warning( | ||
"The fiberThreadLocalContext is already configured. Ignoring subsequent calls."); | ||
} | ||
} | ||
} | ||
|
||
public static Boolean isUnderFiberContext() { | ||
return isUnderFiberContextSupplier.get().get(); | ||
} | ||
|
||
@Nullable | ||
public static Context current() { | ||
ThreadLocal<Context> local = getFiberThreadLocal(); | ||
return local != null ? local.get() : null; | ||
} | ||
|
||
public static Scope attach(Context toAttach) { | ||
ThreadLocal<Context> local = fiberContextThreadLocal.get(); | ||
if (toAttach == null || local == null) { | ||
return Scope.noop(); | ||
} else { | ||
Context beforeAttach = current(); | ||
if (toAttach == beforeAttach) { | ||
return Scope.noop(); | ||
} else { | ||
local.set(toAttach); | ||
return new ScopeImpl(beforeAttach, toAttach); | ||
} | ||
} | ||
} | ||
|
||
@Nullable | ||
private static ThreadLocal<Context> getFiberThreadLocal() { | ||
return fiberContextThreadLocal.get(); | ||
} | ||
|
||
private static class ScopeImpl implements Scope { | ||
@Nullable private final Context beforeAttach; | ||
private final Context toAttach; | ||
private boolean closed; | ||
|
||
private ScopeImpl(@Nullable Context beforeAttach, Context toAttach) { | ||
this.beforeAttach = beforeAttach; | ||
this.toAttach = toAttach; | ||
} | ||
|
||
@Override | ||
public void close() { | ||
if (!this.closed && FiberLocalContextHelper.current() == this.toAttach) { | ||
this.closed = true; | ||
FiberLocalContextHelper.fiberContextThreadLocal.get().set(this.beforeAttach); | ||
} else { | ||
FiberLocalContextHelper.logger.log( | ||
Level.FINE, | ||
"Trying to close scope which does not represent current context. Ignoring the call."); | ||
} | ||
} | ||
} | ||
|
||
private FiberLocalContextHelper() {} | ||
} |
54 changes: 54 additions & 0 deletions
54
instrumentation/cats-effect/cats-effect-3.6/javaagent/build.gradle.kts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
plugins { | ||
id("otel.javaagent-instrumentation") | ||
id("otel.nullaway-conventions") | ||
id("otel.scala-conventions") | ||
} | ||
|
||
val scalaVersion = "2.13" | ||
val catsEffectVersion = "3.6.0" | ||
|
||
muzzle { | ||
pass { | ||
group.set("org.typelevel") | ||
module.set("cats-effect_2.12") | ||
versions.set("[$catsEffectVersion,)") | ||
assertInverse.set(true) | ||
} | ||
pass { | ||
group.set("org.typelevel") | ||
module.set("cats-effect_2.13") | ||
versions.set("[$catsEffectVersion,)") | ||
assertInverse.set(true) | ||
} | ||
pass { | ||
group.set("org.typelevel") | ||
module.set("cats-effect_3") | ||
versions.set("[$catsEffectVersion,)") | ||
assertInverse.set(true) | ||
} | ||
} | ||
|
||
dependencies { | ||
bootstrap(project(":instrumentation:cats-effect:cats-effect-3.6:bootstrap")) | ||
|
||
// we need access to the "application.io.opentelemetry.context.Context" | ||
// to properly bridge fiber and agent context storages | ||
compileOnly(project(":opentelemetry-api-shaded-for-instrumenting", configuration = "shadow")) | ||
compileOnly("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") | ||
implementation(project(":instrumentation:opentelemetry-api:opentelemetry-api-1.0:javaagent")) | ||
|
||
implementation(project(":instrumentation:cats-effect:cats-effect-common-3.6:javaagent")) | ||
|
||
compileOnly("org.typelevel:cats-effect_$scalaVersion:$catsEffectVersion") | ||
|
||
testImplementation("org.typelevel:cats-effect_$scalaVersion:$catsEffectVersion") | ||
|
||
latestDepTestLibrary("org.typelevel:cats-effect_$scalaVersion:latest.release") | ||
} | ||
|
||
tasks { | ||
withType<Test>().configureEach { | ||
jvmArgs("-Dio.opentelemetry.javaagent.shaded.io.opentelemetry.context.enableStrictContext=false") | ||
jvmArgs("-Dcats.effect.trackFiberContext=true") | ||
} | ||
} |
50 changes: 50 additions & 0 deletions
50
...ntelemetry/javaagent/instrumentation/catseffect/v3_6/CatsEffectInstrumentationModule.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
/* | ||
* Copyright The OpenTelemetry Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package io.opentelemetry.javaagent.instrumentation.catseffect.v3_6; | ||
|
||
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; | ||
|
||
import com.google.auto.service.AutoService; | ||
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; | ||
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; | ||
import io.opentelemetry.javaagent.extension.instrumentation.internal.ExperimentalInstrumentationModule; | ||
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; | ||
import java.util.Arrays; | ||
import java.util.List; | ||
import net.bytebuddy.matcher.ElementMatcher; | ||
|
||
@AutoService(InstrumentationModule.class) | ||
public class CatsEffectInstrumentationModule extends InstrumentationModule | ||
implements ExperimentalInstrumentationModule { | ||
|
||
public CatsEffectInstrumentationModule() { | ||
super("cats-effect", "cats-effect-3.6"); | ||
} | ||
|
||
@Override | ||
public List<TypeInstrumentation> typeInstrumentations() { | ||
return Arrays.asList(new IoRuntimeInstrumentation(), new IoInstrumentation()); | ||
} | ||
|
||
@Override | ||
public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() { | ||
return hasClassesNamed("cats.effect.IO") | ||
// missing before 3.6.0 | ||
.and(hasClassesNamed("cats.effect.unsafe.metrics.IORuntimeMetrics")); | ||
} | ||
|
||
@Override | ||
public boolean defaultEnabled(ConfigProperties config) { | ||
return super.defaultEnabled(config) | ||
&& config.getBoolean("cats.effect.trackFiberContext", false); | ||
} | ||
|
||
// ensure it's the last one | ||
@Override | ||
public int order() { | ||
return Integer.MAX_VALUE; | ||
} | ||
} |
40 changes: 40 additions & 0 deletions
40
...n/java/io/opentelemetry/javaagent/instrumentation/catseffect/v3_6/FiberContextBridge.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.catseffect.v3_6; | ||
|
||
import io.opentelemetry.context.Context; | ||
import io.opentelemetry.context.ContextStorage; | ||
import io.opentelemetry.context.Scope; | ||
import io.opentelemetry.javaagent.bootstrap.catseffect.v3_6.FiberLocalContextHelper; | ||
import javax.annotation.Nullable; | ||
|
||
public class FiberContextBridge implements ContextStorage { | ||
|
||
private final ContextStorage agentContextStorage; | ||
|
||
public FiberContextBridge(ContextStorage delegate) { | ||
this.agentContextStorage = delegate; | ||
} | ||
|
||
@Override | ||
public Scope attach(Context toAttach) { | ||
if (FiberLocalContextHelper.isUnderFiberContext()) { | ||
return FiberLocalContextHelper.attach(toAttach); | ||
} else { | ||
return agentContextStorage.attach(toAttach); | ||
} | ||
} | ||
|
||
@Nullable | ||
@Override | ||
public Context current() { | ||
if (FiberLocalContextHelper.isUnderFiberContext()) { | ||
return FiberLocalContextHelper.current(); | ||
} else { | ||
return agentContextStorage.current(); | ||
} | ||
} | ||
} |
29 changes: 29 additions & 0 deletions
29
.../opentelemetry/javaagent/instrumentation/catseffect/v3_6/FiberContextBridgeInstaller.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
/* | ||
* Copyright The OpenTelemetry Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package io.opentelemetry.javaagent.instrumentation.catseffect.v3_6; | ||
|
||
import com.google.auto.service.AutoService; | ||
import io.opentelemetry.context.ContextStorage; | ||
import io.opentelemetry.javaagent.tooling.BeforeAgentListener; | ||
import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk; | ||
import java.util.logging.Logger; | ||
|
||
/** | ||
* A {@link BeforeAgentListener} that installs {@link FiberContextBridge} if `cats.effect.IO` is | ||
* present in the classpath. | ||
*/ | ||
@AutoService(BeforeAgentListener.class) | ||
public class FiberContextBridgeInstaller implements BeforeAgentListener { | ||
|
||
private static final Logger logger = | ||
Logger.getLogger(FiberContextBridgeInstaller.class.getName()); | ||
|
||
@Override | ||
public void beforeAgent(AutoConfiguredOpenTelemetrySdk autoConfiguredOpenTelemetrySdk) { | ||
ContextStorage.addWrapper(FiberContextBridge::new); | ||
logger.fine("Installed Cats Effect FiberContextBridge"); | ||
} | ||
} |
42 changes: 42 additions & 0 deletions
42
...in/java/io/opentelemetry/javaagent/instrumentation/catseffect/v3_6/IoInstrumentation.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
/* | ||
* Copyright The OpenTelemetry Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package io.opentelemetry.javaagent.instrumentation.catseffect.v3_6; | ||
|
||
import static net.bytebuddy.matcher.ElementMatchers.isMethod; | ||
import static net.bytebuddy.matcher.ElementMatchers.named; | ||
|
||
import application.io.opentelemetry.context.Context; | ||
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; | ||
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; | ||
import io.opentelemetry.javaagent.instrumentation.catseffect.common.v3_6.IoLocalContextSingleton; | ||
import net.bytebuddy.asm.Advice; | ||
import net.bytebuddy.description.type.TypeDescription; | ||
import net.bytebuddy.matcher.ElementMatcher; | ||
|
||
public class IoInstrumentation implements TypeInstrumentation { | ||
|
||
@Override | ||
public ElementMatcher<TypeDescription> typeMatcher() { | ||
return named("cats.effect.IO"); | ||
} | ||
|
||
@Override | ||
public void transform(TypeTransformer transformer) { | ||
transformer.applyAdviceToMethod( | ||
isMethod().and(named("unsafeRunFiber")), | ||
this.getClass().getName() + "$UnsafeRunFiberAdvice"); | ||
} | ||
|
||
@SuppressWarnings("unused") | ||
public static final class UnsafeRunFiberAdvice { | ||
private UnsafeRunFiberAdvice() {} | ||
|
||
@Advice.OnMethodEnter(suppress = Throwable.class) | ||
public static void onEnter(@Advice.This(readOnly = false) cats.effect.IO<?> io) { | ||
io = IoLocalContextSingleton.ioLocal.asLocal().scope(io, Context.current()); | ||
} | ||
} | ||
} |
43 changes: 43 additions & 0 deletions
43
.../io/opentelemetry/javaagent/instrumentation/catseffect/v3_6/IoRuntimeInstrumentation.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
/* | ||
* Copyright The OpenTelemetry Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package io.opentelemetry.javaagent.instrumentation.catseffect.v3_6; | ||
|
||
import static net.bytebuddy.matcher.ElementMatchers.isConstructor; | ||
import static net.bytebuddy.matcher.ElementMatchers.named; | ||
|
||
import cats.effect.unsafe.IORuntime; | ||
import io.opentelemetry.javaagent.bootstrap.catseffect.v3_6.FiberLocalContextHelper; | ||
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; | ||
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; | ||
import io.opentelemetry.javaagent.instrumentation.catseffect.common.v3_6.IoLocalContextSingleton; | ||
import net.bytebuddy.asm.Advice; | ||
import net.bytebuddy.description.type.TypeDescription; | ||
import net.bytebuddy.matcher.ElementMatcher; | ||
|
||
public class IoRuntimeInstrumentation implements TypeInstrumentation { | ||
|
||
@Override | ||
public ElementMatcher<TypeDescription> typeMatcher() { | ||
return named("cats.effect.unsafe.IORuntime"); | ||
} | ||
|
||
@Override | ||
public void transform(TypeTransformer transformer) { | ||
transformer.applyAdviceToMethod( | ||
isConstructor(), this.getClass().getName() + "$ConstructorAdvice"); | ||
} | ||
|
||
@SuppressWarnings("unused") | ||
public static final class ConstructorAdvice { | ||
private ConstructorAdvice() {} | ||
|
||
@Advice.OnMethodEnter(suppress = Throwable.class) | ||
public static void onEnter() { | ||
FiberLocalContextHelper.initialize( | ||
IoLocalContextSingleton.contextThreadLocal, IORuntime::isUnderFiberContext); | ||
} | ||
} | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if you have deployed 2 wars on tomcat that use this library, won't this break? Messing with the context storage is unusual, my hunch is that this is not a good idea. Typically such instrumentations restore the otel context when fiber starts running on a thread and save the context when it stops using the thread.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a valid concern indeed.
If I understand correctly, each deployment (app) will have its own classloader, but the bootstrap will still be shared.
If that's the case, my implementation won't work, I'm afraid.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suppose I don't find a proper way to make the instrumentation work. Can I distribute the current implementation as a third-party extension? Can the extension have access to the bootstrap loader?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not directly, but you could try using byte-buddy to define the class you need in boot loader or you could experiment with
Instrumentation.appendToBootstrapClassLoaderSearch
.