# Go

> net/http, Gin and Echo services with the OpenTelemetry Go SDK

Go has no runtime agent — instrumentation is explicit, which means about twenty lines of
setup and a middleware per framework.

## Install

```bash
go get go.opentelemetry.io/otel \
       go.opentelemetry.io/otel/sdk \
       go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp \
       go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp
```

> **Warning:** Use the `otlptracehttp` exporter, not `otlptracegrpc`. The ingress speaks OTLP over HTTP.

## Set up the provider

```go
package main

import (
    "context"
    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/attribute"
    "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
    "go.opentelemetry.io/otel/sdk/resource"
    sdktrace "go.opentelemetry.io/otel/sdk/trace"
    semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
)

func initTracer(ctx context.Context) (*sdktrace.TracerProvider, error) {
    exporter, err := otlptracehttp.New(ctx,
        otlptracehttp.WithEndpoint("ingress.rocketlog.io"), // host only, no scheme
        otlptracehttp.WithHeaders(map[string]string{
            "Authorization": "Bearer rg_live_xxxxxx",
        }),
    )
    if err != nil {
        return nil, err
    }

    res, _ := resource.New(ctx, resource.WithAttributes(
        semconv.ServiceName("my-go-service"),
    ))

    tp := sdktrace.NewTracerProvider(
        sdktrace.WithBatcher(exporter),
        sdktrace.WithResource(res),
    )
    otel.SetTracerProvider(tp)
    return tp, nil
}
```

> **Note:** `WithEndpoint` takes a **host**, not a URL — no `https://` and no path. Passing a full URL
  is the most common Go setup mistake. Use `otlptracehttp.WithInsecure()` only against a
  local collector.

## Wire it up and shut it down

```go
func main() {
    ctx := context.Background()
    tp, err := initTracer(ctx)
    if err != nil {
        log.Fatal(err)
    }
    // Without this, buffered spans are dropped on exit.
    defer tp.Shutdown(ctx)

    mux := http.NewServeMux()
    mux.Handle("/transfers", otelhttp.NewHandler(transfersHandler(), "POST /transfers"))
    http.ListenAndServe(":8080", mux)
}
```

## Custom spans

```go
tracer := otel.Tracer("payments")

ctx, span := tracer.Start(ctx, "settle-transfer")
defer span.End()

span.SetAttributes(attribute.String("transfer.id", transferID))

if err := settle(ctx, transferID); err != nil {
    span.RecordError(err)
    span.SetStatus(codes.Error, err.Error())
}
```

Pass `ctx` down through every call. A span started from a background context is a new
root and will not attach to the request that caused it.
