github.com/uber-go/tally/v4@v4.1.17/prometheus/config.go (about)

     1  // Copyright (c) 2021 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package prometheus
    22  
    23  import (
    24  	"fmt"
    25  	"log"
    26  	"net"
    27  	"net/http"
    28  	"os"
    29  	"strings"
    30  
    31  	prom "github.com/prometheus/client_golang/prometheus"
    32  )
    33  
    34  // Configuration is a configuration for a Prometheus reporter.
    35  type Configuration struct {
    36  	// HandlerPath if specified will be used instead of using the default
    37  	// HTTP handler path "/metrics".
    38  	HandlerPath string `yaml:"handlerPath"`
    39  
    40  	// ListenNetwork if specified will be used instead of using tcp network.
    41  	// Supported networks: tcp, tcp4, tcp6 and unix.
    42  	ListenNetwork string `yaml:"listenNetwork"`
    43  
    44  	// ListenAddress if specified will be used instead of just registering the
    45  	// handler on the default HTTP serve mux without listening.
    46  	ListenAddress string `yaml:"listenAddress"`
    47  
    48  	// TimerType is the default Prometheus type to use for Tally timers.
    49  	TimerType string `yaml:"timerType"`
    50  
    51  	// DefaultHistogramBuckets if specified will set the default histogram
    52  	// buckets to be used by the reporter.
    53  	DefaultHistogramBuckets []HistogramObjective `yaml:"defaultHistogramBuckets"`
    54  
    55  	// DefaultSummaryObjectives if specified will set the default summary
    56  	// objectives to be used by the reporter.
    57  	DefaultSummaryObjectives []SummaryObjective `yaml:"defaultSummaryObjectives"`
    58  
    59  	// OnError specifies what to do when an error either with listening
    60  	// on the specified listen address or registering a metric with the
    61  	// Prometheus. By default the registerer will panic.
    62  	OnError string `yaml:"onError"`
    63  }
    64  
    65  // HistogramObjective is a Prometheus histogram bucket.
    66  // See: https://godoc.org/github.com/prometheus/client_golang/prometheus#HistogramOpts
    67  type HistogramObjective struct {
    68  	Upper float64 `yaml:"upper"`
    69  }
    70  
    71  // SummaryObjective is a Prometheus summary objective.
    72  // See: https://godoc.org/github.com/prometheus/client_golang/prometheus#SummaryOpts
    73  type SummaryObjective struct {
    74  	Percentile   float64 `yaml:"percentile"`
    75  	AllowedError float64 `yaml:"allowedError"`
    76  }
    77  
    78  // ConfigurationOptions allows some programatic options, such as using a
    79  // specific registry and what error callback to register.
    80  type ConfigurationOptions struct {
    81  	// Registry if not nil will specify the specific registry to use
    82  	// for registering metrics.
    83  	Registry *prom.Registry
    84  	// OnError allows for customization of what to do when a metric
    85  	// registration error fails, the default is to panic.
    86  	OnError func(e error)
    87  }
    88  
    89  // NewReporter creates a new M3 reporter from this configuration.
    90  func (c Configuration) NewReporter(
    91  	configOpts ConfigurationOptions,
    92  ) (Reporter, error) {
    93  	var opts Options
    94  
    95  	if configOpts.Registry != nil {
    96  		opts.Registerer = configOpts.Registry
    97  	}
    98  
    99  	if configOpts.OnError != nil {
   100  		opts.OnRegisterError = configOpts.OnError
   101  	} else {
   102  		switch c.OnError {
   103  		case "stderr":
   104  			opts.OnRegisterError = func(err error) {
   105  				fmt.Fprintf(os.Stderr, "tally prometheus reporter error: %v\n", err)
   106  			}
   107  		case "log":
   108  			opts.OnRegisterError = func(err error) {
   109  				log.Printf("tally prometheus reporter error: %v\n", err)
   110  			}
   111  		case "none":
   112  			opts.OnRegisterError = func(err error) {}
   113  		default:
   114  			opts.OnRegisterError = func(err error) {
   115  				panic(err)
   116  			}
   117  		}
   118  	}
   119  
   120  	switch c.TimerType {
   121  	case "summary":
   122  		opts.DefaultTimerType = SummaryTimerType
   123  	case "histogram":
   124  		opts.DefaultTimerType = HistogramTimerType
   125  	}
   126  
   127  	if len(c.DefaultHistogramBuckets) > 0 {
   128  		var values []float64
   129  		for _, value := range c.DefaultHistogramBuckets {
   130  			values = append(values, value.Upper)
   131  		}
   132  		opts.DefaultHistogramBuckets = values
   133  	}
   134  
   135  	if len(c.DefaultSummaryObjectives) > 0 {
   136  		values := make(map[float64]float64)
   137  		for _, value := range c.DefaultSummaryObjectives {
   138  			values[value.Percentile] = value.AllowedError
   139  		}
   140  		opts.DefaultSummaryObjectives = values
   141  	}
   142  
   143  	reporter := NewReporter(opts)
   144  
   145  	path := "/metrics"
   146  	if handlerPath := strings.TrimSpace(c.HandlerPath); handlerPath != "" {
   147  		path = handlerPath
   148  	}
   149  
   150  	if addr := strings.TrimSpace(c.ListenAddress); addr == "" {
   151  		http.Handle(path, reporter.HTTPHandler())
   152  	} else {
   153  		mux := http.NewServeMux()
   154  		mux.Handle(path, reporter.HTTPHandler())
   155  		go func() {
   156  			network := c.ListenNetwork
   157  			if network == "" {
   158  				network = "tcp"
   159  			}
   160  
   161  			listener, err := net.Listen(network, addr)
   162  			if err != nil {
   163  				opts.OnRegisterError(err)
   164  				return
   165  			}
   166  
   167  			defer listener.Close()
   168  
   169  			if err = http.Serve(listener, mux); err != nil {
   170  				opts.OnRegisterError(err)
   171  			}
   172  		}()
   173  	}
   174  
   175  	return reporter, nil
   176  }