mirror of
https://github.com/hanzoai/metrics.git
synced 2026-10-30 20:03:39 +00:00
This is part of the effort to upgrate and validate the [Guides in the docs](https://docs.victoriametrics.com/guides/). Doc page: https://docs.victoriametrics.com/guides/getting-started-with-opentelemetry/ Functionally, nothing should change. Aside from the fix that prevented one of the example applications to run, the rest of the commands in the guide should be equivalent to the original. Header anchor links do not change with this update. I added a few headers but the existing headers anchors should remain unchanged to prevent breaking existing links. - Tested on a more modern version of GKE to validate it still works OK (1.34.1-gke.3971001) - Changed wording of some sections to improve flow and readability - Added some missing steps/troubleshooting - Add tips annotations for cardinality explorer and setup references to make them stand apart form the main content - Use `kubectl port-forward svc/...` instead of `kubecl port-forward pod` (service selectors vs pod names) in some test commands to make instructions simpler - Updated OpenTelemetry version to fix error that prevented `app.go-collector.example` sample code from running - Replaced the "Visit these links" part in the second program (with the fast/slow endpoints) with curl commands - Updated the first VMUI test link to show table instead of graph while testing OpenTelemetry ingestion (default graph view can be confusing as there metric value for `k8s_container_ready` doesn't really show any values) - Minor typos, grammar check, and consistency (Kubernetes vs kubernetes, Helm vs Helm, Collector vs collector, etc)
258 lines
6.7 KiB
Plaintext
258 lines
6.7 KiB
Plaintext
package main
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"io"
|
|
"log"
|
|
"math/rand"
|
|
"net"
|
|
"net/http"
|
|
"os"
|
|
"os/signal"
|
|
"strconv"
|
|
"time"
|
|
|
|
"go.opentelemetry.io/contrib/bridges/otelslog"
|
|
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
|
|
"go.opentelemetry.io/otel"
|
|
"go.opentelemetry.io/otel/attribute"
|
|
"go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp"
|
|
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp"
|
|
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
|
|
"go.opentelemetry.io/otel/log/global"
|
|
"go.opentelemetry.io/otel/metric"
|
|
"go.opentelemetry.io/otel/propagation"
|
|
sdklog "go.opentelemetry.io/otel/sdk/log"
|
|
sdkmetric "go.opentelemetry.io/otel/sdk/metric"
|
|
"go.opentelemetry.io/otel/sdk/metric/metricdata"
|
|
"go.opentelemetry.io/otel/sdk/resource"
|
|
"go.opentelemetry.io/otel/sdk/trace"
|
|
semconv "go.opentelemetry.io/otel/semconv/v1.37.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")
|
|
logger = otelslog.NewLogger("rolldice")
|
|
rollCnt metric.Int64Counter
|
|
)
|
|
|
|
func init() {
|
|
var err error
|
|
rollCnt, err = meter.Int64Counter("dice.rolls",
|
|
metric.WithDescription("The number of rolls by roll value"),
|
|
metric.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, metric.WithAttributes(rollValueAttr))
|
|
logger.InfoContext(ctx, "Anonymous player is rolling the dice", "result", roll)
|
|
|
|
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)
|
|
|
|
// Set up log provider.
|
|
logProvider, err := newLoggerProvider(ctx)
|
|
if err != nil {
|
|
handleErr(err)
|
|
return
|
|
}
|
|
global.SetLoggerProvider(logProvider)
|
|
shutdownFuncs = append(shutdownFuncs, logProvider.Shutdown)
|
|
|
|
return
|
|
}
|
|
|
|
func newPropagator() propagation.TextMapPropagator {
|
|
return propagation.NewCompositeTextMapPropagator(
|
|
propagation.TraceContext{},
|
|
propagation.Baggage{},
|
|
)
|
|
}
|
|
|
|
func newTraceProvider(ctx context.Context) (*trace.TracerProvider, error) {
|
|
exporter, err := otlptracehttp.New(ctx, otlptracehttp.WithInsecure(), otlptracehttp.WithEndpoint("localhost:4318"))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
provider := trace.NewTracerProvider(
|
|
trace.WithBatcher(exporter,
|
|
// Default is 5s. Set to 1s for demonstrative purposes.
|
|
trace.WithBatchTimeout(time.Second)),
|
|
)
|
|
return provider, nil
|
|
}
|
|
|
|
func newMeterProvider(ctx context.Context) (*sdkmetric.MeterProvider, error) {
|
|
exporter, err := otlpmetrichttp.New(ctx,
|
|
otlpmetrichttp.WithInsecure(),
|
|
otlpmetrichttp.WithEndpoint("localhost:4318"),
|
|
otlpmetrichttp.WithTemporalitySelector(
|
|
func(kind sdkmetric.InstrumentKind) metricdata.Temporality {
|
|
return metricdata.DeltaTemporality
|
|
},
|
|
),
|
|
)
|
|
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
|
|
}
|
|
provider := sdkmetric.NewMeterProvider(
|
|
sdkmetric.WithResource(res),
|
|
sdkmetric.WithReader(
|
|
sdkmetric.NewPeriodicReader(
|
|
exporter,
|
|
sdkmetric.WithInterval(3*time.Second),
|
|
),
|
|
),
|
|
)
|
|
|
|
return provider, nil
|
|
}
|
|
|
|
func newLoggerProvider(ctx context.Context) (*sdklog.LoggerProvider, error) {
|
|
exporter, err := otlploghttp.New(ctx, otlploghttp.WithInsecure(), otlploghttp.WithEndpoint("localhost:4318"))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
provider := sdklog.NewLoggerProvider(
|
|
sdklog.WithProcessor(sdklog.NewBatchProcessor(exporter)),
|
|
)
|
|
return provider, nil
|
|
}
|