github.com/letsencrypt/boulder@v0.20251208.0/observer/obs_conf.go (about) 1 package observer 2 3 import ( 4 "errors" 5 "fmt" 6 "net" 7 "strconv" 8 9 "github.com/prometheus/client_golang/prometheus" 10 11 "github.com/letsencrypt/boulder/cmd" 12 "github.com/letsencrypt/boulder/observer/probers" 13 ) 14 15 var ( 16 countMonitors = prometheus.NewCounterVec( 17 prometheus.CounterOpts{ 18 Name: "obs_monitors", 19 Help: "details of each configured monitor", 20 }, 21 []string{"kind", "valid"}, 22 ) 23 histObservations *prometheus.HistogramVec 24 ) 25 26 // ObsConf is exported to receive YAML configuration. 27 type ObsConf struct { 28 DebugAddr string `yaml:"debugaddr" validate:"omitempty,hostname_port"` 29 Buckets []float64 `yaml:"buckets" validate:"min=1,dive"` 30 Syslog cmd.SyslogConfig `yaml:"syslog"` 31 OpenTelemetry cmd.OpenTelemetryConfig 32 MonConfs []*MonConf `yaml:"monitors" validate:"min=1,dive"` 33 } 34 35 // validateSyslog ensures the `Syslog` field received by `ObsConf` 36 // contains valid log levels. 37 func (c *ObsConf) validateSyslog() error { 38 syslog, stdout := c.Syslog.SyslogLevel, c.Syslog.StdoutLevel 39 if stdout < 0 || stdout > 7 || syslog < 0 || syslog > 7 { 40 return fmt.Errorf( 41 "invalid 'syslog', '%+v', valid log levels are 0-7", c.Syslog) 42 } 43 return nil 44 } 45 46 // validateDebugAddr ensures the `debugAddr` received by `ObsConf` is 47 // properly formatted and a valid port. 48 func (c *ObsConf) validateDebugAddr() error { 49 _, p, err := net.SplitHostPort(c.DebugAddr) 50 if err != nil { 51 return fmt.Errorf( 52 "invalid 'debugaddr', %q, not expected format", c.DebugAddr) 53 } 54 port, _ := strconv.Atoi(p) 55 if port <= 0 || port > 65535 { 56 return fmt.Errorf( 57 "invalid 'debugaddr','%d' is not a valid port", port) 58 } 59 return nil 60 } 61 62 func (c *ObsConf) makeMonitors(metrics prometheus.Registerer) ([]*monitor, []error, error) { 63 var errs []error 64 var monitors []*monitor 65 proberSpecificMetrics := make(map[string]map[string]prometheus.Collector) 66 for e, m := range c.MonConfs { 67 entry := strconv.Itoa(e + 1) 68 proberConf, err := probers.GetConfigurer(m.Kind) 69 if err != nil { 70 // append error to errs 71 errs = append(errs, fmt.Errorf("'monitors' entry #%s couldn't be validated: %w", entry, err)) 72 // increment metrics 73 countMonitors.WithLabelValues(m.Kind, "false").Inc() 74 // bail out before constructing the monitor. with no configurer, it will fail 75 continue 76 } 77 kind := proberConf.Kind() 78 79 // set up custom metrics internal to each prober kind 80 _, exist := proberSpecificMetrics[kind] 81 if !exist { 82 // we haven't seen this prober kind before, so we need to request 83 // any custom metrics it may have and register them with the 84 // prometheus registry 85 proberSpecificMetrics[kind] = make(map[string]prometheus.Collector) 86 for name, collector := range proberConf.Instrument() { 87 // register the collector with the prometheus registry 88 metrics.MustRegister(collector) 89 // store the registered collector so we can pass it to every 90 // monitor that will construct this kind of prober 91 proberSpecificMetrics[kind][name] = collector 92 } 93 } 94 95 monitor, err := m.makeMonitor(proberSpecificMetrics[kind]) 96 if err != nil { 97 // append validation error to errs 98 errs = append(errs, fmt.Errorf("'monitors' entry #%s couldn't be validated: %w", entry, err)) 99 100 // increment metrics 101 countMonitors.WithLabelValues(kind, "false").Inc() 102 } else { 103 // append monitor to monitors 104 monitors = append(monitors, monitor) 105 106 // increment metrics 107 countMonitors.WithLabelValues(kind, "true").Inc() 108 } 109 } 110 if len(c.MonConfs) == len(errs) { 111 return nil, errs, errors.New("no valid monitors, cannot continue") 112 } 113 return monitors, errs, nil 114 } 115 116 // MakeObserver constructs an `Observer` object from the contents of the 117 // bound `ObsConf`. If the `ObsConf` cannot be validated, an error 118 // appropriate for end-user consumption is returned instead. 119 func (c *ObsConf) MakeObserver() (*Observer, error) { 120 err := c.validateSyslog() 121 if err != nil { 122 return nil, err 123 } 124 125 err = c.validateDebugAddr() 126 if err != nil { 127 return nil, err 128 } 129 130 if len(c.MonConfs) == 0 { 131 return nil, errors.New("no monitors provided") 132 } 133 134 if len(c.Buckets) == 0 { 135 return nil, errors.New("no histogram buckets provided") 136 } 137 138 // Start monitoring and logging. 139 metrics, logger, shutdown := cmd.StatsAndLogging(c.Syslog, c.OpenTelemetry, c.DebugAddr) 140 histObservations = prometheus.NewHistogramVec( 141 prometheus.HistogramOpts{ 142 Name: "obs_observations", 143 Help: "details of each probe attempt", 144 Buckets: c.Buckets, 145 }, []string{"name", "kind", "success"}) 146 metrics.MustRegister(countMonitors) 147 metrics.MustRegister(histObservations) 148 defer cmd.AuditPanic() 149 logger.Info(cmd.VersionString()) 150 logger.Infof("Initializing boulder-observer daemon") 151 logger.Debugf("Using config: %+v", c) 152 153 monitors, errs, err := c.makeMonitors(metrics) 154 if len(errs) != 0 { 155 logger.Errf("%d of %d monitors failed validation", len(errs), len(c.MonConfs)) 156 for _, err := range errs { 157 logger.Errf("%s", err) 158 } 159 } else { 160 logger.Info("all monitors passed validation") 161 } 162 if err != nil { 163 return nil, err 164 } 165 return &Observer{logger, monitors, shutdown}, nil 166 }