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
38 changes: 35 additions & 3 deletions examples/Program.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,36 @@
// Placeholder for Braintrust SDK examples
// Add example programs here
using System;
using System.Threading;
using Braintrust.Sdk;
using Braintrust.Sdk.Trace;

Console.WriteLine("Braintrust SDK Examples");
namespace Braintrust.Sdk.Examples;

class SimpleOpenTelemetryExample
{
static void Main(string[] args)
{
var braintrust = Braintrust.Get();
var tracerProvider = braintrust.OpenTelemetryCreate();
var activitySource = BraintrustTracing.GetActivitySource();

using (var activity = activitySource.StartActivity("hello-dotnet"))
{
if (activity != null)
{
Console.WriteLine("Performing simple operation...");
activity.SetTag("some boolean attribute", true);
Thread.Sleep(100); // Not required. This is just to make the span look interesting
}

if (activity != null)
{
var url = braintrust.ProjectUri()
+ $"/logs?r={activity.TraceId}&s={activity.SpanId}";
Console.WriteLine($"\n\n Example complete! View your data in Braintrust: {url}");
}
}

// Dispose the tracer provider to flush any remaining spans
tracerProvider?.Dispose();
}
}
61 changes: 61 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Braintrust SDK Examples

This directory contains example applications demonstrating how to use the Braintrust .NET SDK.

## Prerequisites

Before running these examples, make sure you have:

1. .NET 8.0 SDK installed
2. A Braintrust account and API key
3. Environment variables configured (see below)

## Configuration

Set the following environment variables:

```bash
export BRAINTRUST_API_KEY="your-api-key"
export BRAINTRUST_DEFAULT_PROJECT_NAME="your-project-name"
# Or use project ID:
# export BRAINTRUST_DEFAULT_PROJECT_ID="your-project-id"
```

## Running the Examples

### Simple OpenTelemetry Example

This example demonstrates basic OpenTelemetry tracing with Braintrust:

```bash
cd examples
dotnet run
```

The example:
- Initializes the Braintrust SDK
- Sets up OpenTelemetry tracing
- Creates a simple span with attributes
- Prints a URL to view the trace in Braintrust

## Example Code

The main example is in `Program.cs`:

```csharp
var braintrust = Braintrust.Get();
var tracerProvider = braintrust.OpenTelemetryCreate();
var activitySource = BraintrustTracing.GetActivitySource();

using (var activity = activitySource.StartActivity("hello-dotnet"))
{
activity?.SetTag("some boolean attribute", true);
// Your code here...
}
```

## Next Steps

- Check out the [Braintrust documentation](https://www.braintrust.dev/docs) for more advanced usage
- Explore the SDK source code in `src/Braintrust.Sdk/`
- Look at the test files in `tests/Braintrust.Sdk.Tests/` for more examples
2 changes: 2 additions & 0 deletions src/Braintrust.Sdk/Braintrust.Sdk.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
<ItemGroup>
<PackageReference Include="OpenTelemetry" Version="1.9.0" />
<PackageReference Include="OpenTelemetry.Api" Version="1.9.0" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.9.0" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.9.0" />
<PackageReference Include="System.Text.Json" Version="8.0.5" />
</ItemGroup>

Expand Down
73 changes: 33 additions & 40 deletions src/Braintrust.Sdk/Braintrust.cs
Original file line number Diff line number Diff line change
Expand Up @@ -128,48 +128,41 @@ public Uri ProjectUri()
return new Uri($"{Config.AppUrl}/app/{orgAndProject.OrgInfo.Name}/p/{orgAndProject.Project.Name}");
}

// TODO: Implement when we have BraintrustTracing
// /// <summary>
// /// Quick start method that sets up global OpenTelemetry with this Braintrust.
// ///
// /// If you're looking for more options for configuring Braintrust/OpenTelemetry,
// /// consult the Enable method.
// /// </summary>
// public OpenTelemetry OpenTelemetryCreate()
// {
// return OpenTelemetryCreate(registerGlobal: true);
// }
/// <summary>
/// Quick start method that sets up OpenTelemetry with this Braintrust.
///
/// If you're looking for more options for configuring Braintrust/OpenTelemetry,
/// consult the Enable method.
/// </summary>
public OpenTelemetry.Trace.TracerProvider OpenTelemetryCreate()
{
return OpenTelemetryCreate(registerGlobal: true);
}

// TODO: Implement when we have BraintrustTracing
// /// <summary>
// /// Quick start method that sets up OpenTelemetry with this Braintrust.
// ///
// /// If you're looking for more options for configuring Braintrust and OpenTelemetry,
// /// consult the Enable method.
// /// </summary>
// public OpenTelemetry OpenTelemetryCreate(bool registerGlobal)
// {
// return BraintrustTracing.Of(Config, registerGlobal);
// }
/// <summary>
/// Quick start method that sets up OpenTelemetry with this Braintrust.
///
/// If you're looking for more options for configuring Braintrust and OpenTelemetry,
/// consult the Enable method.
/// </summary>
public OpenTelemetry.Trace.TracerProvider OpenTelemetryCreate(bool registerGlobal)
{
return Trace.BraintrustTracing.Of(Config, registerGlobal);
}

