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
- Coralogix domain associated with your account.
- Coralogix Send-Your-Data API key.
- Golang installed.
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
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
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.
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.
You can view your traces in the Coralogix dashboard.
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.
You can view your metrics in the Coralogix dashboard.
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.