mirror of
https://github.com/hanzoai/metrics.git
synced 2026-10-30 20:03:39 +00:00
* simplify wording * update styles * remove extra info about go application details. The details are likely not needed and we didn't have details for rolling-dice app anyway. So keep it simple for consstency and brevity. * update navigation for simplicity sake * fix typos follow-up after https://github.com/VictoriaMetrics/VictoriaMetrics/commit/a77cb3eaa4f6337a80fdaeadfb27567ad47edfb8 Signed-off-by: hagen1778 <[email protected]>
190 lines
5.4 KiB
Plaintext
190 lines
5.4 KiB
Plaintext
package main
|
|
|
|
import (
|
|
"context"
|
|
"flag"
|
|
"fmt"
|
|
"net/http"
|
|
"os"
|
|
"os/signal"
|
|
"sync/atomic"
|
|
"syscall"
|
|
"time"
|
|
|
|
"go.opentelemetry.io/contrib/bridges/otelslog"
|
|
"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/log/global"
|
|
"go.opentelemetry.io/otel/metric"
|
|
sdklog "go.opentelemetry.io/otel/sdk/log"
|
|
sdkmetric "go.opentelemetry.io/otel/sdk/metric"
|
|
"go.opentelemetry.io/otel/sdk/resource"
|
|
)
|
|
|
|
var (
|
|
metricsEndpoint = flag.String("vm.endpoint", "http://localhost:8428/opentelemetry/v1/metrics", "VictoriaMetrics endpoint")
|
|
logsEndpoint = flag.String("vl.endpoint", "http://localhost:9428/insert/opentelemetry/v1/logs", "VictoriaLogs endpoint")
|
|
pushInterval = flag.Duration("vm.pushInterval", 10*time.Second, "How often to push collected metrics samples to vm.endpoint")
|
|
)
|
|
|
|
var (
|
|
logger = otelslog.NewLogger("demo-app")
|
|
)
|
|
|
|
func main() {
|
|
flag.Parse()
|
|
fmt.Println("Starting web server...")
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
mux := http.NewServeMux()
|
|
mux.HandleFunc("/api/fast", func(writer http.ResponseWriter, request *http.Request) {
|
|
logger.InfoContext(ctx, "Anonymous access to fast endpoint")
|
|
writer.WriteHeader(http.StatusOK)
|
|
writer.Write([]byte(`fast ok`))
|
|
})
|
|
mux.HandleFunc("/api/slow", func(writer http.ResponseWriter, request *http.Request) {
|
|
time.Sleep(time.Second * 2)
|
|
logger.InfoContext(ctx, "Anonymous access to slow endpoint")
|
|
writer.WriteHeader(http.StatusOK)
|
|
writer.Write([]byte(`slow ok`))
|
|
})
|
|
mw, err := newMiddleware(ctx, mux)
|
|
if err != nil {
|
|
panic(fmt.Sprintf("cannot build middleware: %q", err))
|
|
}
|
|
|
|
mustStop := make(chan os.Signal, 1)
|
|
signal.Notify(mustStop, os.Interrupt, syscall.SIGTERM)
|
|
go func() {
|
|
err := http.ListenAndServe("localhost:8081", mw)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}()
|
|
|
|
fmt.Println("web server started at http://localhost:8081")
|
|
<-mustStop
|
|
fmt.Println("receive shutdown signal, stopping webserver")
|
|
for _, shutdown := range mw.onShutdown {
|
|
if err := shutdown(ctx); err != nil {
|
|
fmt.Printf("cannot shutdown metric provider: %s", err)
|
|
}
|
|
}
|
|
fmt.Println("Done!")
|
|
}
|
|
|
|
func newMeterProvider(ctx context.Context) (*sdkmetric.MeterProvider, error) {
|
|
exporter, err := otlpmetrichttp.New(ctx, otlpmetrichttp.WithEndpointURL(*metricsEndpoint))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("cannot create otlphttp exporter: %w", err)
|
|
}
|
|
|
|
res, err := resource.New(ctx,
|
|
resource.WithAttributes(
|
|
attribute.String("job", "otlp"),
|
|
attribute.String("instance", "localhost"),
|
|
),
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("cannot create meter resource: %w", err)
|
|
}
|
|
|
|
// define histograms for measuring requests latency
|
|
expView := sdkmetric.NewView(
|
|
// classic histogram
|
|
sdkmetric.Instrument{
|
|
Name: "http.requests.latency",
|
|
Kind: sdkmetric.InstrumentKindHistogram,
|
|
},
|
|
// exponential histogram
|
|
sdkmetric.Stream{
|
|
Name: "http.requests.latency.exp",
|
|
Aggregation: sdkmetric.AggregationBase2ExponentialHistogram{
|
|
MaxSize: 160,
|
|
MaxScale: 20,
|
|
},
|
|
},
|
|
)
|
|
return sdkmetric.NewMeterProvider(
|
|
sdkmetric.WithReader(sdkmetric.NewPeriodicReader(exporter, sdkmetric.WithInterval(*pushInterval))),
|
|
sdkmetric.WithResource(res),
|
|
sdkmetric.WithView(expView),
|
|
), nil
|
|
}
|
|
|
|
func newLoggerProvider(ctx context.Context) (*sdklog.LoggerProvider, error) {
|
|
exporter, err := otlploghttp.New(ctx, otlploghttp.WithEndpointURL(*logsEndpoint))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
provider := sdklog.NewLoggerProvider(
|
|
sdklog.WithProcessor(sdklog.NewBatchProcessor(exporter)),
|
|
)
|
|
return provider, nil
|
|
}
|
|
|
|
func newMiddleware(ctx context.Context, h http.Handler) (*middleware, error) {
|
|
mw := &middleware{
|
|
ctx: ctx,
|
|
h: h,
|
|
}
|
|
|
|
lp, err := newLoggerProvider(ctx)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("cannot create logs provider: %w", err)
|
|
}
|
|
global.SetLoggerProvider(lp)
|
|
|
|
mp, err := newMeterProvider(ctx)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("cannot create metrics provider: %w", err)
|
|
}
|
|
otel.SetMeterProvider(mp)
|
|
meter := mp.Meter("")
|
|
|
|
mw.requestsLatency, err = meter.Float64Histogram("http.requests.latency")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("cannot create Float64Histogram: %w", err)
|
|
}
|
|
mw.requestsCount, err = meter.Int64Counter("http.requests")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("cannot create Int64Counter: %w", err)
|
|
}
|
|
cb := func(c context.Context, o metric.Int64Observer) error {
|
|
o.Observe(atomic.LoadInt64(&mw.activeRequests))
|
|
return nil
|
|
}
|
|
_, err = meter.Int64ObservableGauge("http.requests.active", metric.WithInt64Callback(cb))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("cannot create Int64ObservableGauge: %w", err)
|
|
}
|
|
mw.onShutdown = append(mw.onShutdown, mp.Shutdown, lp.Shutdown)
|
|
|
|
return mw, nil
|
|
}
|
|
|
|
type middleware struct {
|
|
ctx context.Context
|
|
h http.Handler
|
|
requestsCount metric.Int64Counter
|
|
requestsLatency metric.Float64Histogram
|
|
activeRequests int64
|
|
onShutdown []func(ctx context.Context) error
|
|
}
|
|
|
|
func (m *middleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
t := time.Now()
|
|
path := r.URL.Path
|
|
m.requestsCount.Add(m.ctx, 1, metric.WithAttributes(attribute.String("path", path)))
|
|
atomic.AddInt64(&m.activeRequests, 1)
|
|
defer func() {
|
|
atomic.AddInt64(&m.activeRequests, -1)
|
|
m.requestsLatency.Record(m.ctx, time.Since(t).Seconds(), metric.WithAttributes(attribute.String("path", path)))
|
|
}()
|
|
|
|
m.h.ServeHTTP(w, r)
|
|
} |