Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ on:

jobs:
build:
# we want to run ubuntu-latest but we'll pin to a specific version so CI is reproducable
# we want to run ubuntu-latest but we'll pin to a specific version so workflow is reproducable
runs-on: ubuntu-24.04

steps:
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/publish-release-from-tag.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ on:
jobs:
validate-and-publish:
name: Validate Tag and Publish Release
# we want to run ubuntu-latest but we'll pin to a specific version so workflow is reproducable
runs-on: ubuntu-24.04
steps:
- name: Checkout code
Expand Down
175 changes: 118 additions & 57 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,78 @@ plugins {
id("io.freefair.lombok") version "9.0.0-rc2"
}

// Generate braintrust.properties at build time with smart versioning
task generateBraintrustProperties {
description = 'Generate braintrust.properties with smart git-based versioning'
group = 'build'
version = generateVersion() // we could cache but not worth the hassle
group = 'dev.braintrust'

def outputDir = file("$buildDir/generated/resources")
def outputFile = file("$outputDir/braintrust.properties")
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
vendor = JvmVendorSpec.ADOPTIUM // eclipse JVM
}
withJavadocJar()
withSourcesJar()
}

outputs.file outputFile
outputs.upToDateWhen { false } // Always regenerate to get latest version
tasks.withType(JavaCompile).configureEach {
options.release = 17
}

doLast {
outputDir.mkdirs()
repositories {
mavenCentral()
}

def version = generateVersion()
ext {
otelVersion = '1.54.1'
jacksonVersion = '2.16.1'
junitVersion = '5.11.4'
slf4jVersion = '2.0.17'
}

outputFile.text = "sdk.version=${version}\n"
dependencies {
api "io.opentelemetry:opentelemetry-api:${otelVersion}"
api "io.opentelemetry:opentelemetry-sdk:${otelVersion}"
api "io.opentelemetry:opentelemetry-sdk-trace:${otelVersion}"
api "io.opentelemetry:opentelemetry-sdk-logs:${otelVersion}"
implementation "io.opentelemetry:opentelemetry-exporter-otlp:${otelVersion}"
implementation "io.opentelemetry:opentelemetry-exporter-logging:${otelVersion}"
implementation "io.opentelemetry:opentelemetry-sdk-testing:${otelVersion}"
implementation "io.opentelemetry:opentelemetry-semconv:1.30.1-alpha"

logger.info("Generated braintrust.properties with sdk.version=${version}")
}
implementation "com.fasterxml.jackson.core:jackson-databind:${jacksonVersion}"
implementation "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:${jacksonVersion}"
implementation "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:${jacksonVersion}"

implementation "org.slf4j:slf4j-api:${slf4jVersion}"
runtimeOnly "org.slf4j:slf4j-simple:${slf4jVersion}"

implementation 'org.apache.commons:commons-lang3:3.14.0'
implementation 'com.google.code.findbugs:jsr305:3.0.2' // for @Nullable annotations

testImplementation "org.junit.jupiter:junit-jupiter:${junitVersion}"
testImplementation "org.junit.jupiter:junit-jupiter-params:${junitVersion}"
testImplementation 'com.github.tomakehurst:wiremock-jre8:2.35.0'

// OAI instrumentation
compileOnly 'com.openai:openai-java:2.8.1'
testImplementation 'com.openai:openai-java:2.8.1'
implementation "io.opentelemetry.instrumentation:opentelemetry-openai-java-1.1:2.19.0-alpha"
}

/**
* Use git to compute the sdk version
*
* This is written into braintrust.properties at build time and shipped into the distributed sdk jar
*
* - if we are on a tag (i.e. v0.0.3), use the tag
* - otherwise, use $mostRecentTag-$currentCommitSha
*
* Additionally, if the git workspace is not clean, append -DIRTY to the version
*
* Examples
* - v0.0.3
* - v0.0.3-c4af682
* - v0.0.3-c4af682-DIRTY
*/
def generateVersion() {
// Check if workspace is clean
def gitStatusProcess = ['git', 'status', '--porcelain'].execute()
Expand Down Expand Up @@ -85,54 +135,26 @@ def generateVersion() {
return version
}

version = generateVersion() // we could cache but not worth the hassle
group = 'dev.braintrust'

java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
withJavadocJar()
withSourcesJar()
}

repositories {
mavenCentral()
}

