metrics

package
v1.2.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 3, 2026 License: GPL-3.0 Imports: 7 Imported by: 0

README

metrics

Import path: github.com/InsideGallery/core/metrics

metrics provides backend-agnostic service instrumentation. Services record counts, gauges, and distributions through a Client; processor packages register concrete exporters by name.

Main APIs

  • Config selects processors.
  • GetEnvConfig(prefix ...string) reads metrics config, defaulting to the METRICS prefix.
  • PrometheusOnly(cfg Config) collapses any enabled config to Prometheus.
  • Processor is the exporter interface: Close, Count, Gauge, and Distribution.
  • Register, RegisteredProcessors, and Factory manage processor registration.
  • New(cfg Config, service string) builds a fanout client.
  • Default, SetDefault, and InstallDefault manage the process-wide client.
  • NormalizeTags returns a sorted copy of tags; TagSet joins sorted tags with commas.
  • Counter, Gauge, and Observer are resolved metric handles; HandleProvider is the optional capability of resolving one, implemented by *Client and by the Prometheus processor.
  • (*Client).CounterHandle, GaugeHandle, and DistributionHandle resolve a handle across every configured processor.

Usage

package example

import (
	"errors"

	_ "github.com/InsideGallery/core/metrics/all"

	"github.com/InsideGallery/core/metrics"
)

func recordMetric() (err error) {
	cfg, err := metrics.GetEnvConfig()
	if err != nil {
		return err
	}

	client, err := metrics.New(cfg, "api")
	if err != nil {
		return err
	}
	if client == nil {
		return nil
	}

	handle := metrics.InstallDefault(client)
	defer func() {
		err = errors.Join(err, handle.Close())
	}()

	return client.Count("requests_total", 1, []string{"status:ok"})
}

Configuration

GetEnvConfig reads:

  • METRICS_PROCESSORS: comma-separated processor names, default prometheus.

Processor names are trimmed, lowercased, de-duplicated, and may be split across comma-separated entries. The values none, off, and disabled disable metrics. Processor-specific environment variables do not select processors; they only configure a processor after it has been selected and registered.

Resolved Handles

Count, Gauge, and Distribution take a (name, tags) tuple and every processor has to turn it back into a backend child metric on each record. A caller on a data path records the same bounded set of tuples forever, so it can resolve the child once instead:

requests, err := client.CounterHandle("requests_total", []string{"op:get", "status:ok"})
if err != nil {
	return err
}

requests.Add(1) // per operation: no tuple, no lookup

Against the Prometheus processor, whose memo already makes a repeat record allocation-free, this is 71.9ns -> 6.8ns per record (BenchmarkResolvedHandleVersusMemoizedRecord, medians of 5, one process, 0 allocations in both arms).

HandleProvider is deliberately not part of Processor. A processor that does not implement it keeps working: *Client adapts its Count/Gauge/Distribution methods into a handle, so datadog, otel, and statsd need no change, and a client with a mix of processors records into all of them through one handle. A resolution error is returned rather than absorbed — the caller has no handle and can still record through Count.

Counter, Gauge, and Observer are aliases to interface literals (interface{ Add(value int64) } and peers), not defined types. That is what lets a consumer pinned to a release without this API declare the same literal locally and detect the capability with a type assertion, using handles when the linked version provides them and Count/Distribution when it does not. Do not turn them into defined types: Go matches method signatures on type identity, and a defined type is never identical to any other type, so consumers would have to bump their pin in lockstep.

Operational Notes

New returns nil, nil when metrics are disabled. A nil *Client is safe to call and returns nil for Close, Count, Gauge, and Distribution.

Import metrics/all or the specific processor packages before selecting processor names in METRICS_PROCESSORS. Processor call errors are joined and wrapped with the metric operation and name.

Documentation

Overview

Package metrics provides backend-agnostic service instrumentation.