// TODO: Implement when we have BraintrustTracing
// /// <summary>
// /// Add Braintrust to existing OpenTelemetry builders.
// ///
// /// This method provides the most options for configuring Braintrust and OpenTelemetry.
// /// If you're looking for a more user-friendly setup, consult the OpenTelemetryCreate methods.
// ///
// /// NOTE: This method should only be invoked once. Enabling Braintrust multiple times is
// /// unsupported and may lead to undesired behavior.
// /// </summary>
// public void OpenTelemetryEnable(
// TracerProviderBuilder tracerProviderBuilder,
// LoggerProviderBuilder loggerProviderBuilder,
// MeterProviderBuilder meterProviderBuilder)
// {
// BraintrustTracing.Enable(
// Config, tracerProviderBuilder, loggerProviderBuilder, meterProviderBuilder);
// }
/// <summary>
/// Add Braintrust to existing OpenTelemetry TracerProviderBuilder.
///
/// This method provides the most options for configuring Braintrust and OpenTelemetry.
/// If you're looking for a more user-friendly setup, consult the OpenTelemetryCreate methods.
///
/// NOTE: This method should only be invoked once. Enabling Braintrust multiple times is
/// unsupported and may lead to undesired behavior.
/// </summary>
public void OpenTelemetryEnable(OpenTelemetry.Trace.TracerProviderBuilder tracerProviderBuilder)
{
Trace.BraintrustTracing.Enable(Config, tracerProviderBuilder);
}

// TODO: Implement when we have Eval
// /// <summary>
Expand Down
4 changes: 2 additions & 2 deletions src/Braintrust.Sdk/Config/BraintrustConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,11 @@ private BraintrustConfig(IDictionary<string, string> envOverrides) : base(envOve
/// </summary>
public string? GetBraintrustParentValue()
{
if (DefaultProjectId != null)
if (!string.IsNullOrEmpty(DefaultProjectId))
{
return $"project_id:{DefaultProjectId}";
}
else if (DefaultProjectName != null)
else if (!string.IsNullOrEmpty(DefaultProjectName))
{
return $"project_name:{DefaultProjectName}";
}
Expand Down
95 changes: 95 additions & 0 deletions src/Braintrust.Sdk/Trace/BraintrustContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
using System;
using System.Diagnostics;
using System.Threading;

namespace Braintrust.Sdk.Trace;

/// <summary>
/// Context carrier for Braintrust parent relationships (project/experiment IDs).
/// This is stored in AsyncLocal to propagate parent information through the trace.
/// </summary>
public sealed class BraintrustContext
{
private static readonly AsyncLocal<BraintrustContext?> _current = new AsyncLocal<BraintrustContext?>();

public string? ProjectId { get; }
public string? ExperimentId { get; }

private BraintrustContext(string? projectId, string? experimentId)
{
ProjectId = projectId;
ExperimentId = experimentId;
}

/// <summary>
/// Create a Braintrust context for an experiment.
/// </summary>
public static BraintrustContext OfExperiment(string experimentId)
{
return new BraintrustContext(null, experimentId);
}

/// <summary>
/// Create a Braintrust context for a project.
/// </summary>
public static BraintrustContext OfProject(string projectId)
{
return new BraintrustContext(projectId, null);
}

/// <summary>
/// Get the current Braintrust context.
/// </summary>
public static BraintrustContext? Current => _current.Value;

/// <summary>
/// Get the Braintrust context from the current Activity (span).
/// </summary>
public static BraintrustContext? FromContext(Activity? activity = null)
{
// Return the current async local value
return _current.Value;
}

/// <summary>
/// Set this context as the current Braintrust context.
/// Returns a disposable scope that will restore the previous context.
/// </summary>
public IDisposable MakeCurrent()
{
var previous = _current.Value;
_current.Value = this;
return new ContextScope(previous);
}

private class ContextScope : IDisposable
{
private readonly BraintrustContext? _previous;

public ContextScope(BraintrustContext? previous)
{
_previous = previous;
}

public void Dispose()
{
_current.Value = _previous;
}
}

/// <summary>
/// Get the parent value string for this context (format: "project_id:X" or "experiment_id:Y").
/// </summary>
public string? GetParentValue()
{
if (ExperimentId != null)
{
return $"experiment_id:{ExperimentId}";
}
if (ProjectId != null)
{
return $"project_id:{ProjectId}";
}
return null;
}
}
42 changes: 42 additions & 0 deletions src/Braintrust.Sdk/Trace/BraintrustSpanExporter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using System;
using System.Diagnostics;
using Braintrust.Sdk.Config;
using OpenTelemetry;

namespace Braintrust.Sdk.Trace;

/// <summary>
/// Custom span exporter for Braintrust that logs span exports in debug mode.
///
/// Note: Unlike the Java SDK, this implementation does not yet support dynamic per-parent
/// HTTP header routing. The parent information is still included in span attributes,
/// so the backend can route based on that. Full per-parent header support requires
/// a more complex implementation with custom HTTP handling.
/// </summary>
internal sealed class BraintrustSpanExporter : BaseExporter<Activity>
{
private readonly BraintrustConfig _config;

public BraintrustSpanExporter(BraintrustConfig config)
{
_config = config ?? throw new ArgumentNullException(nameof(config));
}

public override ExportResult Export(in Batch<Activity> batch)
{
if (_config.Debug)
{
var count = 0;
foreach (var activity in batch)
{
count++;
var parent = activity.GetTagItem(BraintrustSpanProcessor.ParentAttributeKey);
Console.WriteLine($"[BraintrustSpanExporter] Exporting span: {activity.DisplayName}, parent={parent}");
}
Console.WriteLine($"[BraintrustSpanExporter] Exported {count} spans");
}

// Return success - the actual export is handled by the OTLP exporter in the pipeline
return ExportResult.Success;
}
}
Loading