OpenTelemetry Instrumentation for GO
Overview
OpenTelemetry provides instrumentation for GO applications, enabling you to collect telemetry data such as traces and metrics. Although Lumigo does not offer its own OpenTelemetry Distribution for GO, you can still configure it. This guide explains how to set up OpenTelemetry GO Instrumentation and configure it to report data to Lumigo.
Prerequisites
Before you begin, ensure you have the following:
- Go 1.18+
- OpenTelemetry-Go SDK
- Logrus (for logging)
Installation Steps
1. Initialize the OTLP Log Exporter
To send logs to Lumigo, we use an OpenTelemetry Protocol (OTLP) HTTP log exporter. This exporter will transmit logs to Lumigo's designated endpoint using your Lumigo token.
The key configuration parameters for the exporter are:
- Endpoint: The Lumigo OTLP endpoint is
ga-otlp.lumigo-tracer-edge.golumigo.com
. This is where logs are sent. - Authentication: The exporter requires your Lumigo token for authentication. This token should be added to the HTTP headers as
Authorization: LumigoToken <your_token>
.
Use the OpenTelemetry Protocol (OTLP) HTTP log exporter to send logs to Lumigo:
func initLogExporter() {
exporter, err := otlploghttp.New(context.Background(),
otlploghttp.WithEndpoint("ga-otlp.lumigo-tracer-edge.golumigo.com"),
otlploghttp.WithURLPath("/v1/logs"),
otlploghttp.WithHeaders(map[string]string{
"Authorization": "LumigoToken t_xxxxxxxxxxxxxxxx", // Replace with your Lumigo token
}),
)
if err != nil {
log.Fatalf("Failed to create log exporter: %v", err)
}
}
This exporter sends logs directly to the Lumigo OTLP endpoint. It attaches an authentication token to every request, allowing Lumigo to ingest and analyze your logs.
2. Set Up the Log Provider
The Log Provider in OpenTelemetry is responsible for managing the lifecycle of the loggers and ensuring logs are processed and exported properly.
In this example, we configure the log provider with:
- Batch Processor: This sends logs in batches to improve performance.
- Resource: This defines the attributes for your application, such as the service name (
"my-webserver"
in this example). These attributes are attached to all logs sent to Lumigo, providing context about where the logs originated from.
Here’s how to set up the log provider:
func initLogExporter() {
logProvider := sdklog.NewLoggerProvider(
sdklog.WithProcessor(sdklog.NewBatchProcessor(exporter)), // Send logs in batches
)
}
The log provider manages the creation of loggers, and its job is to ensure that the logs you create with Logrus, or any other logging library, are correctly processed and sent to Lumigo through the exporter.
3. Logging in Your Application
Once the log exporter and provider are set up, you can integrate logging into your application using the logrus
package. The example below shows how to log incoming HTTP requests:
func helloHandler(w http.ResponseWriter, r *http.Request) {
logger.WithFields(logrus.Fields{
"method": r.Method,
"path": r.URL.Path,
}).Info("Request received")
w.Write([]byte("Hello, World!"))
}
func main() {
// Initialize OTLP Log Exporter
initLogExporter()
// Setup HTTP server
http.HandleFunc("/", helloHandler)
port := ":8080"
logger.WithField("port", port).Info("Starting server")
if err := http.ListenAndServe(port, nil); err != nil {
logger.WithError(err).Fatal("Server failed to start")
}
}
Full example
Here is a full example of a webserver sending logs to Lumigo:
package main
import (
"context"
"log"
"net/http"
"os"
"github.com/sirupsen/logrus"
"go.opentelemetry.io/contrib/bridges/otellogrus"
"go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp"
sdklog "go.opentelemetry.io/otel/sdk/log"
"go.opentelemetry.io/otel/sdk/resource"
semconv "go.opentelemetry.io/otel/semconv/v1.17.0"
)
var logger *logrus.Logger
func initLogExporter() {
// Define the service name and create a new resource for OTLP
resources, err := resource.New(
context.Background(),
resource.WithAttributes(
semconv.ServiceNameKey.String("my-webserver"),
),
)
if err != nil {
log.Fatalf("Failed to create resource: %v", err)
}
// Create a new OTLP log exporter that sends logs to Lumigo
exporter, err := otlploghttp.New(context.Background(),
otlploghttp.WithEndpoint("ga-otlp.lumigo-tracer-edge.golumigo.com"),
otlploghttp.WithURLPath("/v1/logs"),
otlploghttp.WithHeaders(map[string]string{
"Authorization": "LumigoToken t_xxxxxxxxxxxxxxxx",
}),
)
if err != nil {
log.Fatalf("Failed to create log exporter: %v", err)
}
// Create a new logger provider with the OTLP log exporter and resource
logProvider := sdklog.NewLoggerProvider(
sdklog.WithProcessor(sdklog.NewBatchProcessor(exporter)),
sdklog.WithResource(resources),
)
// Create a new logrus logger and add the OTLP log exporter as a hook
logger = logrus.New()
logger.AddHook(otellogrus.NewHook("test",
otellogrus.WithLoggerProvider(logProvider),
otellogrus.WithLevels(
[]logrus.Level{logrus.InfoLevel},
),
))
// Some formatting for the logrus logger - this is optional
logger.SetFormatter(&logrus.JSONFormatter{})
logger.SetOutput(os.Stdout)
logger.SetLevel(logrus.InfoLevel)
logger.Info("Logger started")
}
func helloHandler(w http.ResponseWriter, r *http.Request) {
logger.WithFields(logrus.Fields{
"method": r.Method,
"path": r.URL.Path,
}).Info("Request received")
w.Write([]byte("Hello, World!"))
}
func main() {
initLogExporter()
http.HandleFunc("/", helloHandler)
port := ":8080"
logger.WithField("port", port).Info("Starting server")
if err := http.ListenAndServe(port, nil); err != nil {
logger.WithError(err).Fatal("Server failed to start")
}
}
Traces
Once logging using logrus
is integrated, you can also integrate distributed tracing using Lumigo and OpenTelemetry. To initialize Lumigo Tracing:
import (
"fmt"
"log"
"context"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
"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.17.0"
"go.opentelemetry.io/otel/trace"
)
var tracer trace.Tracer
func Resource() *resource.Resource {
return resource.NewWithAttributes(
semconv.SchemaURL,
semconv.ServiceName("yourAppName"),
)
}
func InitiateLumigoTracer(ctx context.Context) (func(context.Context) error, error) {
options := []otlptracehttp.Option{
otlptracehttp.WithEndpoint("ga-otlp.lumigo-tracer-edge.golumigo.com"),
otlptracehttp.WithHeaders(map[string]string{"Authorization": "LumigoToken <yourLumigoToken>}),
}
client := otlptracehttp.NewClient(options...)
exporter, err := otlptrace.New(ctx, client)
if err != nil {
return nil, fmt.Errorf("creating stdout exporter: %w", err)
}
tracerProvider := sdktrace.NewTracerProvider(
sdktrace.WithBatcher(exporter),
sdktrace.WithResource(Resource()),
)
otel.SetTracerProvider(tracerProvider)
tracer = tracerProvider.Tracer("tracer")
return tracerProvider.Shutdown, nil
}
func main() {
ctx := context.Background()
// Registers a tracer Provider globally.
_, err := InitiateLumigoTracer(ctx)
if err != nil {
panic(err.Error())
}
To manually create a span in your function:
_, span := tracer.Start(r.Context(), "hello-span")
defer span.End()
Updated about 16 hours ago