Skip to content

Commit ae62671

Browse files
committed
nest instrumentation subprojects under SDK
1 parent 995626c commit ae62671

File tree

62 files changed

+254
-3636
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+254
-3636
lines changed

braintrust-java-agent/build.gradle

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,11 @@ subprojects {
4444
}
4545
}
4646

47-
// Add muzzle generation task and muzzle check plugin to all instrumentation subprojects.
47+
// Add muzzle generation task and muzzle check plugin to smoke-test test-instrumentation.
48+
// (Instrumentation subprojects under :braintrust-sdk are configured in braintrust-sdk/build.gradle.)
4849
subprojects { subproject ->
4950
subproject.afterEvaluate {
50-
if (subproject.path.startsWith(':braintrust-java-agent:instrumentation:')
51-
|| subproject.path == ':braintrust-java-agent:smoke-test:test-instrumentation') {
51+
if (subproject.path == ':braintrust-java-agent:smoke-test:test-instrumentation') {
5252
def instrumentationApi = project(':braintrust-java-agent:instrumenter')
5353

5454
// Bootstrap classes (e.g. BraintrustBridge) are on the bootstrap classpath at runtime.

braintrust-java-agent/instrumentation/langchain_1_8_0/src/main/java/dev/braintrust/instrumentation/langchain/v1_8_0/BraintrustLangchain.java

Lines changed: 0 additions & 150 deletions
This file was deleted.

braintrust-java-agent/internal/build.gradle

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,15 +48,18 @@ dependencies {
4848
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
4949
}
5050

51-
// Auto-discover all instrumentation subprojects and add them as implementation dependencies.
52-
// Any subproject under :braintrust-java-agent:instrumentation is automatically bundled
53-
// into the shadow JAR, and its META-INF/services entries are merged.
54-
project(':braintrust-java-agent').subprojects.each { subproject ->
55-
if (subproject.path.startsWith(':braintrust-java-agent:instrumentation:')
56-
|| subproject.path == ':braintrust-java-agent:smoke-test:test-instrumentation') {
51+
// Auto-discover instrumentation subprojects from braintrust-sdk and add them as implementation
52+
// dependencies. These get bundled into the shadow JAR with merged META-INF/services entries.
53+
project(':braintrust-sdk').subprojects.each { subproject ->
54+
if (subproject.path.startsWith(':braintrust-sdk:instrumentation:')) {
5755
dependencies.add('implementation', subproject)
5856
}
5957
}
58+
// Also include test-instrumentation for smoke tests
59+
// TODO: don't bundle this instrumentation in the agent jar. Instead create a flag to look for instrumentation on the system classpath
60+
if (findProject(':braintrust-java-agent:smoke-test:test-instrumentation') != null) {
61+
dependencies.add('implementation', project(':braintrust-java-agent:smoke-test:test-instrumentation'))
62+
}
6063

6164
test {
6265
useJUnitPlatform()

braintrust-sdk/build.gradle

Lines changed: 129 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,86 @@ repositories {
2323
mavenCentral()
2424
}
2525

26-
def langchainVersion = '1.9.1'
26+
// Shared config for instrumentation subprojects
27+
subprojects {
28+
apply plugin: 'java'
29+
apply plugin: 'dev.braintrust.muzzle'
30+
31+
java {
32+
toolchain {
33+
languageVersion = JavaLanguageVersion.of(17)
34+
vendor = JvmVendorSpec.ADOPTIUM
35+
}
36+
}
37+
38+
tasks.withType(JavaCompile).configureEach {
39+
options.release = 17
40+
}
41+
42+
repositories {
43+
mavenCentral()
44+
}
45+
}
46+
47+
// Add muzzle generation task and muzzle check plugin to all instrumentation subprojects.
48+
subprojects { subproject ->
49+
subproject.afterEvaluate {
50+
if (subproject.path.startsWith(':braintrust-sdk:instrumentation:')) {
51+
def instrumentationApi = project(':braintrust-java-agent:instrumenter')
52+
53+
// Bootstrap classes (e.g. BraintrustBridge) are on the bootstrap classpath at runtime.
54+
// Make them available at compile time so instrumentation modules can reference them.
55+
dependencies.add('compileOnly', project(':braintrust-java-agent:bootstrap'))
56+
57+
// --- $Muzzle side-class generation (compile-time) ---
58+
59+
// Configuration for the muzzle generator classpath
60+
configurations.maybeCreate('muzzleGenerator')
61+
dependencies.add('muzzleGenerator', instrumentationApi)
62+
dependencies.add('muzzleGenerator', 'net.bytebuddy:byte-buddy:1.17.5')
63+
64+
task generateMuzzle(type: JavaExec) {
65+
dependsOn compileJava, instrumentationApi.tasks.named('compileJava')
66+
description = 'Generates $Muzzle side-classes for InstrumentationModule subclasses'
67+
group = 'build'
68+
69+
// Classpath: compiled instrumentation classes + instrumenter + bytebuddy + deps
70+
classpath = files(
71+
sourceSets.main.output.classesDirs,
72+
configurations.muzzleGenerator,
73+
configurations.compileClasspath
74+
)
75+
mainClass = 'dev.braintrust.instrumentation.muzzle.MuzzleGenerator'
76+
args = [sourceSets.main.java.classesDirectory.get().asFile.absolutePath]
77+
}
78+
79+
// Run muzzle generation after compileJava, before jar
80+
tasks.named('classes').configure { dependsOn generateMuzzle }
81+
82+
// Run muzzle checks as part of the check lifecycle
83+
tasks.named('check').configure { dependsOn 'muzzle' }
84+
}
85+
}
86+
}
87+
88+
// Configuration for embedding instrumentation subproject classes into the SDK JAR
89+
configurations {
90+
// Non-transitive: we only want the instrumentation subproject JARs themselves,
91+
// not their transitive dependencies (which include braintrust-sdk itself).
92+
embed {
93+
transitive = false
94+
}
95+
}
96+
97+
// Auto-discover instrumentation subprojects and add them to the embed configuration.
98+
// This must be done after project evaluation so the subprojects are resolved.
99+
afterEvaluate {
100+
project.subprojects.each { sub ->
101+
if (sub.path.startsWith(':braintrust-sdk:instrumentation:')) {
102+
dependencies.add('embed', sub)
103+
}
104+
}
105+
}
27106

28107
dependencies {
29108
api "io.opentelemetry:opentelemetry-api:${otelVersion}"
@@ -52,27 +131,44 @@ dependencies {
52131
testImplementation "org.junit.jupiter:junit-jupiter-params:${junitVersion}"
53132
testImplementation 'org.wiremock:wiremock:3.13.1'
54133

55-
// OAI instrumentation
56-
compileOnly 'com.openai:openai-java:2.8.1'
57-
testImplementation 'com.openai:openai-java:2.8.1'
58-
59-
// Anthropic Instrumentation
60-
compileOnly "com.anthropic:anthropic-java:2.8.1"
61-
testImplementation "com.anthropic:anthropic-java:2.8.1"
62-
63-
// Google GenAI Instrumentation
64-
compileOnly "com.google.genai:google-genai:1.20.0"
65-
testImplementation "com.google.genai:google-genai:1.20.0"
66-
67-
// LangChain4j Instrumentation
68-
compileOnly "dev.langchain4j:langchain4j:${langchainVersion}"
69-
compileOnly "dev.langchain4j:langchain4j-http-client:${langchainVersion}"
70-
compileOnly "dev.langchain4j:langchain4j-open-ai:${langchainVersion}"
71-
testImplementation "dev.langchain4j:langchain4j:${langchainVersion}"
72-
testImplementation "dev.langchain4j:langchain4j-http-client:${langchainVersion}"
73-
testImplementation "dev.langchain4j:langchain4j-open-ai:${langchainVersion}"
134+
74135
}
75136

137+
// Merge META-INF/services files from embedded instrumentation JARs.
138+
// This ensures ServiceLoader can discover InstrumentationModule implementations.
139+
task mergeEmbedServices {
140+
dependsOn configurations.embed
141+
def mergedDir = layout.buildDirectory.dir("merged-services")
142+
outputs.dir mergedDir
143+
144+
doLast {
145+
def servicesMap = [:]
146+
configurations.embed.each { file ->
147+
if (file.isFile()) {
148+
zipTree(file).matching { include 'META-INF/services/**' }.visit { details ->
149+
if (!details.isDirectory()) {
150+
def serviceName = details.relativePath.toString()
151+
if (!servicesMap.containsKey(serviceName)) {
152+
servicesMap[serviceName] = new LinkedHashSet<String>()
153+
}
154+
servicesMap[serviceName].addAll(details.file.readLines().findAll { it.trim() })
155+
}
156+
}
157+
}
158+
}
159+
def outDir = mergedDir.get().asFile
160+
outDir.deleteDir()
161+
servicesMap.each { path, lines ->
162+
def outFile = new File(outDir, path)
163+
outFile.parentFile.mkdirs()
164+
outFile.text = lines.join('\n') + '\n'
165+
}
166+
}
167+
}
168+
169+
jar.dependsOn mergeEmbedServices
170+
jar.from(layout.buildDirectory.dir("merged-services"))
171+
76172
// Generate braintrust.properties at build time with smart versioning
77173
task generateBraintrustProperties {
78174
description = 'Generate braintrust.properties with smart git-based versioning'
@@ -128,6 +224,19 @@ jar {
128224
'Main-Class': 'dev.braintrust.SDKMain'
129225
)
130226
}
227+
228+
// Embed instrumentation subproject classes into the SDK JAR.
229+
// Use dependsOn to ensure instrumentation jars are built first.
230+
dependsOn configurations.embed
231+
232+
from({
233+
configurations.embed.collect { it.isDirectory() ? it : zipTree(it) }
234+
}) {
235+
// Exclude META-INF from embedded jars (we handle service files separately below)
236+
exclude 'META-INF/**'
237+
}
238+
239+
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
131240
}
132241

133242
import com.vanniktech.maven.publish.JavadocJar

braintrust-java-agent/instrumentation/anthropic_2_2_0/build.gradle renamed to braintrust-sdk/instrumentation/anthropic_2_2_0/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ muzzle {
1010
}
1111

1212
dependencies {
13-
implementation project(':braintrust-java-agent:instrumenter')
13+
compileOnly project(':braintrust-java-agent:instrumenter')
1414
implementation "io.opentelemetry:opentelemetry-api:${otelVersion}"
1515
implementation 'com.google.code.findbugs:jsr305:3.0.2' // for @Nullable annotations
1616
implementation "org.slf4j:slf4j-api:${slf4jVersion}"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package dev.braintrust.instrumentation.anthropic;
2+
3+
import com.anthropic.client.AnthropicClient;
4+
import io.opentelemetry.api.OpenTelemetry;
5+
6+
/** Braintrust Anthropic client instrumentation. */
7+
public final class BraintrustAnthropic {
8+
9+
/** Instrument Anthropic client with Braintrust traces. */
10+
public static AnthropicClient wrap(OpenTelemetry openTelemetry, AnthropicClient client) {
11+
return dev.braintrust.instrumentation.anthropic.v2_2_0.BraintrustAnthropic.wrap(
12+
openTelemetry, client);
13+
}
14+
}

braintrust-java-agent/instrumentation/anthropic_2_2_0/src/main/java/dev/braintrust/instrumentation/anthropic/v2_2_0/BraintrustAnthropic.java renamed to braintrust-sdk/instrumentation/anthropic_2_2_0/src/main/java/dev/braintrust/instrumentation/anthropic/v2_2_0/BraintrustAnthropic.java

File renamed without changes.

braintrust-java-agent/instrumentation/anthropic_2_2_0/src/main/java/dev/braintrust/instrumentation/anthropic/v2_2_0/TracingHttpClient.java renamed to braintrust-sdk/instrumentation/anthropic_2_2_0/src/main/java/dev/braintrust/instrumentation/anthropic/v2_2_0/TracingHttpClient.java

File renamed without changes.

braintrust-java-agent/instrumentation/anthropic_2_2_0/src/main/java/dev/braintrust/instrumentation/anthropic/v2_2_0/auto/AnthropicInstrumentationModule.java renamed to braintrust-sdk/instrumentation/anthropic_2_2_0/src/main/java/dev/braintrust/instrumentation/anthropic/v2_2_0/auto/AnthropicInstrumentationModule.java

File renamed without changes.

braintrust-java-agent/instrumentation/anthropic_2_2_0/src/test/java/dev/braintrust/instrumentation/anthropic/v2_2_0/BraintrustAnthropicTest.java renamed to braintrust-sdk/instrumentation/anthropic_2_2_0/src/test/java/dev/braintrust/instrumentation/anthropic/v2_2_0/BraintrustAnthropicTest.java

File renamed without changes.

0 commit comments

Comments
 (0)