mattermost-community-enterp.../enterprise/metrics/histogram.go
Claude ec1f89217a Merge: Complete Mattermost Server with Community Enterprise
Full Mattermost server source with integrated Community Enterprise features.
Includes vendor directory for offline/air-gapped builds.

Structure:
- enterprise-impl/: Enterprise feature implementations
- enterprise-community/: Init files that register implementations
- enterprise/: Bridge imports (community_imports.go)
- vendor/: All dependencies for offline builds

Build (online):
  go build ./cmd/mattermost

Build (offline/air-gapped):
  go build -mod=vendor ./cmd/mattermost

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-17 23:59:07 +09:00

75 lines
2.1 KiB
Go

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.enterprise for license information.
package metrics
import (
"github.com/prometheus/client_golang/prometheus"
"github.com/mattermost/mattermost/server/public/shared/mlog"
)
// HistogramVec is a wrapper of prometheus.HistogramVec that stores the buckets
// which are later passed down to WrappedObserver.
type HistogramVec struct {
*prometheus.HistogramVec
buckets []float64
fqName string
logger mlog.LoggerIFace
}
// WrappedObserver is a wrapper of prometheus.Observer which addtionally
// logs userIDs when the value exceeds or equals to the one in the highest bucket.
type WrappedObserver struct {
prometheus.Observer
labels prometheus.Labels
buckets []float64
logger mlog.LoggerIFace
metricName string
userID string
}
// NewHistogramVec returns a custom-purpose HistogramVec.
func NewHistogramVec(opts prometheus.HistogramOpts, labelNames []string, logger mlog.LoggerIFace) *HistogramVec {
if len(opts.Buckets) == 0 {
opts.Buckets = prometheus.DefBuckets
}
return &HistogramVec{
HistogramVec: prometheus.NewHistogramVec(opts, labelNames),
buckets: opts.Buckets,
fqName: prometheus.BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
logger: logger,
}
}
func (v *HistogramVec) With(labels prometheus.Labels, userID string) prometheus.Observer {
h, err := v.GetMetricWith(labels)
if err != nil {
panic(err)
}
return &WrappedObserver{
Observer: h,
labels: labels,
buckets: v.buckets,
logger: v.logger,
metricName: v.fqName,
userID: userID,
}
}
func (o *WrappedObserver) Observe(v float64) {
if v >= o.buckets[len(o.buckets)-1] {
fields := []mlog.Field{
mlog.String("metric_name", o.metricName),
mlog.String("user_id", o.userID),
mlog.Float("observed_value", v),
mlog.Float("highest_bucket_value", o.buckets[len(o.buckets)-1]),
}
for k, v := range o.labels {
fields = append(fields, mlog.String(k, v))
}
o.logger.Warn("Metric observation exceeded.", fields...)
}
o.Observer.Observe(v)
}