263 lines
		
	
	
		
			6.7 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			263 lines
		
	
	
		
			6.7 KiB
		
	
	
	
		
			Go
		
	
	
	
| package prometheus
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"crypto/tls"
 | |
| 	"fmt"
 | |
| 	"net"
 | |
| 	"net/http"
 | |
| 	"net/url"
 | |
| 	"sync"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/influxdata/telegraf"
 | |
| 	"github.com/influxdata/telegraf/internal"
 | |
| 	tlsint "github.com/influxdata/telegraf/internal/tls"
 | |
| 	"github.com/influxdata/telegraf/plugins/outputs"
 | |
| 	"github.com/influxdata/telegraf/plugins/outputs/prometheus_client/v1"
 | |
| 	"github.com/influxdata/telegraf/plugins/outputs/prometheus_client/v2"
 | |
| 	"github.com/prometheus/client_golang/prometheus"
 | |
| 	"github.com/prometheus/client_golang/prometheus/promhttp"
 | |
| )
 | |
| 
 | |
| var (
 | |
| 	defaultListen             = ":9273"
 | |
| 	defaultPath               = "/metrics"
 | |
| 	defaultExpirationInterval = internal.Duration{Duration: 60 * time.Second}
 | |
| )
 | |
| 
 | |
| var sampleConfig = `
 | |
|   ## Address to listen on
 | |
|   listen = ":9273"
 | |
| 
 | |
|   ## Metric version controls the mapping from Telegraf metrics into
 | |
|   ## Prometheus format.  When using the prometheus input, use the same value in
 | |
|   ## both plugins to ensure metrics are round-tripped without modification.
 | |
|   ##
 | |
|   ##   example: metric_version = 1; deprecated in 1.13
 | |
|   ##            metric_version = 2; recommended version
 | |
|   # metric_version = 1
 | |
| 
 | |
|   ## Use HTTP Basic Authentication.
 | |
|   # basic_username = "Foo"
 | |
|   # basic_password = "Bar"
 | |
| 
 | |
|   ## If set, the IP Ranges which are allowed to access metrics.
 | |
|   ##   ex: ip_range = ["192.168.0.0/24", "192.168.1.0/30"]
 | |
|   # ip_range = []
 | |
| 
 | |
|   ## Path to publish the metrics on.
 | |
|   # path = "/metrics"
 | |
| 
 | |
|   ## Expiration interval for each metric. 0 == no expiration
 | |
|   # expiration_interval = "60s"
 | |
| 
 | |
|   ## Collectors to enable, valid entries are "gocollector" and "process".
 | |
|   ## If unset, both are enabled.
 | |
|   # collectors_exclude = ["gocollector", "process"]
 | |
| 
 | |
|   ## Send string metrics as Prometheus labels.
 | |
|   ## Unless set to false all string metrics will be sent as labels.
 | |
|   # string_as_label = true
 | |
| 
 | |
|   ## If set, enable TLS with the given certificate.
 | |
|   # tls_cert = "/etc/ssl/telegraf.crt"
 | |
|   # tls_key = "/etc/ssl/telegraf.key"
 | |
| 
 | |
|   ## Set one or more allowed client CA certificate file names to
 | |
|   ## enable mutually authenticated TLS connections
 | |
|   # tls_allowed_cacerts = ["/etc/telegraf/clientca.pem"]
 | |
| 
 | |
|   ## Export metric collection time.
 | |
|   # export_timestamp = false
 | |
| `
 | |
| 
 | |
| type Collector interface {
 | |
| 	Describe(ch chan<- *prometheus.Desc)
 | |
| 	Collect(ch chan<- prometheus.Metric)
 | |
| 	Add(metrics []telegraf.Metric) error
 | |
| }
 | |
| 
 | |
| type PrometheusClient struct {
 | |
| 	Listen             string            `toml:"listen"`
 | |
| 	MetricVersion      int               `toml:"metric_version"`
 | |
| 	BasicUsername      string            `toml:"basic_username"`
 | |
| 	BasicPassword      string            `toml:"basic_password"`
 | |
| 	IPRange            []string          `toml:"ip_range"`
 | |
| 	ExpirationInterval internal.Duration `toml:"expiration_interval"`
 | |
| 	Path               string            `toml:"path"`
 | |
| 	CollectorsExclude  []string          `toml:"collectors_exclude"`
 | |
| 	StringAsLabel      bool              `toml:"string_as_label"`
 | |
| 	ExportTimestamp    bool              `toml:"export_timestamp"`
 | |
| 	tlsint.ServerConfig
 | |
| 
 | |
| 	Log telegraf.Logger `toml:"-"`
 | |
| 
 | |
| 	server    *http.Server
 | |
| 	url       *url.URL
 | |
| 	collector Collector
 | |
| 	wg        sync.WaitGroup
 | |
| }
 | |
| 
 | |
| func (p *PrometheusClient) Description() string {
 | |
| 	return "Configuration for the Prometheus client to spawn"
 | |
| }
 | |
| 
 | |
| func (p *PrometheusClient) SampleConfig() string {
 | |
| 	return sampleConfig
 | |
| }
 | |
| 
 | |
