mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-11-21 14:44:00 +00:00
6652fb630f
Updating documentation around the opentelemetry endpoint for metrics and the "How to use OpenTelemetry metrics with VictoriaMetrics" guide so that it shows not only how to directly write but also how to write to the otel collector and view metrics in vmui. --------- Signed-off-by: Zakhar Bessarab <z.bessarab@victoriametrics.com> Co-authored-by: Zakhar Bessarab <z.bessarab@victoriametrics.com>
223 lines
5.8 KiB
Text
223 lines
5.8 KiB
Text
package main
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"io"
|
|
"log"
|
|
"math/rand"
|
|
"net"
|
|
"net/http"
|
|
"os"
|
|
"os/signal"
|
|
"strconv"
|
|
"time"
|
|
|
|
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
|
|
"go.opentelemetry.io/otel"
|
|
"go.opentelemetry.io/otel/attribute"
|
|
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp"
|
|
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
|
|
"go.opentelemetry.io/otel/propagation"
|
|
otelmetric "go.opentelemetry.io/otel/metric"
|
|
"go.opentelemetry.io/otel/sdk/metric"
|
|
"go.opentelemetry.io/otel/sdk/resource"
|
|
"go.opentelemetry.io/otel/sdk/trace"
|
|
semconv "go.opentelemetry.io/otel/semconv/v1.25.0"
|
|
)
|
|
|
|
func main() {
|
|
if err := run(); err != nil {
|
|
log.Fatalln(err)
|
|
}
|
|
}
|
|
|
|
func run() (err error) {
|
|
// Handle SIGINT (CTRL+C) gracefully.
|
|
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
|
|
defer stop()
|
|
|
|
// Set up OpenTelemetry.
|
|
otelShutdown, err := setupOTelSDK(ctx)
|
|
if err != nil {
|
|
return
|
|
}
|
|
// Handle shutdown properly so nothing leaks.
|
|
defer func() {
|
|
err = errors.Join(err, otelShutdown(context.Background()))
|
|
}()
|
|
|
|
// Start HTTP server.
|
|
srv := &http.Server{
|
|
Addr: ":8080",
|
|
BaseContext: func(_ net.Listener) context.Context { return ctx },
|
|
ReadTimeout: time.Second,
|
|
WriteTimeout: 10 * time.Second,
|
|
Handler: newHTTPHandler(),
|
|
}
|
|
srvErr := make(chan error, 1)
|
|
go func() {
|
|
srvErr <- srv.ListenAndServe()
|
|
}()
|
|
|
|
// Wait for interruption.
|
|
select {
|
|
case err = <-srvErr:
|
|
// Error when starting HTTP server.
|
|
return
|
|
case <-ctx.Done():
|
|
// Wait for first CTRL+C.
|
|
// Stop receiving signal notifications as soon as possible.
|
|
stop()
|
|
}
|
|
|
|
// When Shutdown is called, ListenAndServe immediately returns ErrServerClosed.
|
|
err = srv.Shutdown(context.Background())
|
|
return
|
|
}
|
|
|
|
func newHTTPHandler() http.Handler {
|
|
mux := http.NewServeMux()
|
|
|
|
// handleFunc is a replacement for mux.HandleFunc
|
|
// which enriches the handler's HTTP instrumentation with the pattern as the http.route.
|
|
handleFunc := func(pattern string, handlerFunc func(http.ResponseWriter, *http.Request)) {
|
|
// Configure the "http.route" for the HTTP instrumentation.
|
|
handler := otelhttp.WithRouteTag(pattern, http.HandlerFunc(handlerFunc))
|
|
mux.Handle(pattern, handler)
|
|
}
|
|
|
|
// Register handlers.
|
|
handleFunc("/rolldice", rolldice)
|
|
|
|
// Add HTTP instrumentation for the whole server.
|
|
handler := otelhttp.NewHandler(mux, "/")
|
|
return handler
|
|
}
|
|
|
|
var (
|
|
tracer = otel.Tracer("rolldice")
|
|
meter = otel.Meter("rolldice")
|
|
rollCnt otelmetric.Int64Counter
|
|
)
|
|
|
|
func init() {
|
|
var err error
|
|
rollCnt, err = meter.Int64Counter("dice.rolls",
|
|
otelmetric.WithDescription("The number of rolls by roll value"),
|
|
otelmetric.WithUnit("{roll}"))
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
func rolldice(w http.ResponseWriter, r *http.Request) {
|
|
ctx, span := tracer.Start(r.Context(), "roll")
|
|
defer span.End()
|
|
|
|
roll := 1 + rand.Intn(6)
|
|
|
|
rollValueAttr := attribute.Int("roll.value", roll)
|
|
span.SetAttributes(rollValueAttr)
|
|
rollCnt.Add(ctx, 1, otelmetric.WithAttributes(rollValueAttr))
|
|
|
|
resp := strconv.Itoa(roll) + "\n"
|
|
if _, err := io.WriteString(w, resp); err != nil {
|
|
log.Printf("Write failed: %v\n", err)
|
|
}
|
|
}
|
|
|
|
// setupOTelSDK bootstraps the OpenTelemetry pipeline.
|
|
// If it does not return an error, make sure to call shutdown for proper cleanup.
|
|
func setupOTelSDK(ctx context.Context) (shutdown func(context.Context) error, err error) {
|
|
var shutdownFuncs []func(context.Context) error
|
|
|
|
// shutdown calls cleanup functions registered via shutdownFuncs.
|
|
// The errors from the calls are joined.
|
|
// Each registered cleanup will be invoked once.
|
|
shutdown = func(ctx context.Context) error {
|
|
var err error
|
|
for _, fn := range shutdownFuncs {
|
|
err = errors.Join(err, fn(ctx))
|
|
}
|
|
shutdownFuncs = nil
|
|
return err
|
|
}
|
|
|
|
// handleErr calls shutdown for cleanup and makes sure that all errors are returned.
|
|
handleErr := func(inErr error) {
|
|
err = errors.Join(inErr, shutdown(ctx))
|
|
}
|
|
|
|
// Set up propagator.
|
|
prop := newPropagator()
|
|
otel.SetTextMapPropagator(prop)
|
|
|
|
// Set up trace provider.
|
|
tracerProvider, err := newTraceProvider(ctx)
|
|
if err != nil {
|
|
handleErr(err)
|
|
return
|
|
}
|
|
shutdownFuncs = append(shutdownFuncs, tracerProvider.Shutdown)
|
|
otel.SetTracerProvider(tracerProvider)
|
|
|
|
// Set up meter provider.
|
|
meterProvider, err := newMeterProvider(ctx)
|
|
if err != nil {
|
|
handleErr(err)
|
|
return
|
|
}
|
|
shutdownFuncs = append(shutdownFuncs, meterProvider.Shutdown)
|
|
otel.SetMeterProvider(meterProvider)
|
|
|
|
return
|
|
}
|
|
|
|
func newPropagator() propagation.TextMapPropagator {
|
|
return propagation.NewCompositeTextMapPropagator(
|
|
propagation.TraceContext{},
|
|
propagation.Baggage{},
|
|
)
|
|
}
|
|
|
|
func newTraceProvider(ctx context.Context) (*trace.TracerProvider, error) {
|
|
traceExporter, err := otlptracehttp.New(ctx, otlptracehttp.WithInsecure(), otlptracehttp.WithEndpoint("localhost:4318"))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
traceProvider := trace.NewTracerProvider(
|
|
trace.WithBatcher(traceExporter,
|
|
// Default is 5s. Set to 1s for demonstrative purposes.
|
|
trace.WithBatchTimeout(time.Second)),
|
|
)
|
|
return traceProvider, nil
|
|
}
|
|
|
|
func newMeterProvider(ctx context.Context) (*metric.MeterProvider, error) {
|
|
metricExporter, err := otlpmetrichttp.New(ctx, otlpmetrichttp.WithInsecure(), otlpmetrichttp.WithEndpoint("localhost:4318"))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
//metricExporter, err := stdoutmetric.New()
|
|
//if err != nil {
|
|
// return nil, err
|
|
//}
|
|
res, err := resource.Merge(resource.Default(),
|
|
resource.NewWithAttributes(semconv.SchemaURL,
|
|
semconv.ServiceName("dice-roller"),
|
|
semconv.ServiceVersion("0.1.0"),
|
|
))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
meterProvider := metric.NewMeterProvider(
|
|
metric.WithResource(res),
|
|
metric.WithReader(metric.NewPeriodicReader(metricExporter,
|
|
// Default is 1m. Set to 3s for demonstrative purposes.
|
|
metric.WithInterval(3*time.Second))),
|
|
)
|
|
|
|
return meterProvider, nil
|
|
}
|