Skip to content
Draft
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
28 changes: 28 additions & 0 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -266,3 +266,31 @@ func (c *Client) Permalink(span oteltrace.Span) string {
}
return link
}

// Export serializes the span into a string that can be passed to another process
// and used with ContextWithExportedSpan to create child spans there. The format
// is compatible with the Braintrust JS/Python span.export() output.
//
// Returns an empty string and logs a warning on error.
func (c *Client) Export(span oteltrace.Span) string {
s, err := bttrace.Export(span)
if err != nil {
c.logger.Warn("could not export span", "error", err)
return ""
}
return s
}

// ContextWithExportedSpan returns a context that carries the exported span as the
// remote parent. Spans started with this context will be children of that span.
// The exported string should be from Export(span) or span.export() (JS/Python).
//
// Returns the original context and logs a warning on error.
func (c *Client) ContextWithExportedSpan(ctx context.Context, exported string) context.Context {
out, err := bttrace.ContextWithExportedSpan(ctx, exported)
if err != nil {
c.logger.Warn("could not set context from exported span", "error", err)
return ctx
}
return out
}
29 changes: 28 additions & 1 deletion examples/distributed-tracing/main.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
// Package main demonstrates distributed tracing using W3C baggage propagation.
// Package main demonstrates distributed tracing using W3C baggage propagation
// and Braintrust span export (similar to span.export() in the JS/Python SDK).
//
// This example shows how trace context propagates across service boundaries
// via W3C baggage. A parent span encodes context to headers, and a child span
// extracts it (simulated without an actual HTTP server).
//
// Alternatively, you can use trace.Export(span) to get a serialized string and
// trace.ContextWithExportedSpan(ctx, exported) on the remote side to attach
// children to that span (e.g. when passing the parent in a message or custom header).
//
// To run this example:
//
// export BRAINTRUST_API_KEY="your-api-key"
Expand All @@ -20,6 +25,7 @@ import (
sdktrace "go.opentelemetry.io/otel/sdk/trace"

"github.com/braintrustdata/braintrust-sdk-go"
bttrace "github.com/braintrustdata/braintrust-sdk-go/trace"
)

func main() {
Expand Down Expand Up @@ -59,6 +65,11 @@ func main() {
// Call remote service (simulates crossing service boundary)
simulateHTTPRequest(headers)

// Alternative: pass exported span string (like JS/Python span.export())
if exported, err := bttrace.Export(parentSpan); err == nil {
simulateHTTPRequestWithExportedSpan(exported)
}

// Flush all spans
if err := tp.ForceFlush(context.Background()); err != nil {
log.Printf("Failed to flush spans: %v", err)
Expand All @@ -78,3 +89,19 @@ func simulateHTTPRequest(headers map[string]string) {
_, span := tracer.Start(ctx, "remote-service.handle-request")
defer span.End()
}

// simulateHTTPRequestWithExportedSpan simulates a remote service that receives
// an exported span string (e.g. from a message or custom header) and creates
// a child span. This is similar to using span.export() in the JS/Python SDK.
func simulateHTTPRequestWithExportedSpan(exported string) {
tracer := otel.Tracer("examples/distributed-tracing")

ctx, err := bttrace.ContextWithExportedSpan(context.Background(), exported)
if err != nil {
log.Printf("ContextWithExportedSpan: %v", err)
return
}

_, span := tracer.Start(ctx, "remote-service.handle-request-export")
defer span.End()
}
68 changes: 68 additions & 0 deletions examples/export-span/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Package main demonstrates linking distributed traces using span export.
//
// Service A starts a root span and exports it (like span.export() in JS/Python).
// The exported string is passed to Service B (e.g. via message queue or HTTP header).
// Service B uses ContextWithExportedSpan to create a child span in the same trace.
//
// To run:
//
// export BRAINTRUST_API_KEY="your-api-key"
// go run examples/export-span/main.go
package main

import (
"context"
"fmt"
"log"

sdktrace "go.opentelemetry.io/otel/sdk/trace"
oteltrace "go.opentelemetry.io/otel/trace"

"github.com/braintrustdata/braintrust-sdk-go"
bttrace "github.com/braintrustdata/braintrust-sdk-go/trace"
)

func main() {
tp := sdktrace.NewTracerProvider()
defer tp.Shutdown(context.Background()) //nolint:errcheck

bt, err := braintrust.New(tp,
braintrust.WithProject("go-sdk-examples"),
braintrust.WithBlockingLogin(true),
)
if err != nil {
log.Fatalf("Failed to initialize Braintrust: %v", err)
}

tracer := bt.Tracer("export-span-example")

// --- Service A: create root span and export it ---
_, rootSpan := tracer.Start(context.Background(), "service-a.request")
exported := bt.Export(rootSpan)
if exported == "" {
log.Fatal("Export failed")
}
defer rootSpan.End()

// Simulate sending the exported string to Service B (e.g. in a message or header)
// In a real setup: publish to a queue, put in HTTP header "X-Braintrust-Span", etc.
runServiceB(tracer, exported)

if err := tp.ForceFlush(context.Background()); err != nil {
log.Printf("Flush: %v", err)
}

fmt.Printf("\nView trace: %s\n", bt.Permalink(rootSpan))
}

// runServiceB simulates a separate service that receives the exported span
// and creates a child span in the same trace.
func runServiceB(tracer oteltrace.Tracer, exported string) {
ctx, err := bttrace.ContextWithExportedSpan(context.Background(), exported)
if err != nil {
log.Fatalf("ContextWithExportedSpan: %v", err)
}

_, childSpan := tracer.Start(ctx, "service-b.process")
childSpan.End()
}
Loading
Loading