Skip to content
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

Add otel4s instrumentation #13538

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
plugins {
id("otel.javaagent-bootstrap")
}
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.javaagent.bootstrap.otel4s;

import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import java.util.concurrent.atomic.AtomicReference;
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<>();

public static void setFiberThreadLocal(ThreadLocal<Context> fiberThreadLocal) {
if (fiberContextThreadLocal.get() == null) {
fiberContextThreadLocal.set(fiberThreadLocal);
} else {
logger.warning("The fiberContextThreadLocal is already configured");
}
}

@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 () -> local.set(beforeAttach);
}
}
}

@Nullable
private static ThreadLocal<Context> getFiberThreadLocal() {
return fiberContextThreadLocal.get();
}

private FiberLocalContextHelper() {}
}
48 changes: 48 additions & 0 deletions instrumentation/otel4s/otel4s-0.12/javaagent/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
plugins {
id("otel.javaagent-instrumentation")
id("otel.nullaway-conventions")
id("otel.scala-conventions")
}

val otel4sVersion = "0.12-c8138cf-20250312T184045Z-SNAPSHOT"
val scalaVersion = "2.13"

muzzle {
pass {
group.set("org.typelevel")
module.set("otel4s-oteljava-context-storage_2.13")
extraDependency("io.opentelemetry:opentelemetry-api:1.0.0")
versions.set("[$otel4sVersion,)")
assertInverse.set(true)
excludeInstrumentationName("opentelemetry-api")
}
pass {
group.set("org.typelevel")
module.set("otel4s-oteljava-context-storage_3")
versions.set("[$otel4sVersion,)")
extraDependency("io.opentelemetry:opentelemetry-api:1.0.0")
assertInverse.set(true)
excludeInstrumentationName("opentelemetry-api")
}
}

dependencies {
bootstrap(project(":instrumentation:otel4s:otel4s-0.12: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"))
implementation(project(":instrumentation:opentelemetry-api:opentelemetry-api-1.0:javaagent"))

// otel4s
testImplementation("org.typelevel:otel4s-oteljava-context-storage_$scalaVersion:$otel4sVersion")

latestDepTestLibrary("org.typelevel:otel4s-oteljava-context-storage_$scalaVersion:+")
}

tasks {
withType<Test>().configureEach {
jvmArgs("-Dio.opentelemetry.javaagent.shaded.io.opentelemetry.context.enableStrictContext=false")
jvmArgs("-Dcats.effect.trackFiberContext=true")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.otel4s.v0_12;

import io.opentelemetry.context.Context;
import io.opentelemetry.context.ContextStorage;
import io.opentelemetry.context.Scope;
import io.opentelemetry.javaagent.bootstrap.otel4s.FiberLocalContextHelper;
import io.opentelemetry.javaagent.instrumentation.opentelemetryapi.context.AgentContextStorage;
import javax.annotation.Nullable;

public class Otel4sFiberContextBridge implements ContextStorage {

private final ContextStorage agentContextStorage;

public Otel4sFiberContextBridge(ContextStorage delegate) {
this.agentContextStorage = delegate;
}

@Override
public Scope attach(Context context) {
Scope fiberScope = FiberLocalContextHelper.attach(context);
Scope agentScope = agentContextStorage.attach(context);
return () -> {
fiberScope.close();
agentScope.close();
};
}

@Nullable
@Override
public Context current() {
Context agentContext = agentContextStorage.current();
Context fiberContext = FiberLocalContextHelper.current();

if (agentContext == null && fiberContext != null) {
return fiberContext;
}

// if (agentContext != null) {
// logger.severe("Got conflicting context. Agent: " + agentContext + ". Fiber: " +
// fiberContext);
// }

return agentContext;
}

public static ThreadLocal<Context> contextThreadLocal(
ThreadLocal<application.io.opentelemetry.context.Context> fiberThreadLocal) {
return new ThreadLocal<Context>() {
@Override
public Context get() {
return AgentContextStorage.getAgentContext(fiberThreadLocal.get());
}

@Override
public void set(Context value) {
fiberThreadLocal.set(AgentContextStorage.toApplicationContext(value));
}
};
}
}
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.javaagent.instrumentation.otel4s.v0_12;

import com.google.auto.service.AutoService;
import io.opentelemetry.context.ContextStorage;
import io.opentelemetry.javaagent.extension.AgentListener;
import io.opentelemetry.javaagent.tooling.BeforeAgentListener;
import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;
import java.util.logging.Logger;

/**
* An {@link AgentListener} that enables oshi metrics during agent startup if oshi is present on the
* system classpath.
*/
@AutoService(BeforeAgentListener.class)
public class Otel4sFiberContextBridgeInstaller implements BeforeAgentListener {

private static final Logger logger =
Logger.getLogger(Otel4sFiberContextBridgeInstaller.class.getName());

@Override
public void beforeAgent(AutoConfiguredOpenTelemetrySdk autoConfiguredOpenTelemetrySdk) {
ContextStorage.addWrapper(Otel4sFiberContextBridge::new);
logger.info("Installed FiberContextBridge");
}
}
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.javaagent.instrumentation.otel4s.v0_12;

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 io.opentelemetry.javaagent.bootstrap.otel4s.FiberLocalContextHelper;
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;

@SuppressWarnings("IdentifierName")
public class Otel4sIOLocalContextStorageInstrumentation implements TypeInstrumentation {

@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return named("org.typelevel.otel4s.oteljava.context.IOLocalContextStorage$");
}

@Override
@SuppressWarnings({"CatchingUnchecked", "CatchAndPrintStackTrace"})
public void transform(TypeTransformer transformer) {
transformer.applyAdviceToMethod(
isPublic()
.and(isMethod())
.and(named("registerFiberThreadContext"))
.and(takesArgument(0, ThreadLocal.class)),
this.getClass().getName() + "$RegisterAdvice");
}

@SuppressWarnings("unused")
public static final class RegisterAdvice {

private RegisterAdvice() {}

@Advice.OnMethodEnter(suppress = Throwable.class)
public static void onEnter(
@Advice.Argument(0)
ThreadLocal<application.io.opentelemetry.context.Context> _fiberThreadLocal) {
FiberLocalContextHelper.setFiberThreadLocal(
Otel4sFiberContextBridge.contextThreadLocal(_fiberThreadLocal));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.otel4s.v0_12;

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 java.util.Collections;
import java.util.List;

@AutoService(InstrumentationModule.class)
public class Otel4sInstrumentationModule extends InstrumentationModule
implements ExperimentalInstrumentationModule {

public Otel4sInstrumentationModule() {
super("otel4s", "otel4s-0.12");
}

@Override
public List<TypeInstrumentation> typeInstrumentations() {
return Collections.singletonList(new Otel4sIOLocalContextStorageInstrumentation());
}

// ensure it's the last one
@Override
public int order() {
return Integer.MAX_VALUE;
}

@Override
public String getModuleGroup() {
// This module uses the api context bridge helpers, therefore must be in the same classloader
return "opentelemetry-api-bridge";
}
}
Loading
Loading