Merge pull request #51 from hobbyistlabs-coder/feature/opentelemetry-metrics-integration-3940720966979177033

feat: integrate OpenTelemetry metrics for system health visibility
This commit is contained in:
hobbyistlabs-coder 2026-03-18 04:34:37 -04:00 committed by GitHub
commit c4aa14f0cb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 81 additions and 1 deletions

5
go.mod
View file

@ -30,6 +30,8 @@ require (
github.com/tencent-connect/botgo v0.2.1 github.com/tencent-connect/botgo v0.2.1
github.com/traefik/yaegi v0.16.1 github.com/traefik/yaegi v0.16.1
go.mau.fi/whatsmeow v0.0.0-20260219150138-7ae702b1eed4 go.mau.fi/whatsmeow v0.0.0-20260219150138-7ae702b1eed4
go.opentelemetry.io/otel v1.29.0
go.opentelemetry.io/otel/metric v1.29.0
golang.org/x/oauth2 v0.35.0 golang.org/x/oauth2 v0.35.0
golang.org/x/time v0.14.0 golang.org/x/time v0.14.0
google.golang.org/protobuf v1.36.11 google.golang.org/protobuf v1.36.11
@ -49,6 +51,8 @@ require (
github.com/elliotchance/orderedmap/v3 v3.1.0 // indirect github.com/elliotchance/orderedmap/v3 v3.1.0 // indirect
github.com/gdamore/encoding v1.0.1 // indirect github.com/gdamore/encoding v1.0.1 // indirect
github.com/go-jose/go-jose/v3 v3.0.4 // indirect github.com/go-jose/go-jose/v3 v3.0.4 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-stack/stack v1.8.1 // indirect github.com/go-stack/stack v1.8.1 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/josharian/intern v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect
@ -68,6 +72,7 @@ require (
github.com/vektah/gqlparser/v2 v2.5.27 // indirect github.com/vektah/gqlparser/v2 v2.5.27 // indirect
go.mau.fi/libsignal v0.2.1 // indirect go.mau.fi/libsignal v0.2.1 // indirect
go.mau.fi/util v0.9.6 // indirect go.mau.fi/util v0.9.6 // indirect
go.opentelemetry.io/otel/trace v1.29.0 // indirect
golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a // indirect golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a // indirect
golang.org/x/term v0.40.0 // indirect golang.org/x/term v0.40.0 // indirect
golang.org/x/text v0.34.0 // indirect golang.org/x/text v0.34.0 // indirect

View file

@ -6,6 +6,8 @@ import (
"sync" "sync"
"time" "time"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/metric"
"jane/pkg/logger" "jane/pkg/logger"
) )
@ -16,6 +18,12 @@ type ResourceTracker struct {
interval time.Duration interval time.Duration
stopCh chan struct{} stopCh chan struct{}
stopOnce sync.Once stopOnce sync.Once
// OpenTelemetry gauges
goroutinesGauge metric.Int64ObservableGauge
allocMBGauge metric.Float64ObservableGauge
totalMBGauge metric.Float64ObservableGauge
sysMBGauge metric.Float64ObservableGauge
} }
// NewResourceTracker creates a new ResourceTracker that logs metrics every `interval`. // NewResourceTracker creates a new ResourceTracker that logs metrics every `interval`.
@ -23,10 +31,77 @@ func NewResourceTracker(interval time.Duration) *ResourceTracker {
if interval == 0 { if interval == 0 {
interval = 60 * time.Second // Default to 1 minute interval = 60 * time.Second // Default to 1 minute
} }
return &ResourceTracker{
rt := &ResourceTracker{
interval: interval, interval: interval,
stopCh: make(chan struct{}), stopCh: make(chan struct{}),
} }
meter := otel.Meter("jane/pkg/health")
var err error
rt.goroutinesGauge, err = meter.Int64ObservableGauge(
"sys.goroutines",
metric.WithDescription("Number of active goroutines"),
)
if err != nil {
logger.ErrorCF("SystemHealth", "Failed to create goroutines gauge", map[string]any{"error": err.Error()})
}
rt.allocMBGauge, err = meter.Float64ObservableGauge(
"sys.memory.alloc_mb",
metric.WithDescription("Memory allocated and still in use, in MB"),
)
if err != nil {
logger.ErrorCF("SystemHealth", "Failed to create memory alloc gauge", map[string]any{"error": err.Error()})
}
rt.totalMBGauge, err = meter.Float64ObservableGauge(
"sys.memory.total_alloc_mb",
metric.WithDescription("Total memory allocated (even if freed), in MB"),
)
if err != nil {
logger.ErrorCF("SystemHealth", "Failed to create total memory alloc gauge", map[string]any{"error": err.Error()})
}
rt.sysMBGauge, err = meter.Float64ObservableGauge(
"sys.memory.sys_mb",
metric.WithDescription("Total memory obtained from the OS, in MB"),
)
if err != nil {
logger.ErrorCF("SystemHealth", "Failed to create memory sys gauge", map[string]any{"error": err.Error()})
}
if _, err := meter.RegisterCallback(rt.observeMetrics, rt.goroutinesGauge, rt.allocMBGauge, rt.totalMBGauge, rt.sysMBGauge); err != nil {
logger.ErrorCF("SystemHealth", "Failed to register metrics callback", map[string]any{"error": err.Error()})
}
return rt
}
func (rt *ResourceTracker) observeMetrics(_ context.Context, o metric.Observer) error {
var m runtime.MemStats
runtime.ReadMemStats(&m)
goroutines := runtime.NumGoroutine()
allocMB := float64(m.Alloc) / 1024 / 1024
totalAllocMB := float64(m.TotalAlloc) / 1024 / 1024
sysMB := float64(m.Sys) / 1024 / 1024
if rt.goroutinesGauge != nil {
o.ObserveInt64(rt.goroutinesGauge, int64(goroutines))
}
if rt.allocMBGauge != nil {
o.ObserveFloat64(rt.allocMBGauge, allocMB)
}
if rt.totalMBGauge != nil {
o.ObserveFloat64(rt.totalMBGauge, totalAllocMB)
}
if rt.sysMBGauge != nil {
o.ObserveFloat64(rt.sysMBGauge, sysMB)
}
return nil
} }
// Start begins tracking resources in a background goroutine. // Start begins tracking resources in a background goroutine.