Copy as Markdown[Open in ChatGPT](https://chatgpt.com/?q=Read%20https%3A%2F%2Fdocs-docusaurus.kinsta.page%2Fuser-guides%2Fai%2Fotel-integration%2Fcode-examples%2Fgo-manual.md%20and%20help%20me%20with%20my%20question%20about%20this%20Coralogix%20documentation%20page.)[Open in Claude](https://claude.ai/new?q=Read%20https%3A%2F%2Fdocs-docusaurus.kinsta.page%2Fuser-guides%2Fai%2Fotel-integration%2Fcode-examples%2Fgo-manual.md%20and%20help%20me%20with%20my%20question%20about%20this%20Coralogix%20documentation%20page.)

# Go — manual instrumentation

No auto-instrumentation for GenAI in Go. Manual spans with new semconv + raw `net/http` to OpenAI.

Before you start

These examples export to a local OpenTelemetry Collector over OTLP/gRPC. Deploy the collector first — see [Code examples → Deploy an OpenTelemetry Collector](https://docs-docusaurus.kinsta.page/user-guides/ai/otel-integration/code-examples/.md#prerequisites-deploy-an-opentelemetry-collector).

## `go.mod`[​](#gomod "Direct link to gomod")

```
module go-genai-manual-demo



go 1.22



require (

	go.opentelemetry.io/otel v1.34.0

	go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0

	go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0

	go.opentelemetry.io/otel/sdk v1.34.0

	go.opentelemetry.io/otel/trace v1.34.0

)
```

## Environment variables[​](#environment-variables "Direct link to Environment variables")

```
export OPENAI_API_KEY="sk-..."

export CX_OTEL_ENDPOINT="<COLLECTOR_HOST>:4317"
```

## `main.go`[​](#maingo "Direct link to maingo")

```
package main



import (

	"bytes"

	"context"

	"encoding/json"

	"fmt"

	"io"

	"log"

	"net/http"

	"os"

	"time"



	"go.opentelemetry.io/otel"

	"go.opentelemetry.io/otel/attribute"

	"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"

	"go.opentelemetry.io/otel/sdk/resource"

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

	semconv "go.opentelemetry.io/otel/semconv/v1.26.0"

	"go.opentelemetry.io/otel/trace"

)



type chatMessage struct {

	Role    string `json:"role"`

	Content string `json:"content"`

}

type chatRequest struct {

	Model    string        `json:"model"`

	Messages []chatMessage `json:"messages"`

	User     string        `json:"user,omitempty"`

}

type chatChoice struct {

	Message      chatMessage `json:"message"`

	FinishReason string      `json:"finish_reason"`

}

type chatUsage struct {

	PromptTokens     int `json:"prompt_tokens"`

	CompletionTokens int `json:"completion_tokens"`

}

type chatResponse struct {

	ID      string       `json:"id"`

	Model   string       `json:"model"`

	Choices []chatChoice `json:"choices"`

	Usage   chatUsage    `json:"usage"`

}



type msgPart struct {

	Type    string `json:"type"`

	Content string `json:"content"`

}

type semconvMsg struct {

	Role  string    `json:"role"`

	Parts []msgPart `json:"parts"`

}



// --- OTel setup: configure OTLP exporter and tracer provider ---

func initTracer(ctx context.Context) (*sdktrace.TracerProvider, error) {

	endpoint := os.Getenv("CX_OTEL_ENDPOINT")

	if endpoint == "" {

		endpoint = "<COLLECTOR_HOST>:4317"

	}

	exporter, err := otlptracegrpc.New(ctx,

		otlptracegrpc.WithEndpoint(endpoint),

		otlptracegrpc.WithInsecure(),

	)

	if err != nil {

		return nil, err

	}

	res, _ := resource.Merge(resource.Default(),

		resource.NewWithAttributes(semconv.SchemaURL,

			semconv.ServiceName("go-genai-manual-demo")))

	tp := sdktrace.NewTracerProvider(

		sdktrace.WithBatcher(exporter), sdktrace.WithResource(res))

	otel.SetTracerProvider(tp)

	return tp, nil

}



// --- Your app logic with manual OTel GenAI spans ---

func chatCompletion(ctx context.Context, tracer trace.Tracer) (*chatResponse, error) {

	model := "gpt-4o-mini"

	user := "user-42"

	msgs := []chatMessage{

		{Role: "system", Content: "You are a concise assistant."},

		{Role: "user", Content: "What is OpenTelemetry?"},

	}



	inputMsgs := make([]semconvMsg, len(msgs))

	for i, m := range msgs {

		inputMsgs[i] = semconvMsg{Role: m.Role,

			Parts: []msgPart{{Type: "text", Content: m.Content}}}

	}

	inputJSON, _ := json.Marshal(inputMsgs)



	ctx, span := tracer.Start(ctx, "chat "+model,

		trace.WithSpanKind(trace.SpanKindClient),

		trace.WithAttributes(

			attribute.String("gen_ai.operation.name", "chat"),

			attribute.String("gen_ai.provider.name", "openai"),

			attribute.String("gen_ai.request.model", model),

			attribute.String("gen_ai.request.user", user),

			attribute.String("gen_ai.input.messages", string(inputJSON)),

			attribute.String("server.address", "api.openai.com"),

			attribute.Int("server.port", 443),

		))

	defer span.End()



	body, _ := json.Marshal(chatRequest{Model: model, Messages: msgs, User: user})

	req, _ := http.NewRequestWithContext(ctx, "POST",

		"https://api.openai.com/v1/chat/completions", bytes.NewReader(body))

	req.Header.Set("Content-Type", "application/json")

	req.Header.Set("Authorization", "Bearer "+os.Getenv("OPENAI_API_KEY"))



	resp, err := http.DefaultClient.Do(req)

	if err != nil {

		span.RecordError(err)

		return nil, err

	}

	defer resp.Body.Close()

	respBytes, _ := io.ReadAll(resp.Body)



	var chatResp chatResponse

	json.Unmarshal(respBytes, &chatResp)



	finishReasons := make([]string, len(chatResp.Choices))

	outMsgs := make([]semconvMsg, len(chatResp.Choices))

	for i, c := range chatResp.Choices {

		finishReasons[i] = c.FinishReason

		outMsgs[i] = semconvMsg{Role: c.Message.Role,

			Parts: []msgPart{{Type: "text", Content: c.Message.Content}}}

	}

	outJSON, _ := json.Marshal(outMsgs)



	span.SetAttributes(

		attribute.String("gen_ai.response.id", chatResp.ID),

		attribute.String("gen_ai.response.model", chatResp.Model),

		attribute.StringSlice("gen_ai.response.finish_reasons", finishReasons),

		attribute.Int("gen_ai.usage.input_tokens", chatResp.Usage.PromptTokens),

		attribute.Int("gen_ai.usage.output_tokens", chatResp.Usage.CompletionTokens),

		attribute.String("gen_ai.output.messages", string(outJSON)))



	return &chatResp, nil

}



func main() {

	ctx := context.Background()

	tp, err := initTracer(ctx)

	if err != nil {

		log.Fatal(err)

	}

	defer func() {

		ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)

		defer cancel()

		tp.Shutdown(ctx)

	}()



	resp, err := chatCompletion(ctx, otel.Tracer("genai-manual-demo"))

	if err != nil {

		log.Fatal(err)

	}

	for _, c := range resp.Choices {

		fmt.Printf("%s: %s\n", c.FinishReason, c.Message.Content)

	}

	fmt.Printf("Tokens: %d in, %d out\n", resp.Usage.PromptTokens, resp.Usage.CompletionTokens)

}
```

## Build and run[​](#build-and-run "Direct link to Build and run")

```
go mod tidy && go run main.go
```

Tip

The Go OTel SDK does not export `gen_ai.*` constants yet — all keys are raw strings. Use `gen_ai.provider.name` (new semconv); the deprecated `gen_ai.system` is not needed.

## Next steps[​](#next-steps "Direct link to Next steps")

Look up which open-source library to use for your provider in [Compatibility matrix](https://docs-docusaurus.kinsta.page/user-guides/ai/otel-integration/providers/.md).
