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