ginprom

package module
v0.0.0-...-ae43f67 Latest Latest
Warning

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

Go to latest
Published: Dec 9, 2022 License: MIT Imports: 13 Imported by: 2

README

gin-prometheus

Gin Web Framework Prometheus metrics exporter with exemplar

Installation

$ go get github.com/wei840222/gin-prometheus

Usage

package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
	ginprom "github.com/wei840222/gin-prometheus"
)

func main() {
	r := gin.New()

	p := ginprom.NewPrometheus("gin")
	p.Use(r)

	r.GET("/", func(c *gin.Context) {
		c.JSON(http.StatusOK, "Hello world!")
	})

	r.Run()
}

See the example.go file

Preserving a low cardinality for the request counter

The request counter (requests_total) has a url label which, although desirable, can become problematic in cases where your application uses templated routes expecting a great number of variations, as Prometheus explicitly recommends against metrics having high cardinality dimensions:

https://prometheus.io/docs/practices/naming/#labels

If you have for instance a /customer/:name templated route and you don't want to generate a time series for every possible customer name, you could supply this mapping function to the middleware:

package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
	ginprom "github.com/wei840222/gin-prometheus"
)

func main() {
	r := gin.New()

	p := ginprom.NewPrometheus("gin")

	p.ReqCntURLLabelMappingFn = func(c *gin.Context) string {
		url := c.Request.URL.Path
		for _, p := range c.Params {
			if p.Key == "name" {
				url = strings.Replace(url, p.Value, ":name", 1)
				break
			}
		}
		return url
	}

	p.Use(r)

	r.GET("/", func(c *gin.Context) {
		c.JSON(http.StatusOK, "Hello world!")
	})

	r.Run()
}

which would map /customer/alice and /customer/bob to their template /customer/:name, and thus preserve a low cardinality for our metrics.

Using with OpenTelemetry Tracer and Meter

package main

import (
	"context"
	"fmt"
	"net/http"
	"time"

	"github.com/gin-gonic/gin"
	ginprom "github.com/wei840222/gin-prometheus"
	"go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin"
	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
	"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
	otelprom "go.opentelemetry.io/otel/exporters/prometheus"
	"go.opentelemetry.io/otel/propagation"
	sdkmetric "go.opentelemetry.io/otel/sdk/metric"
	"go.opentelemetry.io/otel/sdk/resource"
	sdktrace "go.opentelemetry.io/otel/sdk/trace"
	semconv "go.opentelemetry.io/otel/semconv/v1.12.0"
	"go.opentelemetry.io/otel/trace"
)

var ginOtelLogFormatter = func(param gin.LogFormatterParams) string {
	var statusColor, methodColor, resetColor string
	if param.IsOutputColor() {
		statusColor = param.StatusCodeColor()
		methodColor = param.MethodColor()
		resetColor = param.ResetColor()
	}

	if param.Latency > time.Minute {
		param.Latency = param.Latency.Truncate(time.Second)
	}

	return fmt.Sprintf("[GIN] %v |%s %3d %s| %13v | %15s |%s %-7s %s %#v traceID=%s\n%s",
		param.TimeStamp.Format("2006/01/02 - 15:04:05"),
		statusColor, param.StatusCode, resetColor,
		param.Latency,
		param.ClientIP,
		methodColor, param.Method, resetColor,
		param.Path,
		trace.SpanContextFromContext(param.Request.Context()).TraceID(),
		param.ErrorMessage,
	)
}

func main() {
	mExp := otelprom.New()
	provider := sdkmetric.NewMeterProvider(sdkmetric.WithReader(mExp))
	defer provider.Shutdown(context.Background())

	tExp, err := otlptrace.New(context.Background(), otlptracegrpc.NewClient())
	if err != nil {
		panic(err)
	}
	tp := sdktrace.NewTracerProvider(
		sdktrace.WithSampler(sdktrace.AlwaysSample()),
		sdktrace.WithBatcher(tExp),
		sdktrace.WithResource(resource.NewWithAttributes(
			semconv.SchemaURL,
			semconv.ServiceNameKey.String("myservice"),
			semconv.ServiceVersionKey.String("0.0.1"),
		)),
	)
	defer tp.Shutdown(context.Background())

	otel.SetTracerProvider(tp)
	otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}))

	p := ginprom.NewPrometheus("gin").SetEnableExemplar(true).SetOtelPromExporter(&mExp)
	p.SetListenAddress(":2222").SetMetricsPath(nil)

	e := gin.New()
	e.Use(otelgin.Middleware("gin", otelgin.WithTracerProvider(tp)), p.HandlerFunc(), gin.LoggerWithFormatter(ginOtelLogFormatter), gin.Recovery())

	e.GET("/", func(c *gin.Context) {
		c.JSON(http.StatusOK, "Hello world!")
	})
	e.GET("/panic", func(c *gin.Context) {
		panic("oh no")
	})

	e.Run()
}

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func NewMetric

