Skip to content

Golang OpenTelemetry Instrumentation

This tutorial demonstrates how to instrument Golang applications to capture logs, metrics and traces using OpenTelemetry, and send them to Coralogix.

The OpenTelemetry-Go instrumentation for metrics and traces is currently stable, while instrumentation for logs is in beta. You can find the OpenTelemetry Go SDK documentation here.

Note

The examples provided here do not represent a concrete standard for instrumenting Golang applications but serve as guides for basic implementation. You should review your codebase and determine the best approach to manual instrumentation for your specific project.

Prerequisites

Initial Reusable Setup

Before instrumenting logs, traces, or metrics, we will set up Coralogix details and a shared OpenTelemetry Resource. You’ll reuse these across all signals.

1. Initialize your Go module

go mod init github.com/my/app

2. Set the environment variables required for Coralogix

export CX_OTLP_ENDPOINT="ingress.<region>.coralogix.com:443"
export CX_API_KEY="<your-coralogix-api-key>"

3. Install the necessary Go modules

go get go.opentelemetry.io/otel/attribute \
  go.opentelemetry.io/otel/sdk/resource \
  go.opentelemetry.io/otel/semconv/v1.34.0 

We will add signal-specific modules later in the corresponding sections.

4. Set up Coralogix settings and the shared Resource

package main

import (
    "errors"
    "os"
    "fmt"

    "go.opentelemetry.io/otel/attribute"
    "go.opentelemetry.io/otel/sdk/resource"
    semconv "go.opentelemetry.io/otel/semconv/v1.34.0"
)

type OtelSetup struct {
    Resource *resource.Resource
    endpoint string
    headers  map[string]string
}

func Configure() (*OtelSetup, error) {
    endpoint := os.Getenv("CX_OTLP_ENDPOINT") // e.g. ingress.eu1.coralogix.com:443
    token := os.Getenv("CX_API_KEY")          // Send Your Data API Key
    if endpoint == "" || token == "" {
        return nil, errors.New("CX_OTLP_ENDPOINT, CX_TOKEN must be set")
    }

    res, err := resource.Merge(
        resource.Default(),
        resource.NewWithAttributes(
            semconv.SchemaURL,
            semconv.ServiceName("go-otel-coralogix-example"),
            semconv.ServiceVersion("0.1.0"),
            attribute.String("cx.application.name", "my-app"),     // required app name
            attribute.String("cx.subsystem.name", "my-subsystem"), // required subsys name
        ),
    )
    if err != nil {
        return nil, err
    }

    return &OtelSetup{
        Resource: res,
        endpoint: endpoint,
        headers:  map[string]string{"Authorization": "Bearer " + token},
    }, nil
}

func main() {
    // Load Coralogix Otel configuratio
    _, err := Configure()
    if err != nil {
        fmt.Printf("Failed to configure OpenTelemetry: %v", err)
    }
    fmt.Println("OpenTelemetry configured")
}

4. Running the examples

go mod tidy
go run main.go

Logs

For logging, we will initialize a LoggerProvider. This provider lets us use a Log Bridge from existing popular log packages (such as slog, logrus, zap, or logr) into the OpenTelemetry ecosystem. Read more about the OpenTelemetry Logs Go SDK in the official documentation.

Warning

Stability note: The logs signal in OpenTelemetry Go is currently in beta. Its API and behavior may change in future releases.

1. Install the necessary Go modules for logging

go get go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc \
  go.opentelemetry.io/otel/sdk/log \
  go.opentelemetry.io/otel/log/global \
  google.golang.org/grpc/credentials \
  go.opentelemetry.io/contrib/bridges/otelslog

2. Configure Logger Provider

package main

import (
    "context"
    "crypto/tls"
    "errors"
    "fmt"
    "log/slog"
    "os"
    "time"

    "go.opentelemetry.io/contrib/bridges/otelslog"
    "go.opentelemetry.io/otel/attribute"
    "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc"
    "go.opentelemetry.io/otel/log/global"
    logsdk "go.opentelemetry.io/otel/sdk/log"
    "go.opentelemetry.io/otel/sdk/resource"
    semconv "go.opentelemetry.io/otel/semconv/v1.34.0"
    "google.golang.org/grpc/credentials"
)

func (s *OtelSetup) NewLoggerProvider(ctx context.Context) (*logsdk.LoggerProvider, error) {
    exp, err := otlploggrpc.New(ctx,
        otlploggrpc.WithEndpoint(s.endpoint),
        otlploggrpc.WithHeaders(s.headers),
        otlploggrpc.WithTLSCredentials(credentials.NewTLS(&tls.Config{})),
        otlploggrpc.WithTimeout(5*time.Second),
    )
    if err != nil {
        return nil, err
    }
    return logsdk.NewLoggerProvider(
        logsdk.WithResource(s.Resource),
        logsdk.WithProcessor(logsdk.NewBatchProcessor(exp)),
    ), nil
}

3. Set up the Log Bridge

Log bridge is a component that bridges logs from an existing log package into the OpenTelemetry Log SDK using the Logs Bridge API. We will be using slog package for this example.

// ...
// add complete code from above 
// ...