| func (p *PrometheusClient) Init() error {
 | |
| 	defaultCollectors := map[string]bool{
 | |
| 		"gocollector": true,
 | |
| 		"process":     true,
 | |
| 	}
 | |
| 	for _, collector := range p.CollectorsExclude {
 | |
| 		delete(defaultCollectors, collector)
 | |
| 	}
 | |
| 
 | |
| 	registry := prometheus.NewRegistry()
 | |
| 	for collector := range defaultCollectors {
 | |
| 		switch collector {
 | |
| 		case "gocollector":
 | |
| 			registry.Register(prometheus.NewGoCollector())
 | |
| 		case "process":
 | |
| 			registry.Register(prometheus.NewProcessCollector(prometheus.ProcessCollectorOpts{}))
 | |
| 		default:
 | |
| 			return fmt.Errorf("unrecognized collector %s", collector)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	switch p.MetricVersion {
 | |
| 	default:
 | |
| 		fallthrough
 | |
| 	case 1:
 | |
| 		p.Log.Warnf("Use of deprecated configuration: metric_version = 1; please update to metric_version = 2")
 | |
| 		p.collector = v1.NewCollector(p.ExpirationInterval.Duration, p.StringAsLabel, p.Log)
 | |
| 		err := registry.Register(p.collector)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 	case 2:
 | |
| 		p.collector = v2.NewCollector(p.ExpirationInterval.Duration, p.StringAsLabel)
 | |
| 		err := registry.Register(p.collector)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	ipRange := make([]*net.IPNet, 0, len(p.IPRange))
 | |
| 	for _, cidr := range p.IPRange {
 | |
| 		_, ipNet, err := net.ParseCIDR(cidr)
 | |
| 		if err != nil {
 | |
| 			return fmt.Errorf("error parsing ip_range: %v", err)
 | |
| 		}
 | |
| 
 | |
| 		ipRange = append(ipRange, ipNet)
 | |
| 	}
 | |
| 
 | |
| 	authHandler := internal.AuthHandler(p.BasicUsername, p.BasicPassword, "prometheus", onAuthError)
 | |
| 	rangeHandler := internal.IPRangeHandler(ipRange, onError)
 | |
| 	promHandler := promhttp.HandlerFor(registry, promhttp.HandlerOpts{ErrorHandling: promhttp.ContinueOnError})
 | |
| 
 | |
| 	mux := http.NewServeMux()
 | |
| 	if p.Path == "" {
 | |
| 		p.Path = "/"
 | |
| 	}
 | |
| 	mux.Handle(p.Path, authHandler(rangeHandler(promHandler)))
 | |
| 
 | |
| 	tlsConfig, err := p.TLSConfig()
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	p.server = &http.Server{
 | |
| 		Addr:      p.Listen,
 | |
| 		Handler:   mux,
 | |
| 		TLSConfig: tlsConfig,
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (p *PrometheusClient) listen() (net.Listener, error) {
 | |
| 	if p.server.TLSConfig != nil {
 | |
| 		return tls.Listen("tcp", p.Listen, p.server.TLSConfig)
 | |
| 	} else {
 | |
| 		return net.Listen("tcp", p.Listen)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (p *PrometheusClient) Connect() error {
 | |
| 	listener, err := p.listen()
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	scheme := "http"
 | |
| 	if p.server.TLSConfig != nil {
 | |
| 		scheme = "https"
 | |
| 	}
 | |
| 
 | |
| 	p.url = &url.URL{
 | |
| 		Scheme: scheme,
 | |
| 		Host:   listener.Addr().String(),
 | |
| 		Path:   p.Path,
 | |
| 	}
 | |
| 
 | |
| 	p.Log.Infof("Listening on %s", p.URL())
 | |
| 
 | |
| 	p.wg.Add(1)
 | |
| 	go func() {
 | |
| 		defer p.wg.Done()
 | |
| 		err := p.server.Serve(listener)
 | |
| 		if err != nil && err != http.ErrServerClosed {
 | |
| 			p.Log.Errorf("Server error: %v", err)
 | |
| 		}
 | |
| 	}()
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func onAuthError(_ http.ResponseWriter) {
 | |
| }
 | |
| 
 | |
| func onError(rw http.ResponseWriter, code int) {
 | |
| 	http.Error(rw, http.StatusText(code), code)
 | |
| }
 | |
| 
 | |
| // Address returns the address the plugin is listening on.  If not listening
 | |
| // an empty string is returned.
 | |
| func (p *PrometheusClient) URL() string {
 | |
| 	if p.url != nil {
 | |
| 		return p.url.String()
 | |
| 	}
 | |
| 	return ""
 | |
| }
 | |
| 
 | |
| func (p *PrometheusClient) Close() error {
 | |
| 	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
 | |
| 	defer cancel()
 | |
| 
 | |
| 	err := p.server.Shutdown(ctx)
 | |
| 	p.wg.Wait()
 | |
| 	p.url = nil
 | |
| 	prometheus.Unregister(p.collector)
 | |
| 	return err
 | |
| }
 | |
| 
 | |
| func (p *PrometheusClient) Write(metrics []telegraf.Metric) error {
 | |
| 	return p.collector.Add(metrics)
 | |
| }
 | |
| 
 | |
| func init() {
 | |
| 	outputs.Add("prometheus_client", func() telegraf.Output {
 | |
| 		return &PrometheusClient{
 | |
| 			Listen:             defaultListen,
 | |
| 			Path:               defaultPath,
 | |
| 			ExpirationInterval: defaultExpirationInterval,
 | |
| 			StringAsLabel:      true,
 | |
| 		}
 | |
| 	})
 | |
| }
 |