Skip to main content
Temporal is a durable execution platform that enables developers to build resilient distributed applications. This guide shows you how to trace Temporal workflows and activities in LangSmith using OpenTelemetry. LangSmith supports OpenTelemetry (OTEL) trace ingestion, which integrates seamlessly with Temporal’s native OpenTelemetry interceptors. This enables full distributed tracing across your workflow executions, activities, and any LLM calls within them.

Prerequisites

  • A LangSmith account and API key
  • Temporal server running (local or cloud)
  • OpenTelemetry SDK for your language

Environment variables

Set the following environment variables for all implementations:
VariableRequiredDescription
LANGSMITH_API_KEYYesYour LangSmith API key from Settings.
LANGSMITH_PROJECTNoProject name (defaults to "default").
For EU region or self-hosted LangSmith installations, also set LANGCHAIN_BASE_URL to your LangSmith instance URL.

Set up tracing

Go uses the langsmith-go SDK with Temporal’s OpenTelemetry interceptors to automatically trace workflows and activities.
1

Install

Install the LangSmith Go SDK, Temporal SDK, and OpenTelemetry interceptor:
go get github.com/langchain-ai/[email protected]
go get go.temporal.io/sdk
go get go.temporal.io/sdk/contrib/opentelemetry
2

Initialize tracer

Initialize the LangSmith tracer, create Temporal’s OpenTelemetry interceptor, and register it with the Temporal client and worker:
package main

import (
	"context"
	"log"

	"github.com/langchain-ai/langsmith-go"
	"go.temporal.io/sdk/client"
	"go.temporal.io/sdk/contrib/opentelemetry"
	"go.temporal.io/sdk/interceptor"
	"go.temporal.io/sdk/worker"
)

func main() {
	ctx := context.Background()

	// Initialize LangSmith tracer (reads LANGSMITH_API_KEY and LANGSMITH_PROJECT)
	ls, err := langsmith.NewTracer(
		langsmith.WithServiceName("temporal-worker"),
	)
	if err != nil {
		log.Fatal("Failed to initialize LangSmith tracer:", err)
	}
	defer ls.Shutdown(ctx)

	// Create Temporal tracing interceptor
	tracer := ls.Tracer("temporal-app")
	tracingInterceptor, err := opentelemetry.NewTracingInterceptor(
		opentelemetry.TracerOptions{Tracer: tracer},
	)
	if err != nil {
		log.Fatal("Failed to create tracing interceptor:", err)
	}

	// Create Temporal client with tracing
	c, err := client.Dial(client.Options{
		Interceptors: []interceptor.ClientInterceptor{tracingInterceptor},
	})
	if err != nil {
		log.Fatal("Failed to create Temporal client:", err)
	}
	defer c.Close()

	// Create worker with tracing (uses same client)
	w := worker.New(c, "my-task-queue", worker.Options{})
	w.RegisterWorkflow(MyWorkflow)
	w.RegisterActivity(MyActivity)

	// Start worker
	if err := w.Run(worker.InterruptCh()); err != nil {
		log.Fatal("Worker failed:", err)
	}
}
3

Define workflow and activity

Define a workflow that executes an activity. The activity demonstrates how to add custom span attributes for LangSmith visibility:
package main

import (
	"context"
	"fmt"
	"time"

	"go.opentelemetry.io/otel/attribute"
	"go.opentelemetry.io/otel/trace"
	"go.temporal.io/sdk/activity"
	"go.temporal.io/sdk/workflow"
)

// MyWorkflow executes an activity
func MyWorkflow(ctx workflow.Context, input string) (string, error) {
	ao := workflow.ActivityOptions{
		StartToCloseTimeout: 10 * time.Second,
	}
	ctx = workflow.WithActivityOptions(ctx, ao)

	var result string
	err := workflow.ExecuteActivity(ctx, MyActivity, input).Get(ctx, &result)
	return result, err
}