func main() {
    ctx := context.Background()
    // Load Coralogix Otel configuration from environment variables
    setup, err := Configure()
    if err != nil {
        panic(err)
    }

    // Create a logger provider that can be passed directly when creating bridges.
    lp, err := setup.NewLoggerProvider(ctx)
    if err != nil { panic(err) }

    // Handle provider shutdown
    defer func() {
        if err := lp.Shutdown(ctx); err != nil { fmt.Println(err) } 
    }()

    // Register as global logger provider so that it can be accessed global.LoggerProvider.
    // Most log bridges use the global logger provider as default.
    // If the global logger provider is not set then a no-op implementation
    // is used, which fails to generate data.
    global.SetLoggerProvider(lp)

    // Create bridge between slog and OpenTelemetry
    // you can omit WithLoggerProvider and the global will be used.
    h := otelslog.NewHandler("my-app", otelslog.WithLoggerProvider(lp))
    slog.SetDefault(slog.New(h))

    // Emit log
    slog.Info("Hello from Coralogix Go OTEL Log Example")
}

After a successful run, you can view your logs in the Coralogix Logs dashboard.

Go OpenTelemetry Logging Provider

Traces

For tracing, we will initialize an OpenTelemetry TracerProvider that lets us use Tracer to create spans. In this example, we’ll manually instrument a simple web application that exposes /roll endpoint for rolling a dice.

1. Install the necessary Go modules for tracing

go get \
  github.com/coralogix/coralogix-opentelemetry-go/sampler \
  go.opentelemetry.io/otel \
  go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc \
  go.opentelemetry.io/otel/sdk/trace \
  google.golang.org/grpc/credentials

2. Configure Trace Provider

package main

import (
    "context"
    "crypto/tls"
    "errors"
    "fmt"
    "log"
    "math/rand"
    "net/http"
    "os"
    "time"

    "github.com/coralogix/coralogix-opentelemetry-go/sampler"
    "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.34.0"
    trace "go.opentelemetry.io/otel/trace"
    "google.golang.org/grpc/credentials"
)

func (s *OtelSetup) NewTracerProvider(ctx context.Context) (*sdktrace.TracerProvider, error) {
    exp, err := otlptracegrpc.New(ctx,
        otlptracegrpc.WithEndpoint(s.endpoint),
        otlptracegrpc.WithHeaders(s.headers),
        otlptracegrpc.WithTLSCredentials(credentials.NewTLS(&tls.Config{})),
        otlptracegrpc.WithTimeout(5 * time.Second),
    )
    if err != nil {
        return nil, fmt.Errorf("create trace exporter: %w", err)
    }

    return sdktrace.NewTracerProvider(
        sdktrace.WithSampler(sampler.NewCoralogixSampler(sdktrace.AlwaysSample())),
        sdktrace.WithResource(s.Resource),
        sdktrace.WithSpanProcessor(sdktrace.NewBatchSpanProcessor(exp)),
    ), nil
}

3. Instrument an HTTP handler

Here is a simple handler that creates a span for the request and a child span for rolling a dice.

const (
    traceName = "coralogix-go-otel-example"
)

func rollHandler(w http.ResponseWriter, r *http.Request) {
    tracer := otel.Tracer(traceName)

    // Start a span for this HTTP handler
    ctx, span := tracer.Start(r.Context(),
        "roll-handler",
        trace.WithSpanKind(trace.SpanKindServer),
    )
    defer span.End()

    roll := rollDice(ctx)

    // These describe HTTP request context.
    // In production, middleware could enrich spans automatically.
    span.SetAttributes(
        attribute.String("span.kind", "server"),
        attribute.String("resource.name", r.Method+" "+r.URL.Path),
        attribute.String("http.method", r.Method),
        attribute.String("http.url", r.URL.Path),
        attribute.String("http.route", r.URL.Path),
        attribute.String("http.target", r.URL.String()),
        attribute.String("http.useragent", r.UserAgent()),
        attribute.String("http.host", r.Host),
    )

    fmt.Fprintf(w, "rolled a %d\n", roll)
}

func rollDice(ctx context.Context) int {
    roll := rand.Intn(6) + 1

    tracer := otel.Tracer(traceName)
    _, span := tracer.Start(ctx, "roll-dice")

    defer span.End()
    span.SetAttributes(attribute.Int("dice.roll", roll))

    return roll
}

4. Run a simple HTTP server

Finally, configure the provider, register the handler, and start the HTTP server.

// ...
// add complete code from above 
// ...

func main() {
    ctx := context.Background()
    // Load Coralogix Otel configuration from environment variables
    setup, err := Configure()
    if err != nil {
        panic(err)
    }

    // Create a Trace provider
    tp, err := setup.NewTracerProvider(ctx)
    if err != nil {
        panic(err)
    }
    // Handle provider shutdown
    defer func() {
        if err := tp.Shutdown(ctx); err != nil {
            fmt.Println(err)
        }
    }()
    // Register as global traces provider
    otel.SetTracerProvider(tp)

    // Register the HTTP handler
    http.HandleFunc("/roll", rollHandler)

    fmt.Println("serving on :8080/roll")
    if err := http.ListenAndServe(":8080", nil); err != nil {
        panic(err)
    }
}

