Documentation
¶
Overview ¶
Package metrics decorates any r3.CRUD[T, ID] with domain-level analytics, recording configurable metrics for every CRUD operation. Metric records are themselves r3 entities, persisted via any r3.CRUD[MetricRecord, string] - the same or a different backend than the wrapped entity uses.
Ten built-in collectors cover common CRUD analytics (action counts, latency, popularity, error rates, filter analysis, field changes, lifecycle, patch size); the Collector[T, ID] interface adds custom ones. Labels come in three layers - core (auto), context, entity - and records are time-bucketed (minutely to monthly) for cheap aggregation. Async mode keeps the hot path latency-free; r3.Actor is picked up from context for attribution.
Aggregation ¶
The Aggregator aggregates in-memory, or delegates to server-side aggregation when the store also implements AggregationPusher.
Retention and Rollup ¶
RetentionEnforcer deletes old records by age (MaxAge) or count (MaxRecords). RollupExecutor compacts old fine-grained records into coarser time-bucket summaries, preserving label dimensions and tagging summaries with _rollup metadata labels.
Both delete one record at a time via r3.CRUD Delete (slow at scale; a future BatchDelete could optimize this). RollupExecutor compacts all old records for a type regardless of source granularity; RollupPolicy.SourceBucket is provenance metadata only - filter at the query level to target one granularity. The ticker in RetentionEnforcer.Start (and any rollup loop) first fires after one interval elapses, not immediately.
Usage ¶
metricsStore := r3pgx.NewPgxCRUD[metrics.MetricRecord, string](db)
repo := metrics.WithMetrics[Order, int64](
innerRepo,
metricsStore,
metrics.WithIDFunc[Order, int64](func(o Order) int64 { return o.ID }),
metrics.WithCollectors[Order, int64](
metrics.CRUDActionCollector[Order, int64](),
metrics.PopularityCollector[Order, int64](),
metrics.LatencyCollector[Order, int64](),
),
metrics.WithLabelers[Order, int64](
func(o Order) metrics.Labels { return metrics.Labels{"order_type": o.Type} },
),
metrics.WithAsync[Order, int64](),
)
// Query metrics:
agg := metrics.NewAggregator(metricsStore)
count, _ := agg.Count(ctx, "orders", "crud.action", metrics.LastMonth(),
metrics.WithLabel("operation", "create"))
top, _ := agg.TopN(ctx, "orders", "entity.popularity", metrics.LastWeek(), 10)
Index ¶
- Constants
- Variables
- func ComputeBucket(t time.Time, size BucketSize) string
- func QueryByBucket(recordType, metricName, bucket string) r3.Query
- func QueryByEntity(recordType, recordID string, tr TimeRange) r3.Query
- func QueryByEntityMetric(recordType, recordID, metricName string, tr TimeRange) r3.Query
- func QueryByMetric(recordType, metricName string, tr TimeRange) r3.Query
- func QueryByType(recordType string, tr TimeRange) r3.Query
- type AggregationPusher
- type Aggregator
- func (a *Aggregator) Avg(ctx context.Context, recordType, metricName string, tr TimeRange, ...) (float64, error)
- func (a *Aggregator) Count(ctx context.Context, recordType, metricName string, tr TimeRange, ...) (int64, error)
- func (a *Aggregator) GroupBy(ctx context.Context, recordType, metricName string, tr TimeRange, ...) (map[string]float64, error)
- func (a *Aggregator) Sum(ctx context.Context, recordType, metricName string, tr TimeRange, ...) (float64, error)
- func (a *Aggregator) TimeSeries(ctx context.Context, recordType, metricName string, tr TimeRange, ...) ([]BucketValue, error)
- func (a *Aggregator) TopN(ctx context.Context, recordType, metricName string, tr TimeRange, n int, ...) ([]RankedEntity, error)
- type BucketSize
- type BucketValue
- type CRUD
- func (m *CRUD[T, ID]) Aggregate(ctx context.Context, qarg ...r3.Query) ([]r3.AggregateRow, error)
- func (m *CRUD[T, ID]) AggregateThroughRelation(ctx context.Context, relation string, qarg ...r3.Query) ([]r3.AggregateRow, error)
- func (m *CRUD[T, ID]) Count(ctx context.Context, qarg ...r3.Query) (int64, error)
- func (m *CRUD[T, ID]) Create(ctx context.Context, entity T) (T, error)
- func (m *CRUD[T, ID]) Delete(ctx context.Context, id ID) error
- func (m *CRUD[T, ID]) Get(ctx context.Context, id ID, qarg ...r3.Query) (T, error)
- func (m *CRUD[T, ID]) Inner() r3.CRUD[T, ID]
- func (m *CRUD[T, ID]) List(ctx context.Context, qarg ...r3.Query) ([]T, int64, error)
- func (m *CRUD[T, ID]) Metrics() r3.CRUD[MetricRecord, string]
- func (m *CRUD[T, ID]) Patch(ctx context.Context, entity T, fields r3.Fields) (T, error)
- func (m *CRUD[T, ID]) PatchWhere(ctx context.Context, filters r3.Filters, entity T, fields r3.Fields) (int64, error)
- func (m *CRUD[T, ID]) Rewrap(inner r3.CRUD[T, ID]) r3.CRUD[T, ID]
- func (m *CRUD[T, ID]) Unwrap() r3.CRUD[T, ID]
- func (m *CRUD[T, ID]) Update(ctx context.Context, entity T) (T, error)
- func (m *CRUD[T, ID]) Upsert(ctx context.Context, entity T, opts ...r3.UpsertOption) (T, error)
- type Collector
- func CRUDActionCollector[T any, ID comparable]() Collector[T, ID]
- func ErrorCollector[T any, ID comparable]() Collector[T, ID]
- func FieldChangeCollector[T any, ID comparable]() Collector[T, ID]
- func LatencyCollector[T any, ID comparable]() Collector[T, ID]
- func LifecycleCollector[T any, ID comparable](createdAtFunc func(T) time.Time) Collector[T, ID]
- func ListFilterCollector[T any, ID comparable]() Collector[T, ID]
- func ListResultSizeCollector[T any, ID comparable]() Collector[T, ID]
- func ListTotalCountCollector[T any, ID comparable]() Collector[T, ID]
- func PatchSizeCollector[T any, ID comparable]() Collector[T, ID]
- func PopularityCollector[T any, ID comparable]() Collector[T, ID]
- type CollectorFunc
- type ContextLabeler
- type IDFunc
- type Labeler
- type Labels
- type MetricEntry
- type MetricRecord
- type Operation
- type OperationContext
- type Option
- func WithAsync[T any, ID comparable]() Option[T, ID]
- func WithBucketSize[T any, ID comparable](size BucketSize) Option[T, ID]
- func WithCollectors[T any, ID comparable](collectors ...Collector[T, ID]) Option[T, ID]
- func WithContextLabelers[T any, ID comparable](labelers ...ContextLabeler) Option[T, ID]
- func WithErrorHandler[T any, ID comparable](fn func(error)) Option[T, ID]
- func WithIDFunc[T any, ID comparable](fn IDFunc[T, ID]) Option[T, ID]
- func WithLabelers[T any, ID comparable](labelers ...Labeler[T]) Option[T, ID]
- func WithRecordType[T any, ID comparable](name string) Option[T, ID]
- type Options
- type QueryOption
- type RankedEntity
- type RetentionEnforcer
- type RetentionPolicy
- type RollupExecutor
- type RollupPolicy
- type TimeRange
Constants ¶
const ( // MetricEntityFieldChange tracks which fields change most often on updates. MetricEntityFieldChange = "entity.field_change" // MetricEntityLifecycle tracks the duration from creation to deletion. MetricEntityLifecycle = "entity.lifecycle" // MetricEntityPatchSize tracks how many fields are patched at once. MetricEntityPatchSize = "entity.patch_size" )
Metric names for entity-level collectors.
const ( // MetricListFilter tracks which fields users filter by in List queries. MetricListFilter = "crud.list.filter" // MetricListResultSize tracks how many results each List query returns. MetricListResultSize = "crud.list.result_size" // MetricListTotalCount tracks the total matching count from List queries (before pagination). MetricListTotalCount = "crud.list.total_count" )
Metric names for list-related collectors.
const MetricCRUDAction = "crud.action"
MetricCRUDAction is the metric name for the universal CRUD action counter, incremented by 1 on every operation. The "operation" core label distinguishes create/get/list/update/patch/delete.
const MetricCRUDError = "crud.error"
MetricCRUDError is the metric name for failed operation tracking.
const MetricCRUDLatency = "crud.action.latency"
MetricCRUDLatency is the metric name for operation duration; value is milliseconds.
const MetricEntityPopularity = "entity.popularity"
MetricEntityPopularity is the metric name for entity access scoring. Incremented when an entity is accessed via Get or appears in List results.
Variables ¶
var ( // ErrNoMetrics is returned when querying metrics for an entity with no recorded data. ErrNoMetrics = errors.New("r3metrics: no metrics found") // ErrIDFuncRequired is returned when a collector requires IDFunc but it's not set. ErrIDFuncRequired = errors.New("r3metrics: IDFunc is required for entity-level metrics; use WithIDFunc option") )
Sentinel errors for metrics operations.
Functions ¶
func ComputeBucket ¶
func ComputeBucket(t time.Time, size BucketSize) string
ComputeBucket truncates t to the bucket boundary and returns it as RFC3339. Truncation is delegated to the years library so boundary semantics stay consistent across the codebase.
func QueryByBucket ¶
QueryByBucket retrieves metrics for a specific time bucket.
func QueryByEntity ¶
QueryByEntity retrieves all metrics for a specific entity instance.
func QueryByEntityMetric ¶
QueryByEntityMetric builds a Query for a specific metric on a specific entity.
func QueryByMetric ¶
QueryByMetric retrieves a specific metric across an entity type.
Types ¶
type AggregationPusher ¶
type AggregationPusher interface {
// PushCount returns the count of matching records. A non-nil labels filters
// to records carrying those labels.
PushCount(ctx context.Context, recordType, metricName string, tr TimeRange, labels Labels) (int64, error)
// PushSum returns the sum of metric values matching the given criteria.
PushSum(ctx context.Context, recordType, metricName string, tr TimeRange, labels Labels) (float64, error)
// PushAvg returns the average of metric values matching the given criteria.
PushAvg(ctx context.Context, recordType, metricName string, tr TimeRange, labels Labels) (float64, error)
// PushTopN returns the top N entities by summed metric value.
PushTopN(
ctx context.Context,
recordType, metricName string,
tr TimeRange,
n int,
labels Labels,
) ([]RankedEntity, error)
// PushTimeSeries returns metric values grouped by time bucket.
PushTimeSeries(
ctx context.Context,
recordType, metricName string,
tr TimeRange,
bucket BucketSize,
labels Labels,
) ([]BucketValue, error)
// PushGroupBy returns metric values grouped by a label key.
PushGroupBy(
ctx context.Context,
recordType, metricName string,
tr TimeRange,
labelKey string,
labels Labels,
) (map[string]float64, error)
}
AggregationPusher is an optional interface a metric store implements to push aggregation server-side instead of fetching all records and computing in Go. If a store implements it, Aggregator delegates to these methods; otherwise it falls back to in-memory aggregation, so this is pure opt-in.
type Aggregator ¶
type Aggregator struct {
// contains filtered or unexported fields
}
Aggregator computes metric aggregations over any r3.Querier for MetricRecord (read access only). It aggregates in-memory unless the store implements AggregationPusher, in which case it delegates server-side.
func NewAggregator ¶
func NewAggregator(store r3.Querier[MetricRecord, string]) *Aggregator
NewAggregator creates an Aggregator over the given metric store, enabling server-side aggregation automatically if the store implements AggregationPusher.
func (*Aggregator) Avg ¶
func (a *Aggregator) Avg( ctx context.Context, recordType, metricName string, tr TimeRange, opts ...QueryOption, ) (float64, error)
Avg returns the average metric value matching the query.
agg.Avg(ctx, "users", "crud.action.latency", LastWeek(), WithLabel("operation", "create"))
func (*Aggregator) Count ¶
func (a *Aggregator) Count( ctx context.Context, recordType, metricName string, tr TimeRange, opts ...QueryOption, ) (int64, error)
Count returns the number of metric records matching the query. For counter metrics (value=1) this equals the sum.
agg.Count(ctx, "users", "crud.action", LastMonth(), WithLabel("operation", "create"))
func (*Aggregator) GroupBy ¶
func (a *Aggregator) GroupBy( ctx context.Context, recordType, metricName string, tr TimeRange, labelKey string, opts ...QueryOption, ) (map[string]float64, error)
GroupBy returns metric values grouped by a label key.
agg.GroupBy(ctx, "orders", "crud.action", LastMonth(), "operation")
// -> {"create": 1247, "get": 8391, "list": 2100, ...}
func (*Aggregator) Sum ¶
func (a *Aggregator) Sum( ctx context.Context, recordType, metricName string, tr TimeRange, opts ...QueryOption, ) (float64, error)
Sum returns the sum of metric values matching the query.
agg.Sum(ctx, "users", "crud.action.latency", LastWeek(), WithLabel("operation", "list"))
func (*Aggregator) TimeSeries ¶
func (a *Aggregator) TimeSeries( ctx context.Context, recordType, metricName string, tr TimeRange, bucket BucketSize, opts ...QueryOption, ) ([]BucketValue, error)
TimeSeries returns metric values grouped by time bucket.
agg.TimeSeries(ctx, "users", "crud.action", Last30Days(), BucketDaily, WithLabel("operation", "create"))
func (*Aggregator) TopN ¶
func (a *Aggregator) TopN( ctx context.Context, recordType, metricName string, tr TimeRange, n int, opts ...QueryOption, ) ([]RankedEntity, error)
TopN returns the N entities with the highest summed metric value.
agg.TopN(ctx, "users", "entity.popularity", LastMonth(), 10)
type BucketSize ¶
type BucketSize string
BucketSize defines the granularity of time-based metric bucketing.
const ( BucketMinutely BucketSize = "minutely" BucketHourly BucketSize = "hourly" BucketDaily BucketSize = "daily" BucketWeekly BucketSize = "weekly" BucketMonthly BucketSize = "monthly" )
type BucketValue ¶
BucketValue is a time bucket with its aggregated metric value.
type CRUD ¶
type CRUD[T any, ID comparable] struct { // contains filtered or unexported fields }
CRUD records metrics for every operation via configurable collectors, storing them in any r3.CRUD[MetricRecord, string]. See the package doc for details.
func WithMetrics ¶
func WithMetrics[T any, ID comparable]( inner r3.CRUD[T, ID], store r3.CRUD[MetricRecord, string], optFns ...Option[T, ID], ) *CRUD[T, ID]
WithMetrics wraps inner with metrics collection persisted to store.
func (*CRUD[T, ID]) Aggregate ¶ added in v0.0.3
Aggregate computes grouped aggregates and records metrics (OpAggregate, with TotalCount = number of grouped rows returned). An inner repo without aggregation support yields r3.ErrAggregateNotSupported.
func (*CRUD[T, ID]) AggregateThroughRelation ¶ added in v0.0.5
func (m *CRUD[T, ID]) AggregateThroughRelation( ctx context.Context, relation string, qarg ...r3.Query, ) ([]r3.AggregateRow, error)
AggregateThroughRelation aggregates related rows and records metrics (OpAggregate, with TotalCount = number of grouped rows returned). An inner repo without relation-aggregation support yields r3.ErrRelationAggregateNotSupported.
func (*CRUD[T, ID]) Inner ¶
Inner returns the underlying CRUD, bypassing metrics for a specific operation.
func (*CRUD[T, ID]) Metrics ¶
func (m *CRUD[T, ID]) Metrics() r3.CRUD[MetricRecord, string]
Metrics returns the metric record CRUD for querying metrics directly, e.g. via the query builders or the Aggregator.
func (*CRUD[T, ID]) PatchWhere ¶ added in v0.0.4
func (m *CRUD[T, ID]) PatchWhere( ctx context.Context, filters r3.Filters, entity T, fields r3.Fields, ) (int64, error)
PatchWhere runs a bulk conditional update via the inner CRUD and records metrics (OpPatchWhere, with TotalCount = affected-row count). Returns r3.ErrBulkPatchNotSupported when the inner repo has no bulk-patch capability.
func (*CRUD[T, ID]) Rewrap ¶
Rewrap rebuilds this decorator around a different inner CRUD, sharing the store and options (used to re-apply metrics over a transaction-bound CRUD).
func (*CRUD[T, ID]) Upsert ¶ added in v0.0.4
Upsert inserts-or-updates via the inner CRUD and records metrics (OpUpsert). The pre-state is fetched (when IDFunc is set) so diff-based collectors see the old entity, mirroring Update. Returns r3.ErrUpsertNotSupported when the inner repo has no upsert capability.
type Collector ¶
type Collector[T any, ID comparable] interface { Collect(ctx context.Context, opCtx OperationContext[T, ID]) []MetricEntry }
Collector emits zero or more MetricEntry values for a completed CRUD operation. It is called after every operation; nil means record nothing. Collectors must be pure - no I/O or side effects, just compute from the OperationContext.
func CRUDActionCollector ¶
func CRUDActionCollector[T any, ID comparable]() Collector[T, ID]
CRUDActionCollector emits one "crud.action" entry (value 1) per successful operation. The operation type rides the decorator's "operation" core label, so this collector does not add it. RecordID is set for entity-level ops and empty for type-level ones (list/count/aggregate).
func ErrorCollector ¶
func ErrorCollector[T any, ID comparable]() Collector[T, ID]
ErrorCollector emits one "crud.error" entry (value 1) per failed operation. Unlike other collectors, it fires ONLY on error.
func FieldChangeCollector ¶
func FieldChangeCollector[T any, ID comparable]() Collector[T, ID]
FieldChangeCollector emits one "entity.field_change" entry per changed field, labeled {"field": "<name>"}. Patch uses the Fields list; Update/Upsert detect changes via a shallow reflect comparison, so it needs the old entity (HasOld).
func LatencyCollector ¶
func LatencyCollector[T any, ID comparable]() Collector[T, ID]
LatencyCollector emits one "crud.action.latency" entry per operation, valued as wall-clock milliseconds. Fires on both success and failure.
func LifecycleCollector ¶
func LifecycleCollector[T any, ID comparable](createdAtFunc func(T) time.Time) Collector[T, ID]
LifecycleCollector emits one "entity.lifecycle" entry on Delete, valued as hours since creation. createdAtFunc extracts the creation timestamp; pass nil (or return a zero time) and the collector is a no-op.
func ListFilterCollector ¶
func ListFilterCollector[T any, ID comparable]() Collector[T, ID]
ListFilterCollector emits one "crud.list.filter" entry per leaf filter, labeled {"field": "<name>", "operator": "<op>"} - answering "what do users filter by?".
func ListResultSizeCollector ¶
func ListResultSizeCollector[T any, ID comparable]() Collector[T, ID]
ListResultSizeCollector emits one "crud.list.result_size" entry per List, valued as the number of results returned.
func ListTotalCountCollector ¶
func ListTotalCountCollector[T any, ID comparable]() Collector[T, ID]
ListTotalCountCollector emits one "crud.list.total_count" entry per List, valued as the total matching count (before pagination) - tracks data growth.
func PatchSizeCollector ¶
func PatchSizeCollector[T any, ID comparable]() Collector[T, ID]
PatchSizeCollector emits one "entity.patch_size" entry on Patch, valued as the number of fields patched at once.
func PopularityCollector ¶
func PopularityCollector[T any, ID comparable]() Collector[T, ID]
PopularityCollector emits "entity.popularity" (value 1) per entity accessed: on Get one entry labeled {"source": "get"}, on List one per result labeled {"source": "list"}. Needs IDFunc set so entity IDs are available; on List, entity labelers apply per-entity, overriding the decorator's single-entity merge.
type CollectorFunc ¶
type CollectorFunc[T any, ID comparable] func(ctx context.Context, opCtx OperationContext[T, ID]) []MetricEntry
CollectorFunc adapts a function to Collector for inline collectors:
metrics.CollectorFunc[Order, int64](func(ctx context.Context, opCtx metrics.OperationContext[Order, int64]) []metrics.MetricEntry {
// custom logic
})
func (CollectorFunc[T, ID]) Collect ¶
func (f CollectorFunc[T, ID]) Collect(ctx context.Context, opCtx OperationContext[T, ID]) []MetricEntry
Collect implements the Collector interface.
type ContextLabeler ¶
ContextLabeler extracts dimension labels from request context, for labels that come from the HTTP layer rather than the entity.
type IDFunc ¶
type IDFunc[T any, ID comparable] func(entity T) ID
IDFunc extracts an entity's primary key. The decorator uses it to populate RecordID and to fetch old entity state before updates/deletes.
type Labeler ¶
Labeler extracts dimension labels from an entity; composed labelers' outputs are merged into every metric record.
orderTypeLabeler := func(o Order) metrics.Labels {
return metrics.Labels{"order_type": o.Type}
}
type Labels ¶
Labels attaches arbitrary key-value dimensions to a metric record for slicing and filtering (e.g. {"order_type": "manual", "actor_id": "42"}).
type MetricEntry ¶
type MetricEntry struct {
// MetricName identifies the metric (e.g. "crud.action").
MetricName string
// Value is the numeric measurement.
Value float64
// Labels are collector-specific, merged with global/context/core labels by
// the decorator.
Labels Labels
// RecordID is the stringified entity key; empty for type-level metrics.
RecordID string
}
MetricEntry is what a Collector emits; the decorator enriches it with ID, bucket, timestamps, and merged labels before storing as a MetricRecord.
type MetricRecord ¶
type MetricRecord struct {
// ID is the unique identifier for this record.
ID string `json:"id" db:"id,pk" bson:"_id"`
// RecordType is the entity type name (e.g. "users"), derived from the struct
// type or set via options.
RecordType string `json:"record_type" db:"record_type" bson:"record_type"`
// RecordID is the stringified entity key; empty for type-level metrics.
RecordID string `json:"record_id" db:"record_id" bson:"record_id"`
// MetricName identifies the metric (e.g. "crud.action").
MetricName string `json:"metric_name" db:"metric_name" bson:"metric_name"`
// Value is the numeric measurement (1 for counters, ms for latency, etc.).
Value float64 `json:"value" db:"value" bson:"value"`
// Labels carries arbitrary dimensions, stored as a JSON blob in SQL.
Labels r3.JSONColumn[Labels] `json:"labels" db:"labels" bson:"labels"`
// Bucket is the pre-computed time bucket for efficient aggregation: RFC3339
// truncated to the boundary (e.g. "2026-02-16T00:00:00Z" for daily).
Bucket string `json:"bucket" db:"bucket" bson:"bucket"`
// CreatedAt is when this metric was recorded.
CreatedAt time.Time `json:"created_at" db:"created_at" bson:"created_at"`
}
MetricRecord is a single recorded metric data point, itself a first-class r3 entity storable via any r3.CRUD[MetricRecord, string] backend.
type Operation ¶
type Operation string
Operation identifies which CRUD method was invoked.
const ( OpCreate Operation = "create" OpGet Operation = "get" OpList Operation = "list" OpCount Operation = "count" OpAggregate Operation = "aggregate" OpUpdate Operation = "update" OpPatch Operation = "patch" OpDelete Operation = "delete" OpUpsert Operation = "upsert" OpPatchWhere Operation = "patch_where" )
type OperationContext ¶
type OperationContext[T any, ID comparable] struct { // Operation is the CRUD method that was invoked. Operation Operation // Duration is the wall-clock time the inner operation took. Duration time.Duration // Entity is the result: post-mutation for create/update/patch, pre-deletion // for delete, fetched for get. Zero for list - use Entities. Entity T // Entities is the result set for List. Nil otherwise. Entities []T // OldEntity is the state before mutation (update/patch/delete). OldEntity T // HasOld reports whether OldEntity is valid. HasOld bool // EntityID is the primary key (get/delete). EntityID ID // TotalCount is List's total match count before pagination. TotalCount int64 // Query is the r3.Query passed to Get or List. Query r3.Query // Fields is the field list passed to Patch. Fields r3.Fields // Err is the inner operation's error, nil on success. Most collectors fire // only on success; ErrorCollector uses this. Err error }
OperationContext carries everything a collector needs about a completed CRUD operation. Fields not relevant to a given Operation hold their zero value.
type Option ¶
type Option[T any, ID comparable] func(*Options[T, ID])
Option is a functional option for configuring the metrics CRUD decorator.
func WithAsync ¶
func WithAsync[T any, ID comparable]() Option[T, ID]
WithAsync enables asynchronous metric recording.
func WithBucketSize ¶
func WithBucketSize[T any, ID comparable](size BucketSize) Option[T, ID]
WithBucketSize sets the time bucketing granularity.
func WithCollectors ¶
func WithCollectors[T any, ID comparable](collectors ...Collector[T, ID]) Option[T, ID]
WithCollectors sets the metric collectors.
func WithContextLabelers ¶
func WithContextLabelers[T any, ID comparable](labelers ...ContextLabeler) Option[T, ID]
WithContextLabelers sets context-based label extractors.
func WithErrorHandler ¶
func WithErrorHandler[T any, ID comparable](fn func(error)) Option[T, ID]
WithErrorHandler sets a custom error handler for metric persistence failures.
func WithIDFunc ¶
func WithIDFunc[T any, ID comparable](fn IDFunc[T, ID]) Option[T, ID]
WithIDFunc sets the function that extracts the primary key from an entity.
func WithLabelers ¶
func WithLabelers[T any, ID comparable](labelers ...Labeler[T]) Option[T, ID]
WithLabelers sets entity-based label extractors.
func WithRecordType ¶
func WithRecordType[T any, ID comparable](name string) Option[T, ID]
WithRecordType sets an explicit record type name for metric records.
type Options ¶
type Options[T any, ID comparable] struct { // RecordType names this entity type in metric records. If empty, derived from // T as snake_case plural (e.g. Order -> "orders"). RecordType string // IDFunc extracts the primary key; required for entity-level metrics. IDFunc IDFunc[T, ID] // Collectors are the metrics emitted per operation; all run on every op. Collectors []Collector[T, ID] // Labelers extract labels from entities, applied wherever an entity is available. Labelers []Labeler[T] // ContextLabelers extract labels from request context, applied to every record. ContextLabelers []ContextLabeler // BucketSize is the time bucketing granularity. Default: BucketDaily. BucketSize BucketSize // Async records metrics in a background goroutine: the CRUD op returns // immediately and recording errors are logged, not surfaced. Default: false. Async bool // ErrorHandler is called on persistence failure; if nil, errors go to slog. ErrorHandler func(error) }
Options configures a metrics CRUD decorator.
type QueryOption ¶
type QueryOption func(*queryConfig)
QueryOption configures additional filtering for aggregator methods.
func WithLabel ¶
func WithLabel(key, value string) QueryOption
WithLabel restricts an aggregator query to records carrying this exact label key-value pair.
type RankedEntity ¶
RankedEntity is an entity ranked by an aggregated metric value.
type RetentionEnforcer ¶
type RetentionEnforcer struct {
// contains filtered or unexported fields
}
RetentionEnforcer cleans up old metric records in any r3.CRUD[MetricRecord, string] per its policy.
Usage:
enforcer := metrics.NewRetentionEnforcer(metricsStore, metrics.RetentionPolicy{
MaxAge: 90 * 24 * time.Hour, // delete records older than 90 days
MaxRecords: 1_000_000, // keep at most 1M records per type
})
// Run once (e.g. from a cron job):
enforcer.Enforce(ctx, "orders")
// Run periodically in the background:
stop := enforcer.Start(ctx, "orders", 1*time.Hour)
defer stop()
func NewRetentionEnforcer ¶
func NewRetentionEnforcer(store r3.CRUD[MetricRecord, string], policy RetentionPolicy) *RetentionEnforcer
NewRetentionEnforcer creates an enforcer for store with the given policy.
type RetentionPolicy ¶
type RetentionPolicy struct {
// MaxAge deletes records older than this. Zero disables age-based retention.
MaxAge time.Duration
// MaxRecords caps records kept per entity type, deleting the oldest first.
// Zero disables the count-based limit.
MaxRecords int64
}
RetentionPolicy defines cleanup rules for old metric records; both fields apply together, whichever deletes more.
type RollupExecutor ¶
type RollupExecutor struct {
// contains filtered or unexported fields
}
RollupExecutor compacts fine-grained metric records into coarser summaries by querying old records, grouping by (record_type, metric_name, target_bucket, label set), summing each group into one summary, then deleting the originals.
Usage:
executor := metrics.NewRollupExecutor(metricsStore, metrics.RollupPolicy{
SourceBucket: metrics.BucketMinutely,
TargetBucket: metrics.BucketHourly,
MinAge: 7 * 24 * time.Hour,
})
executor.Execute(ctx, "orders")
func NewRollupExecutor ¶
func NewRollupExecutor(store r3.CRUD[MetricRecord, string], policy RollupPolicy) *RollupExecutor
NewRollupExecutor creates a rollup executor for store with the given policy.
type RollupPolicy ¶
type RollupPolicy struct {
// SourceBucket is recorded in the summary's _rollup_source label for
// provenance only; it does not filter which records are compacted.
SourceBucket BucketSize
// TargetBucket is the coarser bucket size to roll up into.
TargetBucket BucketSize
// MinAge compacts only records older than this. Zero means all records.
MinAge time.Duration
}
RollupPolicy defines how old fine-grained records are compacted into coarser summaries - e.g. minutely records older than 7 days into hourly.
type TimeRange ¶
TimeRange defines a time window for querying metrics.
func Last24Hours ¶
func Last24Hours() TimeRange
Last24Hours returns a TimeRange covering the last 24 hours.
func Last30Days ¶
func Last30Days() TimeRange
Last30Days returns a TimeRange covering the last 30 days.
func LastMinutes ¶
LastMinutes returns a TimeRange covering the last n minutes.