// MyActivity processes input with custom span attributes
func MyActivity(ctx context.Context, input string) (string, error) {
	logger := activity.GetLogger(ctx)
	logger.Info("Processing", "input", input)

	// Get the span created by Temporal's interceptor
	span := trace.SpanFromContext(ctx)

	// Add Gen AI attributes for LangSmith visibility
	span.SetAttributes(
		attribute.String("gen_ai.prompt", input),
		attribute.String("gen_ai.operation.name", "chat"),
	)

	result := fmt.Sprintf("Processed: %s", input)

	// Set completion attribute
	span.SetAttributes(
		attribute.String("gen_ai.completion", result),
	)

	return result, nil
}
4

Execute workflow

In a separate client application, initialize the tracer and execute the workflow:
client.go
// In a separate function or client application
func executeWorkflow() {
	ctx := context.Background()

	// Initialize tracer for client
	ls, err := langsmith.NewTracer(
		langsmith.WithServiceName("temporal-client"),
	)
	if err != nil {
		log.Fatal(err)
	}
	defer ls.Shutdown(ctx)

	// Create client with tracing
	tracer := ls.Tracer("temporal-app")
	tracingInterceptor, err := opentelemetry.NewTracingInterceptor(
		opentelemetry.TracerOptions{Tracer: tracer},
	)
	if err != nil {
		log.Fatal(err)
	}

	c, err := client.Dial(client.Options{
		Interceptors: []interceptor.ClientInterceptor{tracingInterceptor},
	})
	if err != nil {
		log.Fatal(err)
	}
	defer c.Close()

	// Execute workflow
	workflowOptions := client.StartWorkflowOptions{
		ID:        "my-workflow-1",
		TaskQueue: "my-task-queue",
	}

	we, err := c.ExecuteWorkflow(ctx, workflowOptions, MyWorkflow, "Hello World")
	if err != nil {
		log.Fatal(err)
	}

	var result string
	if err := we.Get(ctx, &result); err != nil {
		log.Fatal(err)
	}

	log.Printf("Workflow result: %s", result)
}

View traces in LangSmith

Once configured, traces will appear in your LangSmith project:
  1. Navigate to your LangSmith instance.
  2. Select your project.
  3. View traces in the Tracing tab.
  4. Click on individual traces to see the full span hierarchy.

Configuration options

Set a custom service name

Set a custom service name to distinguish different Temporal workers or services:
ls, err := langsmith.NewTracer(
    langsmith.WithServiceName("my-temporal-worker"),
)

Add custom span attributes

Add custom attributes to enrich your traces:
import "go.opentelemetry.io/otel/attribute"

span := trace.SpanFromContext(ctx)
span.SetAttributes(
    attribute.String("user.id", userID),
    attribute.String("workflow.version", "v2"),
)

Configure sampling

For high-volume workflows, configure sampling to reduce trace volume:
// Note: langsmith.NewTracer() uses default sampling
// For custom sampling, use the TracerProvider directly
tp := sdktrace.NewTracerProvider(
    sdktrace.WithBatcher(exporter),
    sdktrace.WithSampler(sdktrace.TraceIDRatioBased(0.1)), // 10% sampling
)

Troubleshooting

Traces not appearing

  1. Verify API key: Ensure LANGSMITH_API_KEY is set correctly
  2. Check endpoint: Confirm you’re using https://api.smith.langchain.com/otel/v1/traces
  3. Flush on shutdown: Call provider.shutdown() to flush pending spans before the application exits
  4. Check project: Verify traces are sent to the correct project (default is "default")

Missing activity spans

Ensure the tracing interceptor is configured on both the client and worker:
  • Client: Needs interceptor for starting workflows
  • Worker: Needs interceptor for executing activities

Context propagation issues

Verify propagators are configured correctly:
  • Go: langsmith.NewTracer() automatically configures propagators
  • Python/TypeScript: Ensure OpenTelemetry SDK is properly initialized with trace propagators

Worker shutdown hangs

If traces aren’t flushing, ensure you’re calling the shutdown method with proper timeout:
defer ls.Shutdown(context.Background())

Next steps

Additional resources


Connect these docs to Claude, VSCode, and more via MCP for real-time answers.