ext {
otelVersion = '1.54.1'
jacksonVersion = '2.16.1'
junitVersion = '5.11.4'
slf4jVersion = '2.0.17'
}
// Generate braintrust.properties at build time with smart versioning
task generateBraintrustProperties {
description = 'Generate braintrust.properties with smart git-based versioning'
group = 'build'

dependencies {
api "io.opentelemetry:opentelemetry-api:${otelVersion}"
api "io.opentelemetry:opentelemetry-sdk:${otelVersion}"
api "io.opentelemetry:opentelemetry-sdk-trace:${otelVersion}"
api "io.opentelemetry:opentelemetry-sdk-logs:${otelVersion}"
implementation "io.opentelemetry:opentelemetry-exporter-otlp:${otelVersion}"
implementation "io.opentelemetry:opentelemetry-exporter-logging:${otelVersion}"
implementation "io.opentelemetry:opentelemetry-sdk-testing:${otelVersion}"
implementation "io.opentelemetry:opentelemetry-semconv:1.30.1-alpha"
def outputDir = file("$buildDir/generated/resources")
def outputFile = file("$outputDir/braintrust.properties")

implementation "com.fasterxml.jackson.core:jackson-databind:${jacksonVersion}"
implementation "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:${jacksonVersion}"
implementation "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:${jacksonVersion}"
outputs.file outputFile
outputs.upToDateWhen { false } // Always regenerate to get latest version

implementation "org.slf4j:slf4j-api:${slf4jVersion}"
runtimeOnly "org.slf4j:slf4j-simple:${slf4jVersion}"
doLast {
outputDir.mkdirs()

implementation 'org.apache.commons:commons-lang3:3.14.0'
implementation 'com.google.code.findbugs:jsr305:3.0.2' // for @Nullable annotations
def version = generateVersion()

compileOnly 'com.openai:openai-java:2.8.1'
implementation "io.opentelemetry.instrumentation:opentelemetry-openai-java-1.1:2.19.0-alpha"
outputFile.text = "sdk.version=${version}\n"

testImplementation "org.junit.jupiter:junit-jupiter:${junitVersion}"
testImplementation "org.junit.jupiter:junit-jupiter-params:${junitVersion}"
testImplementation 'com.openai:openai-java:2.8.1'
testImplementation 'com.github.tomakehurst:wiremock-jre8:2.35.0'
logger.info("Generated braintrust.properties with sdk.version=${version}")
}
}

test {
Expand Down Expand Up @@ -170,7 +192,8 @@ jar {
attributes(
'Implementation-Title': 'Braintrust Java SDK',
'Implementation-Version': version,
'Implementation-Vendor': 'Braintrust'
'Implementation-Vendor': 'Braintrust',
'Main-Class': 'dev.braintrust.trace.SDKMain'
)
}
}
Expand Down Expand Up @@ -217,6 +240,7 @@ sourceSets {
// Make sure properties are generated before compilation and packaging
compileJava.dependsOn generateBraintrustProperties
processResources.dependsOn generateBraintrustProperties
sourcesJar.dependsOn generateBraintrustProperties
jar.dependsOn generateBraintrustProperties

// Configure Spotless for code formatting
Expand All @@ -238,6 +262,43 @@ spotless {
}
}

task validateJavaVersion {
doLast {
def currentVersion = JavaVersion.current()
def requiredVersion = JavaVersion.VERSION_17

if (!currentVersion.isCompatibleWith(requiredVersion)) {
throw new GradleException(
"This project requires Java ${requiredVersion} or later. " +
"Current Java version: ${currentVersion} " +
"(${System.getProperty('java.version')}) " +
"from ${System.getProperty('java.home')}"
)
}
// println "✓ Using Java ${currentVersion} from ${System.getProperty('java.home')}"
}
}


// Task to test the jar by running it
task testJar(type: Exec) {
description = 'Test the jar by running it and fail build if non-zero exit code'
group = 'verification'
dependsOn jar

commandLine 'java', '-jar', jar.archiveFile.get().asFile.absolutePath

doFirst {
// println "Testing jar: ${jar.archiveFile.get().asFile.absolutePath}"
}
}

// Run validation before compilation
compileJava.dependsOn validateJavaVersion

// Run jar test as part of check task
check.dependsOn testJar

// Task to install git hooks
task installGitHooks(type: Exec) {
description = 'Install git hooks for code formatting'
Expand Down
8 changes: 2 additions & 6 deletions examples/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,10 @@ def braintrustLogLevel = System.getenv('BRAINTRUST_LOG_LEVEL') ?: 'info'

dependencies {
implementation project(':')
// TODO: not sure why I have to do this. I would have expected this to come over in the project root transitive deps
// To run otel examples
implementation "io.opentelemetry:opentelemetry-exporter-otlp:${otelVersion}"

// Official OpenAI Java SDK
// to run OAI instrumentation examples
implementation 'com.openai:openai-java:2.8.1'

// OkHttp for HTTP client (required by OpenAI SDK)
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
}

application {
Expand Down
27 changes: 11 additions & 16 deletions src/main/java/dev/braintrust/trace/BraintrustTracing.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
import io.opentelemetry.sdk.trace.export.BatchSpanProcessor;
import io.opentelemetry.semconv.resource.attributes.ResourceAttributes;
import java.time.Duration;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
import javax.annotation.Nonnull;
Expand All @@ -32,7 +31,7 @@ public final class BraintrustTracing {
public static final String PARENT_KEY = "braintrust.parent";
static final String OTEL_SERVICE_NAME = "braintrust-app";
static final String INSTRUMENTATION_NAME = "braintrust-java";
static final String INSTRUMENTATION_VERSION = loadVersionFromProperties();
static final String INSTRUMENTATION_VERSION = SDKMain.loadVersionFromProperties();

/**
* Quick start method that sets up global OpenTelemetry with Braintrust defaults. <br>
Expand Down Expand Up @@ -88,12 +87,7 @@ public static void enable(
final Duration exportInterval = Duration.ofSeconds(5);
final int maxQueueSize = 2048;
final int maxExportBatchSize = 512;
log.info(
"Initializing Braintrust OpenTelemetry with service={}, instrumentation-name={},"
+ " instrumentation-version={}",
OTEL_SERVICE_NAME,
INSTRUMENTATION_NAME,
INSTRUMENTATION_VERSION);
log.info(sdkInfoLogMessage());

// Create resource first so BraintrustSpanProcessor can access service.name
var resourceBuilder =
Expand Down Expand Up @@ -161,14 +155,15 @@ public static Tracer getTracer(OpenTelemetry openTelemetry) {
return openTelemetry.getTracer(INSTRUMENTATION_NAME, INSTRUMENTATION_VERSION);
}

private static String loadVersionFromProperties() {
try (var is = BraintrustTracing.class.getResourceAsStream("/braintrust.properties")) {
var props = new Properties();
props.load(is);
return props.getProperty("sdk.version");
} catch (Exception e) {
throw new RuntimeException("unable to determine sdk version", e);
}
private static String sdkInfoLogMessage() {
return "Initializing Braintrust OpenTelemetry with service=%s, instrumentation-name=%s, instrumentation-version=%s, jvm-version=%s, jvm-vendor=%s, jvm-name=%s"
.formatted(
OTEL_SERVICE_NAME,
INSTRUMENTATION_NAME,
INSTRUMENTATION_VERSION,
System.getProperty("java.runtime.version"),
System.getProperty("java.vendor"),
System.getProperty("java.vm.name"));
}

private BraintrustTracing() {}
Expand Down
29 changes: 29 additions & 0 deletions src/main/java/dev/braintrust/trace/SDKMain.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package dev.braintrust.trace;

import java.util.Properties;

class SDKMain {
/**
* Called by the build system to verify internals of the SDK. Prints sdk version to stdout.
*
* <p>Be mindful of classloading here. Otel is not shipped in the jar, so referencing otel
* classes directly or indirectly will fail the build with a NoClassDefFound error.
*/
public static void main(String... args) {
var sdkVersion = loadVersionFromProperties();
if (null == sdkVersion || sdkVersion.isEmpty()) {
throw new RuntimeException("sdk version not found: %s".formatted(sdkVersion));
}
System.out.println(sdkVersion);
}

static String loadVersionFromProperties() {
try (var is = SDKMain.class.getResourceAsStream("/braintrust.properties")) {
var props = new Properties();
props.load(is);
return props.getProperty("sdk.version");
} catch (Exception e) {
throw new RuntimeException("unable to determine sdk version", e);
}
}
}