func NewMetric(m *Metric, subsystem string) prometheus.Collector

NewMetric associates prometheus.Collector based on Metric.Type

Types

type Metric

type Metric struct {
	MetricCollector prometheus.Collector
	ID              string
	Name            string
	Description     string
	Type            string
	Args            []string
}

Metric is a definition for the name, description, type, ID, and prometheus.Collector type (i.e. CounterVec, Summary, etc) of each metric

type Prometheus

type Prometheus struct {
	Ppg PrometheusPushGateway

	MetricsList []*Metric
	MetricsPath string

	ReqCntURLLabelMappingFn RequestCounterURLLabelMappingFn

	// gin.Context string to use as a prometheus URL label
	URLLabelFromContext string

	EnableExemplar         bool
	OtelPrometheusExporter *otelprom.Exporter
	// contains filtered or unexported fields
}

Prometheus contains the metrics gathered by the instance and its path

func NewPrometheus

func NewPrometheus(subsystem string, customMetricsList ...[]*Metric) *Prometheus

NewPrometheus generates a new set of metrics with a certain subsystem name

func (*Prometheus) HandlerFunc

func (p *Prometheus) HandlerFunc() gin.HandlerFunc

HandlerFunc defines handler function for middleware

func (*Prometheus) SetEnableExemplar

func (p *Prometheus) SetEnableExemplar(enableExemplar bool) *Prometheus

SetEnableExemplar set enable exemplar feature

func (*Prometheus) SetListenAddress

func (p *Prometheus) SetListenAddress(address string) *Prometheus

SetListenAddress for exposing metrics on address. If not set, it will be exposed at the same address of the gin engine that is being used

func (*Prometheus) SetListenAddressWithRouter

func (p *Prometheus) SetListenAddressWithRouter(listenAddress string, r *gin.Engine) *Prometheus

SetListenAddressWithRouter for using a separate router to expose metrics. (this keeps things like GET /metrics out of your content's access log).

func (*Prometheus) SetMetricsPath

func (p *Prometheus) SetMetricsPath(e *gin.Engine) *Prometheus

SetMetricsPath set metrics paths

func (*Prometheus) SetMetricsPathWithAuth

func (p *Prometheus) SetMetricsPathWithAuth(e *gin.Engine, accounts gin.Accounts) *Prometheus

SetMetricsPathWithAuth set metrics paths with authentication

func (*Prometheus) SetOtelPromExporter

func (p *Prometheus) SetOtelPromExporter(otelPromExporter *otelprom.Exporter) *Prometheus

SetOtelPromExporter set opentelemetry prometheus exporter integration

func (*Prometheus) SetPushGateway

func (p *Prometheus) SetPushGateway(pushGatewayURL, metricsURL string, pushInterval time.Duration) *Prometheus

SetPushGateway sends metrics to a remote pushgateway exposed on pushGatewayURL every pushInterval. Metrics are fetched from metricsURL

func (*Prometheus) SetPushGatewayJob

func (p *Prometheus) SetPushGatewayJob(j string) *Prometheus

SetPushGatewayJob job name, defaults to "gin"

func (*Prometheus) Use

func (p *Prometheus) Use(e *gin.Engine)

Use adds the middleware to a gin engine.

func (*Prometheus) UseWithAuth

func (p *Prometheus) UseWithAuth(e *gin.Engine, accounts gin.Accounts)

UseWithAuth adds the middleware to a gin engine with BasicAuth.

type PrometheusPushGateway

type PrometheusPushGateway struct {

	// Push interval
	PushInterval time.Duration

	// Push Gateway URL in format http://domain:port
	// where JOBNAME can be any string of your choice
	PushGatewayURL string

	// Local metrics URL where metrics are fetched from, this could be ommited in the future
	// if implemented using prometheus common/expfmt instead
	MetricsURL string

	// pushgateway job name, defaults to "gin"
	Job string
}

PrometheusPushGateway contains the configuration for pushing to a Prometheus pushgateway (optional)

type RequestCounterURLLabelMappingFn

type RequestCounterURLLabelMappingFn func(c *gin.Context) string

RequestCounterURLLabelMappingFn is a function which can be supplied to the middleware to control the cardinality of the request counter's "url" label, which might be required in some contexts. For instance, if for a "/customer/:name" route you don't want to generate a time series for every possible customer name, you could use this function:

func(c *gin.Context) string {
	url := c.Request.URL.Path
	for _, p := range c.Params {
		if p.Key == "name" {
			url = strings.Replace(url, p.Value, ":name", 1)
			break
		}
	}
	return url
}

which would map "/customer/alice" and "/customer/bob" to their template "/customer/:name".

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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