github.com/blend/go-sdk@v1.20220411.3/statsd/server.go (about) 1 /* 2 3 Copyright (c) 2022 - Present. Blend Labs, Inc. All rights reserved 4 Use of this source code is governed by a MIT license that can be found in the LICENSE file. 5 6 */ 7 8 package statsd 9 10 import ( 11 "fmt" 12 "log" 13 "net" 14 "strings" 15 ) 16 17 // Server is a listener for statsd metrics. 18 // It is meant to be used for diagnostic purposes, and is not suitable for 19 // production anything. 20 type Server struct { 21 Addr string 22 Log *log.Logger 23 MaxPacketSize int 24 Listener net.PacketConn 25 Handler func(...Metric) 26 } 27 28 // MaxPacketSizeOrDefault returns the max packet size or a default. 29 func (s *Server) MaxPacketSizeOrDefault() int { 30 if s.MaxPacketSize > 0 { 31 return s.MaxPacketSize 32 } 33 return DefaultMaxPacketSize 34 } 35 36 // Start starts the server. This call blocks. 37 func (s *Server) Start() error { 38 var err error 39 if s.Handler == nil { 40 return fmt.Errorf("server cannot start; no handler provided") 41 } 42 if s.Listener == nil && s.Addr != "" { 43 s.Listener, err = NewUDPListener(s.Addr) 44 if err != nil { 45 return err 46 } 47 } 48 if s.Listener == nil { 49 return fmt.Errorf("server cannot start; no listener or addr provided") 50 } 51 52 s.logf("statsd server listening: %s", s.Listener.LocalAddr().String()) 53 data := make([]byte, s.MaxPacketSizeOrDefault()) 54 var metrics []Metric 55 var n int 56 for { 57 n, _, err = s.Listener.ReadFrom(data) 58 if IsErrUseOfClosedNetworkConnection(err) { 59 return nil 60 } else if err != nil { 61 return err 62 } 63 metrics, err = s.parseMetrics(data[:n]) 64 if err != nil { 65 return err 66 } 67 s.Handler(metrics...) 68 } 69 } 70 71 // Stop closes the server. 72 func (s *Server) Stop() error { 73 if s.Listener == nil { 74 return nil 75 } 76 return s.Listener.Close() 77 } 78 79 func (s *Server) parseMetrics(data []byte) ([]Metric, error) { 80 var metrics []Metric 81 for index := 0; index < len(data); index++ { 82 m, err := s.parseMetric(&index, data) 83 if err != nil { 84 return nil, err 85 } 86 metrics = append(metrics, m) 87 } 88 return metrics, nil 89 } 90 91 // parseMetric parses a metric from a given data packet. 92 func (s *Server) parseMetric(index *int, data []byte) (m Metric, err error) { 93 var name []byte 94 var metricType []byte 95 var value []byte 96 var tag []byte 97 98 const ( 99 stateName = iota 100 stateValue = iota 101 stateMetricType = iota 102 stateTags = iota 103 stateTagValues = iota 104 ) 105 106 var b byte 107 state := stateName 108 for ; *index < len(data); (*index)++ { 109 b = data[*index] 110 111 if b == '\n' { 112 break // drop out at newline 113 } 114 115 switch state { 116 case stateName: //name 117 if b == ':' { 118 state = stateValue 119 continue 120 } 121 name = append(name, b) 122 continue 123 case stateValue: //value 124 if b == '|' { 125 state = stateMetricType 126 continue 127 } 128 value = append(value, b) 129 continue 130 case stateMetricType: // metric type 131 if b == '|' { 132 state = stateTags 133 continue 134 } 135 metricType = append(metricType, b) 136 continue 137 case stateTags: // tags 138 if b == '#' { 139 state = stateTagValues 140 continue 141 } 142 err = fmt.Errorf("invalid metric; tags should be marked with '#'") 143 return 144 case stateTagValues: 145 if b == ',' { 146 m.Tags = append(m.Tags, string(tag)) 147 tag = nil 148 continue 149 } 150 tag = append(tag, b) 151 } 152 } 153 if len(tag) > 0 { 154 m.Tags = append(m.Tags, string(tag)) 155 } 156 157 m.Name = string(name) 158 m.Type = string(metricType) 159 m.Value = string(value) 160 return 161 } 162 163 // 164 // logging 165 // 166 167 func (s *Server) logf(format string, args ...interface{}) { 168 if s.Log != nil { 169 format = strings.TrimSpace(format) 170 s.Log.Printf(format+"\n", args...) 171 } 172 } 173 174 func (s *Server) logln(args ...interface{}) { 175 if s.Log != nil { 176 s.Log.Println(args...) 177 } 178 } 179 180 // FormatContent