Services record metrics through Client or the Processor interface. Concrete exporters live in pkg/metrics/processors/* and register themselves at init, following the same plugin pattern used by pkg/fastlog.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func NormalizeTags

func NormalizeTags(tags []string) []string

NormalizeTags returns a stable copy of tags suitable for processors.

func Register

func Register(kind string, factory Factory)

Register makes a metrics processor available by kind.

func RegisteredProcessors

func RegisteredProcessors() []string

RegisteredProcessors returns all registered processor names.

func SetDefault

func SetDefault(c *Client)

SetDefault stores the process-wide metrics client for service-specific instrumentation.

func TagSet

func TagSet(tags []string) string

TagSet returns a stable tag-set strings for processors that cannot model arbitrary labels.

Types

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client fans metric calls out to configured processors.

func Default

func Default() *Client

Default returns the process-wide metrics client, or nil when metrics are disabled.

func New

func New(cfg Config, service string) (*Client, error)

New creates a metrics client from configured processors. Returns nil if cfg is not enabled.

func (*Client) Close

func (c *Client) Close() error

Close flushes pending metrics and closes all processors.

func (*Client) Count

func (c *Client) Count(name string, value int64, tags []string) error

Count records a count metric.

func (*Client) CounterHandle added in v1.2.5

func (c *Client) CounterHandle(name string, tags []string) (Counter, error)

CounterHandle resolves one counter handle for the metric, covering every configured processor. See HandleProvider.

func (*Client) Distribution

func (c *Client) Distribution(name string, value float64, tags []string) error

Distribution records a distribution metric.

func (*Client) DistributionHandle added in v1.2.5

func (c *Client) DistributionHandle(name string, tags []string) (Observer, error)

DistributionHandle resolves one distribution handle for the metric, covering every configured processor. See HandleProvider.

func (*Client) Gauge

func (c *Client) Gauge(name string, value float64, tags []string) error

Gauge records a gauge metric.

func (*Client) GaugeHandle added in v1.2.5

func (c *Client) GaugeHandle(name string, tags []string) (Gauge, error)

GaugeHandle resolves one gauge handle for the metric, covering every configured processor. See HandleProvider.

type Config

type Config struct {
	Processors []string `env:"_PROCESSORS" envDefault:"prometheus"`
}

Config holds backend-agnostic metrics configuration.

Environment:

  • METRICS_PROCESSORS defaults to prometheus.

func GetEnvConfig

func GetEnvConfig(prefix ...string) (Config, error)

GetEnvConfig reads metrics configuration from environment variables. Default prefix is METRICS. Processor-specific packages own their own env config.

func PrometheusOnly added in v1.2.1

func PrometheusOnly(cfg Config) Config

PrometheusOnly returns a config that uses Prometheus for every enabled metrics setup.

func (Config) Enabled

func (c Config) Enabled() bool

Enabled reports whether any processor is configured.

func (Config) EnabledProcessors

func (c Config) EnabledProcessors() []string

EnabledProcessors returns the configured processors.

type Counter added in v1.2.5

type Counter = interface{ Add(value int64) }

Counter, Gauge and Observer are resolved metric handles: a caller resolves one through HandleProvider at wiring time and records through it afterwards, so turning a (name, tags) tuple back into a backend child metric happens once instead of on every record. On a data path that records the same bounded set of tuples forever, that is the difference between a hashed cache lookup per record and an increment.

They are aliases to interface literals rather than defined types, and that is load-bearing. A consumer pinned to a release of this module that predates HandleProvider cannot name metrics.Counter, but it can declare the identical literal locally and assert the capability structurally, because Go matches method signatures on type identity and a defined type is never identical to any other type. The aliases are therefore what let a consumer use handles when the linked version provides them and keep using Count/Gauge/Distribution when it does not, without bumping its pin in lockstep with this addition. Do not turn them into defined types.

type DefaultHandle

type DefaultHandle struct {
	// contains filtered or unexported fields
}

DefaultHandle restores a package-level metrics default and closes its client.

func InstallDefault

func InstallDefault(c *Client) *DefaultHandle

InstallDefault installs a process-wide metrics default with an explicit close path.

func (*DefaultHandle) Client

func (h *DefaultHandle) Client() *Client

Client returns the installed default client.

func (*DefaultHandle) Close

func (h *DefaultHandle) Close() error

Close restores the previous default client and closes the installed client.

type Factory

type Factory func(Config, string) (Processor, error)

Factory creates a concrete metrics processor for a service.

type Gauge added in v1.2.5

type Gauge = interface{ Set(value float64) }

Counter, Gauge and Observer are resolved metric handles: a caller resolves one through HandleProvider at wiring time and records through it afterwards, so turning a (name, tags) tuple back into a backend child metric happens once instead of on every record. On a data path that records the same bounded set of tuples forever, that is the difference between a hashed cache lookup per record and an increment.

They are aliases to interface literals rather than defined types, and that is load-bearing. A consumer pinned to a release of this module that predates HandleProvider cannot name metrics.Counter, but it can declare the identical literal locally and assert the capability structurally, because Go matches method signatures on type identity and a defined type is never identical to any other type. The aliases are therefore what let a consumer use handles when the linked version provides them and keep using Count/Gauge/Distribution when it does not, without bumping its pin in lockstep with this addition. Do not turn them into defined types.

type HandleProvider added in v1.2.5

type HandleProvider interface {
	CounterHandle(name string, tags []string) (Counter, error)
	GaugeHandle(name string, tags []string) (Gauge, error)
	DistributionHandle(name string, tags []string) (Observer, error)
}

HandleProvider is the optional capability of resolving a metric handle before recording. It is deliberately not part of Processor: a processor that does not implement it keeps recording through Count, Gauge and Distribution, so implementing it stays opt-in per backend and callers detect support with a type assertion. A returned handle must be safe for concurrent use and stays valid for the life of the processor.

type Observer added in v1.2.5

type Observer = interface{ Observe(value float64) }

Counter, Gauge and Observer are resolved metric handles: a caller resolves one through HandleProvider at wiring time and records through it afterwards, so turning a (name, tags) tuple back into a backend child metric happens once instead of on every record. On a data path that records the same bounded set of tuples forever, that is the difference between a hashed cache lookup per record and an increment.

They are aliases to interface literals rather than defined types, and that is load-bearing. A consumer pinned to a release of this module that predates HandleProvider cannot name metrics.Counter, but it can declare the identical literal locally and assert the capability structurally, because Go matches method signatures on type identity and a defined type is never identical to any other type. The aliases are therefore what let a consumer use handles when the linked version provides them and keep using Count/Gauge/Distribution when it does not, without bumping its pin in lockstep with this addition. Do not turn them into defined types.

type Processor

type Processor interface {
	Close() error
	Count(name string, value int64, tags []string) error
	Gauge(name string, value float64, tags []string) error
	Distribution(name string, value float64, tags []string) error
}

Processor records metrics for one concrete backend.

Directories

Path Synopsis
Package all imports every in-tree metrics processor so each processor registers with the default metrics registry through its init hook.
Package all imports every in-tree metrics processor so each processor registers with the default metrics registry through its init hook.
processors

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL