Files
netbird/util/wsproxy/server/metrics.go

119 lines
3.2 KiB
Go

package server
import (
"context"
log "github.com/sirupsen/logrus"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/metric"
)
// MetricsRecorder defines the interface for recording proxy metrics
type MetricsRecorder interface {
// RecordConnection records a new connection
RecordConnection(ctx context.Context)
// RecordDisconnection records a connection closing
RecordDisconnection(ctx context.Context)
// RecordBytesTransferred records bytes transferred in a direction
RecordBytesTransferred(ctx context.Context, direction string, bytes int64)
// RecordError records an error
RecordError(ctx context.Context, errorType string)
}
// NoOpMetricsRecorder is a no-op implementation that does nothing
type NoOpMetricsRecorder struct{}
func (n NoOpMetricsRecorder) RecordConnection(ctx context.Context) {
// no-op
}
func (n NoOpMetricsRecorder) RecordDisconnection(ctx context.Context) {
// no-op
}
func (n NoOpMetricsRecorder) RecordBytesTransferred(ctx context.Context, direction string, bytes int64) {
// no-op
}
func (n NoOpMetricsRecorder) RecordError(ctx context.Context, errorType string) {
// no-op
}
// Recorder implements MetricsRecorder using OpenTelemetry
type Recorder struct {
activeConnections metric.Int64UpDownCounter
bytesTransferred metric.Int64Counter
errors metric.Int64Counter
}
// NewMetricsRecorder creates a new OpenTelemetry-based metrics recorder
func NewMetricsRecorder(meter metric.Meter) (*Recorder, error) {
activeConnections, err := meter.Int64UpDownCounter(
"wsproxy_active_connections",
metric.WithDescription("Number of active WebSocket proxy connections"),
)
if err != nil {
return nil, err
}
bytesTransferred, err := meter.Int64Counter(
"wsproxy_bytes_transferred_total",
metric.WithDescription("Total bytes transferred through the proxy"),
)
if err != nil {
return nil, err
}
errors, err := meter.Int64Counter(
"wsproxy_errors_total",
metric.WithDescription("Total number of proxy errors"),
)
if err != nil {
return nil, err
}
return &Recorder{
activeConnections: activeConnections,
bytesTransferred: bytesTransferred,
errors: errors,
}, nil
}
func (o *Recorder) RecordConnection(ctx context.Context) {
o.activeConnections.Add(ctx, 1)
}
func (o *Recorder) RecordDisconnection(ctx context.Context) {
o.activeConnections.Add(ctx, -1)
}
func (o *Recorder) RecordBytesTransferred(ctx context.Context, direction string, bytes int64) {
o.bytesTransferred.Add(ctx, bytes, metric.WithAttributes(
attribute.String("direction", direction),
))
}
func (o *Recorder) RecordError(ctx context.Context, errorType string) {
o.errors.Add(ctx, 1, metric.WithAttributes(
attribute.String("error_type", errorType),
))
}
// Option defines functional options for the Proxy
type Option func(*Config)
// WithMetrics sets a custom metrics recorder
func WithMetrics(recorder MetricsRecorder) Option {
return func(c *Config) {
c.MetricsRecorder = recorder
}
}
// WithOTelMeter creates and sets an OpenTelemetry metrics recorder
func WithOTelMeter(meter metric.Meter) Option {
return func(c *Config) {
if recorder, err := NewMetricsRecorder(meter); err == nil {
c.MetricsRecorder = recorder
} else {
log.Warnf("Failed to create OTel metrics recorder: %v", err)
}
}
}