After a successful run, make a few requests to the /roll endpoint to generate data.

curl http://localhost:8080/roll

You can view your traces in the Coralogix dashboard.

Go OpenTelemetry Trace Provider

Metrics

For producing metrics, we will set up a MeterProvider, which lets us use a Meter to define and record metrics. Read more about supported metrics in the official documentation.

In this example, we will use the same roll dice example from the Traces section, but this time we will instrument it using a simple middleware that counts every incoming HTTP request and exports it as a counter metric.

1. Install the necessary Go modules for metrics

go get \
  go.opentelemetry.io/otel \
  go.opentelemetry.io/otel/metric \
  go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc \
  go.opentelemetry.io/otel/sdk/metric \
  google.golang.org/grpc/credentials

2. Configure the Metric Provider

package main

import (
    "context"
    "crypto/tls"
    "errors"
    "fmt"
    "math/rand"
    "os"
    "time"

    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/attribute"
    "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc"
    "go.opentelemetry.io/otel/metric"
    sdkmetric "go.opentelemetry.io/otel/sdk/metric"
    "go.opentelemetry.io/otel/sdk/resource"
    semconv "go.opentelemetry.io/otel/semconv/v1.34.0"
    "google.golang.org/grpc/credentials"
)

func (s *OtelSetup) NewMeterProvider(ctx context.Context) (*sdkmetric.MeterProvider, error) {
    exp, err := otlpmetricgrpc.New(ctx,
        otlpmetricgrpc.WithEndpoint(s.endpoint),
        otlpmetricgrpc.WithHeaders(s.headers),
        otlpmetricgrpc.WithTLSCredentials(credentials.NewTLS(&tls.Config{})),
        otlpmetricgrpc.WithTimeout(5*time.Second),
    )
    if err != nil {
        return nil, fmt.Errorf("create metric exporter: %w", err)
    }

    // default periodic reader 
    reader := sdkmetric.NewPeriodicReader(exp)

    return sdkmetric.NewMeterProvider(
        sdkmetric.WithResource(s.Resource),
        sdkmetric.WithReader(reader),
    ), nil
}

3. Set HTTP Request Counter

We’ll implement a simple HTTP request counter using middleware that increments a counter for every request.

// rollHandler simulates rolling a dice
func rollHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "rolled a %d\n", rand.Intn(6)+1)
}

type statusWriter struct {
    http.ResponseWriter
    status int
}

func (w *statusWriter) WriteHeader(code int) {
    w.status = code
    w.ResponseWriter.WriteHeader(code)
}

// requestMetricsMiddleware counts each request
func requestMetricsMiddleware(
    reqCount metric.Int64Counter,
    next http.HandlerFunc,
) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        sw := &statusWriter{ResponseWriter: w, status: http.StatusOK}

        // Call the next handler
        next(sw, r)

        // Build dynamic attributes
        attrs := []attribute.KeyValue{
            attribute.String("http.method", r.Method),
            attribute.String("http.route", r.URL.Path), // or your router's route template
            attribute.Int("http.status_code", sw.status),
        }

        // Record metrics
        ctx := r.Context()
        reqCount.Add(ctx, 1, metric.WithAttributes(attrs...))

    }
}

4. Count HTTP Request

Now we initialize the Metric provider, and create the HTTP counter. We’ll wrap our /roll handler with middleware so each request is counted.

// ...
// add complete code from above 
// ...

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

    setup, err := Configure()
    if err != nil { panic(err) }

    mp, err := setup.NewMeterProvider(ctx)
    if err != nil { panic(err) }
    defer func() {
        shCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
        defer cancel()
        _ = mp.Shutdown(shCtx)
    }()

    otel.SetMeterProvider(mp)
    meter := otel.Meter("go-otel-coralogix-example")

    // Counter: Total number of HTTP requests
    reqCounter, err := meter.Int64Counter(
        "http.server.requests",
        metric.WithDescription("Total number of HTTP requests"),
    )
    if err != nil { log.Fatalf("failed to create request counter: %v", err) }

    http.HandleFunc("/roll", requestMetricsMiddleware(reqCounter, rollHandler))

    log.Println("serving on :8080/roll")
    if err := http.ListenAndServe(":8080", nil); err != nil {
        log.Fatal(err)
    }
}

After a successful run, make a few requests to the /roll endpoint to generate metrics.

curl http://localhost:8080/roll

You can view your metrics in the Coralogix dashboard.

Go OpenTelemetry Metric Provider

Conslusion

In this tutorial, we demonstrated how to instrument Go applications with OpenTelemetry and send logs, traces, and metrics to Coralogix. Using the shared setup and simple examples, we saw how each signal works and appears in the Coralogix dashboards.

These examples are starting points. Real-world services will likely need richer attributes, error handling, and integration with frameworks or middleware. We encourage you to adapt and extend these patterns to match your application’s needs.

Support

Need help?

Our world-class customer success team is available 24/7 to walk you through your setup and answer any questions that may come up.

Feel free to reach out to us via our in-app chat or by sending us an email at support@coralogix.com.