Skip to content

Instantly share code, notes, and snippets.

@wreulicke
Last active March 24, 2025 02:57
Show Gist options
  • Save wreulicke/41b82cf4b1fbb5d8a0aefc9d7baf6491 to your computer and use it in GitHub Desktop.
Save wreulicke/41b82cf4b1fbb5d8a0aefc9d7baf6491 to your computer and use it in GitHub Desktop.
JUnitでotelのセットアップするやつ
// (deprecated) テストでjavaagentを入れるサンプル
configurations {
otel {}
}
tasks.named('test') {
jvmArgs += [
"-XX:+EnableDynamicAgentLoading",
"-javaagent:${configurations.otel.singleFile}",
"-Dotel.resource.attributes=service.name=SERVICE_NAME",
"-Dotel.instrumentation.enabled=true",
"-Dotel.javaagent.debug=false",
"-Dotel.traces.exporter=console",
"-Dotel.metrics.exporter=none",
"-Dotel.logs.exporter=none",
]
}
dependencies {
otel 'io.opentelemetry.javaagent:opentelemetry-javaagent:2.14.0'
package com.github.wreulicke.otel;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.context.Scope;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.common.CompletableResultCode;
import io.opentelemetry.sdk.trace.SdkTracerProvider;
import io.opentelemetry.sdk.trace.data.SpanData;
import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor;
import io.opentelemetry.sdk.trace.export.SpanExporter;
import net.logstash.logback.argument.StructuredArguments;
import org.junit.platform.engine.TestExecutionResult;
import org.junit.platform.engine.UniqueId;
import org.junit.platform.launcher.LauncherSession;
import org.junit.platform.launcher.LauncherSessionListener;
import org.junit.platform.launcher.TestExecutionListener;
import org.junit.platform.launcher.TestIdentifier;
import org.junit.platform.launcher.TestPlan;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
// 使う時は、META-INF/services/org.junit.platform.launcher.LauncherSessionListener に OtelLauncherSessinListener fqcn を書く必要がある
// @AutoService(LauncherSessionListener.class)
public class OtelLauncherSessinListener implements LauncherSessionListener {
@Nullable
private volatile OtelFixture fixture;
@Override
public void launcherSessionOpened(LauncherSession session) {
session.getLauncher().registerTestExecutionListeners(new TestExecutionListener() {
record TestScope(Span span, Scope scope) {
}
private final Map<UniqueId, TestScope> testScopes = new ConcurrentHashMap<>();
@Override
public void testPlanExecutionStarted(TestPlan testPlan) {
if (fixture == null) {
fixture = new OtelFixture();
fixture.setUp();
}
}
@Override
@SuppressWarnings("MustBeClosedChecker")
public void executionStarted(TestIdentifier testIdentifier) {
if (fixture != null) {
Span span = fixture.getTracer()
.spanBuilder(testIdentifier.getLegacyReportingName())
.startSpan();
Scope scope = span.makeCurrent();
testScopes.put(testIdentifier.getUniqueIdObject(), new TestScope(span, scope));
}
}
@Override
public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) {
TestScope testScope = testScopes.remove(testIdentifier.getUniqueIdObject());
if (testScope != null) {
testScope.span().end();
testScope.scope().close();
}
}
});
}
@Override
public void launcherSessionClosed(LauncherSession session) {
if (fixture != null) {
fixture.close();
fixture = null;
}
}
static class OtelFixture {
@Nullable
private OpenTelemetrySdk sdk;
@Nullable
private Tracer tracer;
void setUp() {
this.sdk = OpenTelemetrySdk.builder()
.setTracerProvider(
SdkTracerProvider.builder()
.addSpanProcessor(
SimpleSpanProcessor.create(new Slf4jSpanExporter())
)
.build()
)
.build();
GlobalOpenTelemetry.set(sdk);
}
Tracer getTracer() {
if (sdk == null) {
throw new IllegalStateException("setUp() not called");
}
if (tracer == null) {
tracer = sdk.getTracer(OtelLauncherSessinListener.class.getName());
}
return tracer;
}
void close() {
if (sdk != null) {
sdk.close();
GlobalOpenTelemetry.resetForTest();
}
}
}
static class Slf4jSpanExporter implements SpanExporter {
private static final Logger log = LoggerFactory.getLogger(Slf4jSpanExporter.class);
@Override
@SuppressWarnings("Slf4jPlaceholderMismatch")
public CompletableResultCode export(Collection<SpanData> collection) {
collection.forEach(spanData -> log.info("span", StructuredArguments.kv("span", spanData)));
return CompletableResultCode.ofSuccess();
}
@Override
public CompletableResultCode flush() {
return CompletableResultCode.ofSuccess();
}
@Override
public CompletableResultCode shutdown() {
return CompletableResultCode.ofSuccess();
}
}
}
package com.github.wreulicke.otel;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.common.CompletableResultCode;
import io.opentelemetry.sdk.trace.SdkTracerProvider;
import io.opentelemetry.sdk.trace.data.SpanData;
import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor;
import io.opentelemetry.sdk.trace.export.SpanExporter;
import net.logstash.logback.argument.StructuredArguments;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collection;
// @ExtendWith(TestSpanProvider.class)でExtension設定しないといけないので面倒なので
// こっちはCloseableResourceを使ったサンプルとして置いておく。
public class TestSpanProvider implements BeforeAllCallback, BeforeEachCallback, AfterEachCallback {
static class OtelSdkResource implements ExtensionContext.Store.CloseableResource {
public final OpenTelemetrySdk sdk;
public OtelSdkResource(OpenTelemetrySdk sdk) {
this.sdk = sdk;
}
public void init() {
GlobalOpenTelemetry.set(sdk);
}
@Override
public void close() throws Throwable {
sdk.close();
GlobalOpenTelemetry.resetForTest();
}
}
static class Slf4jSpanExporter implements SpanExporter {
private static final Logger log = LoggerFactory.getLogger(Slf4jSpanExporter.class);
@Override
@SuppressWarnings("Slf4jPlaceholderMismatch")
public CompletableResultCode export(Collection<SpanData> collection) {
collection.forEach(spanData -> log.info("span", StructuredArguments.kv("span", spanData)));
return CompletableResultCode.ofSuccess();
}
@Override
public CompletableResultCode flush() {
return CompletableResultCode.ofSuccess();
}
@Override
public CompletableResultCode shutdown() {
return CompletableResultCode.ofSuccess();
}
}
@Override
public void beforeAll(ExtensionContext context) {
context.getStore(ExtensionContext.Namespace.GLOBAL)
.getOrComputeIfAbsent(TestSpanProvider.class.getName(), s -> {
OpenTelemetrySdk sdk = OpenTelemetrySdk.builder()
.setTracerProvider(
SdkTracerProvider.builder()
.addSpanProcessor(
SimpleSpanProcessor.create(new Slf4jSpanExporter())
)
.build()
)
.build();
OtelSdkResource resource = new OtelSdkResource(sdk);
resource.init();
return resource;
}, OtelSdkResource.class);
}
@Override
public void beforeEach(ExtensionContext context) {
OtelSdkResource resource = context.getStore(ExtensionContext.Namespace.GLOBAL)
.get(TestSpanProvider.class.getName(), OtelSdkResource.class);
Tracer tracer = resource.sdk.getTracer(TestSpanProvider.class.getName());
Span span = tracer.spanBuilder(context.getDisplayName()).startSpan();
context.getStore(ExtensionContext.Namespace.create(context.getUniqueId()))
.put(Span.class, span);
}
@Override
public void afterEach(ExtensionContext context) throws Exception {
Span span = context.getStore(ExtensionContext.Namespace.create(context.getUniqueId()))
.get(Span.class, Span.class);
span.end();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment