Log error and exit instead of panicking

Following the lead of prometheus/prometheus, we prefer to log-and-exit
instead of an unstructured panic.

cf. https://github.com/prometheus/prometheus/pull/3061/files#diff-4a3ccbb3ebdcd530af96f0105fe833c2R182

Signed-off-by: Matthias Rampke <mr@soundcloud.com>
This commit is contained in:
Matthias Rampke 2020-01-10 14:02:59 +00:00
parent 0d72309324
commit 53f732d480
No known key found for this signature in database
GPG key ID: 2CDE413A9BD0A5BC
2 changed files with 55 additions and 27 deletions

View file

@ -234,7 +234,8 @@ func (b *Exporter) handleEvent(event Event) {
} }
default: default:
panic(fmt.Sprintf("unknown timer type '%s'", t)) level.Error(b.logger).Log("msg", "unknown timer type", "type", t)
os.Exit(1)
} }
default: default:
@ -531,7 +532,8 @@ func (l *StatsDTCPListener) Listen() {
if strings.HasSuffix(err.Error(), "use of closed network connection") { if strings.HasSuffix(err.Error(), "use of closed network connection") {
return return
} }
panic(fmt.Sprintf("AcceptTCP failed: %s", err)) level.Error(l.logger).Log("msg", "AcceptTCP failed", "error", err)
os.Exit(1)
} }
go l.handleConn(c) go l.handleConn(c)
} }

76
main.go
View file

@ -40,7 +40,7 @@ func init() {
prometheus.MustRegister(version.NewCollector("statsd_exporter")) prometheus.MustRegister(version.NewCollector("statsd_exporter"))
} }
func serveHTTP(listenAddress, metricsEndpoint string) { func serveHTTP(listenAddress, metricsEndpoint string, logger log.Logger) {
http.Handle(metricsEndpoint, promhttp.Handler()) http.Handle(metricsEndpoint, promhttp.Handler())
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(`<html> w.Write([]byte(`<html>
@ -51,13 +51,14 @@ func serveHTTP(listenAddress, metricsEndpoint string) {
</body> </body>
</html>`)) </html>`))
}) })
panic(http.ListenAndServe(listenAddress, nil)) level.Error(logger).Log("msg", http.ListenAndServe(listenAddress, nil))
os.Exit(1)
} }
func ipPortFromString(addr string) (*net.IPAddr, int) { func ipPortFromString(addr string) (*net.IPAddr, int, error) {
host, portStr, err := net.SplitHostPort(addr) host, portStr, err := net.SplitHostPort(addr)
if err != nil { if err != nil {
panic(fmt.Sprintf("Bad StatsD listening address: %s", addr)) return nil, 0, fmt.Errorf("bad StatsD listening address: %s", addr)
} }
if host == "" { if host == "" {
@ -65,33 +66,39 @@ func ipPortFromString(addr string) (*net.IPAddr, int) {
} }
ip, err := net.ResolveIPAddr("ip", host) ip, err := net.ResolveIPAddr("ip", host)
if err != nil { if err != nil {
panic(fmt.Sprintf("Unable to resolve %s: %s", host, err)) return nil, 0, fmt.Errorf("Unable to resolve %s: %s", host, err)
} }
port, err := strconv.Atoi(portStr) port, err := strconv.Atoi(portStr)
if err != nil || port < 0 || port > 65535 { if err != nil || port < 0 || port > 65535 {
panic(fmt.Sprintf("Bad port %s: %s", portStr, err)) return nil, 0, fmt.Errorf("Bad port %s: %s", portStr, err)
} }
return ip, port return ip, port, nil
} }
func udpAddrFromString(addr string) *net.UDPAddr { func udpAddrFromString(addr string) (*net.UDPAddr, error) {
ip, port := ipPortFromString(addr) ip, port, err := ipPortFromString(addr)
if err != nil {
return nil, err
}
return &net.UDPAddr{ return &net.UDPAddr{
IP: ip.IP, IP: ip.IP,
Port: port, Port: port,
Zone: ip.Zone, Zone: ip.Zone,
} }, nil
} }
func tcpAddrFromString(addr string) *net.TCPAddr { func tcpAddrFromString(addr string) (*net.TCPAddr, error) {
ip, port := ipPortFromString(addr) ip, port, err := ipPortFromString(addr)
if err != nil {
return nil, err
}
return &net.TCPAddr{ return &net.TCPAddr{
IP: ip.IP, IP: ip.IP,
Port: port, Port: port,
Zone: ip.Zone, Zone: ip.Zone,
} }, nil
} }
func configReloader(fileName string, mapper *mapper.MetricMapper, cacheSize int, logger log.Logger) { func configReloader(fileName string, mapper *mapper.MetricMapper, cacheSize int, logger log.Logger) {
@ -156,7 +163,8 @@ func main() {
logger := promlog.New(promlogConfig) logger := promlog.New(promlogConfig)
if *statsdListenUDP == "" && *statsdListenTCP == "" && *statsdListenUnixgram == "" { if *statsdListenUDP == "" && *statsdListenTCP == "" && *statsdListenUnixgram == "" {
panic("At least one of UDP/TCP/Unixgram listeners must be specified.") level.Error(logger).Log("At least one of UDP/TCP/Unixgram listeners must be specified.")
os.Exit(1)
} }
level.Info(logger).Log("msg", "Starting StatsD -> Prometheus Exporter", "version", version.Info()) level.Info(logger).Log("msg", "Starting StatsD -> Prometheus Exporter", "version", version.Info())
@ -164,23 +172,29 @@ func main() {
level.Info(logger).Log("msg", "Accepting StatsD Traffic", "udp", *statsdListenUDP, "tcp", *statsdListenTCP, "unixgram", *statsdListenUnixgram) level.Info(logger).Log("msg", "Accepting StatsD Traffic", "udp", *statsdListenUDP, "tcp", *statsdListenTCP, "unixgram", *statsdListenUnixgram)
level.Info(logger).Log("msg", "Accepting Prometheus Requests", "addr", *listenAddress) level.Info(logger).Log("msg", "Accepting Prometheus Requests", "addr", *listenAddress)
go serveHTTP(*listenAddress, *metricsEndpoint) go serveHTTP(*listenAddress, *metricsEndpoint, logger)
events := make(chan Events, *eventQueueSize) events := make(chan Events, *eventQueueSize)
defer close(events) defer close(events)
eventQueue := newEventQueue(events, *eventFlushThreshold, *eventFlushInterval) eventQueue := newEventQueue(events, *eventFlushThreshold, *eventFlushInterval)
if *statsdListenUDP != "" { if *statsdListenUDP != "" {
udpListenAddr := udpAddrFromString(*statsdListenUDP) udpListenAddr, err := udpAddrFromString(*statsdListenUDP)
if err != nil {
level.Error(logger).Log("msg", "invalid UDP listen address", "address", *statsdListenUDP, "error", err)
os.Exit(1)
}
uconn, err := net.ListenUDP("udp", udpListenAddr) uconn, err := net.ListenUDP("udp", udpListenAddr)
if err != nil { if err != nil {
panic(err) level.Error(logger).Log("msg", "failed to start UDP listener", "error", err)
os.Exit(1)
} }
if *readBuffer != 0 { if *readBuffer != 0 {
err = uconn.SetReadBuffer(*readBuffer) err = uconn.SetReadBuffer(*readBuffer)
if err != nil { if err != nil {
panic(fmt.Sprintf("Error setting UDP read buffer: %s", err)) level.Error(logger).Log("msg", "error setting UDP read buffer", "error", err)
os.Exit(1)
} }
} }
@ -189,10 +203,15 @@ func main() {
} }
if *statsdListenTCP != "" { if *statsdListenTCP != "" {
tcpListenAddr := tcpAddrFromString(*statsdListenTCP) tcpListenAddr, err := tcpAddrFromString(*statsdListenTCP)
if err != nil {
level.Error(logger).Log("msg", "invalid TCP listen address", "address", *statsdListenUDP, "error", err)
os.Exit(1)
}
tconn, err := net.ListenTCP("tcp", tcpListenAddr) tconn, err := net.ListenTCP("tcp", tcpListenAddr)
if err != nil { if err != nil {
panic(err) level.Error(logger).Log("msg", err)
os.Exit(1)
} }
defer tconn.Close() defer tconn.Close()
@ -203,14 +222,16 @@ func main() {
if *statsdListenUnixgram != "" { if *statsdListenUnixgram != "" {
var err error var err error
if _, err = os.Stat(*statsdListenUnixgram); !os.IsNotExist(err) { if _, err = os.Stat(*statsdListenUnixgram); !os.IsNotExist(err) {
panic(fmt.Sprintf("Unixgram socket \"%s\" already exists", *statsdListenUnixgram)) level.Error(logger).Log("msg", "Unixgram socket already exists", "socket_name", *statsdListenUnixgram)
os.Exit(1)
} }
uxgconn, err := net.ListenUnixgram("unixgram", &net.UnixAddr{ uxgconn, err := net.ListenUnixgram("unixgram", &net.UnixAddr{
Net: "unixgram", Net: "unixgram",
Name: *statsdListenUnixgram, Name: *statsdListenUnixgram,
}) })
if err != nil { if err != nil {
panic(err) level.Error(logger).Log("msg", "failed to listen on Unixgram socket", "error", err)
os.Exit(1)
} }
defer uxgconn.Close() defer uxgconn.Close()
@ -218,7 +239,8 @@ func main() {
if *readBuffer != 0 { if *readBuffer != 0 {
err = uxgconn.SetReadBuffer(*readBuffer) err = uxgconn.SetReadBuffer(*readBuffer)
if err != nil { if err != nil {
panic(fmt.Sprintf("Error setting Unixgram read buffer: %s", err)) level.Error(logger).Log("msg", "error setting Unixgram read buffer", "error", err)
os.Exit(1)
} }
} }
@ -248,12 +270,16 @@ func main() {
if *mappingConfig != "" { if *mappingConfig != "" {
err := mapper.InitFromFile(*mappingConfig, *cacheSize) err := mapper.InitFromFile(*mappingConfig, *cacheSize)
if err != nil { if err != nil {
panic(fmt.Sprintf("Error loading config: %s", err)) level.Error(logger).Log("msg", "error loading config", "error", err)
os.Exit(1)
} }
if *dumpFSMPath != "" { if *dumpFSMPath != "" {
err := dumpFSM(mapper, *dumpFSMPath, logger) err := dumpFSM(mapper, *dumpFSMPath, logger)
if err != nil { if err != nil {
panic(fmt.Sprintf("Error dumping FSM: %s", err)) level.Error(logger).Log("msg", "error dumping FSM", "error", err)
// Failure to dump the FSM is an error (the user asked for it and it
// didn't happen) but not fatal (the exporter is fully functional
// afterwards).
} }
} }
} else { } else {