github.com/xmidt-org/webpa-common@v1.11.9/service/monitor/listener.go (about) 1 package monitor 2 3 import ( 4 "sync/atomic" 5 "time" 6 7 "github.com/go-kit/kit/log" 8 "github.com/go-kit/kit/log/level" 9 "github.com/go-kit/kit/metrics/provider" 10 "github.com/go-kit/kit/sd" 11 "github.com/xmidt-org/webpa-common/logging" 12 "github.com/xmidt-org/webpa-common/service" 13 ) 14 15 // Event carries the same information as go-kit's sd.Event, but with the extra Key that identifies 16 // which filtered service or path was updated. 17 type Event struct { 18 // Key is the in-process unique identifier for the sd.Instancer that produced this event. 19 // For consul, this value does not equal the name of the service (as it includes tags, datacenters, etc.). 20 // For that purpose, use the Service field. 21 Key string 22 23 // Service, unlike Key, specifically identifies the service of the sd.Instancer that produced this event. 24 // This value is used by listeners to update metric labels. 25 Service string 26 27 // Instancer is the go-kit sd.Instancer which sent this event. This instance can be used to enrich 28 // logging via logging.Enrich. 29 Instancer sd.Instancer 30 31 // EventCount is the postive, ascending integer identifying this event's sequence, e.g. 1 refers to the first 32 // service discovery event. Useful for logging and certain types of logic, such as ignoring the initial instances from a monitor. 33 EventCount int 34 35 // Instances are the filtered instances that came from the sd.Instancer. If this is set, 36 // Err will be nil. 37 Instances []string 38 39 // Err is any service discovery error that occurred. If this is set, Instances will be empty. 40 Err error 41 42 // Stopped is set to true if and only if this event is being sent to indicate the monitoring goroutine 43 // has exited, either because of being explicitly stopped or because the environment was closed. 44 Stopped bool 45 } 46 47 type Listener interface { 48 MonitorEvent(Event) 49 } 50 51 type ListenerFunc func(Event) 52 53 func (lf ListenerFunc) MonitorEvent(e Event) { 54 lf(e) 55 } 56 57 type Listeners []Listener 58 59 func (ls Listeners) MonitorEvent(e Event) { 60 for _, v := range ls { 61 v.MonitorEvent(e) 62 } 63 } 64 65 // NewMetricsListener produces a monitor Listener that gathers metrics related to service discovery. 66 func NewMetricsListener(p provider.Provider) Listener { 67 var ( 68 errorCount = p.NewCounter(service.ErrorCount) 69 lastError = p.NewGauge(service.LastErrorTimestamp) 70 updateCount = p.NewCounter(service.UpdateCount) 71 lastUpdate = p.NewGauge(service.LastUpdateTimestamp) 72 instanceCount = p.NewGauge(service.InstanceCount) 73 ) 74 75 return ListenerFunc(func(e Event) { 76 timestamp := float64(time.Now().Unix()) 77 78 if e.Err != nil { 79 errorCount.With(service.ServiceLabel, e.Service, service.EventKeyLabel, e.Key).Add(1.0) 80 lastError.With(service.ServiceLabel, e.Service, service.EventKeyLabel, e.Key).Set(timestamp) 81 } else { 82 updateCount.With(service.ServiceLabel, e.Service, service.EventKeyLabel, e.Key).Add(1.0) 83 lastUpdate.With(service.ServiceLabel, e.Service, service.EventKeyLabel, e.Key).Set(timestamp) 84 } 85 86 instanceCount.With(service.ServiceLabel, e.Service, service.EventKeyLabel, e.Key).Set(float64(len(e.Instances))) 87 }) 88 } 89 90 // NewAccessorListener creates a service discovery Listener that dispatches accessor instances to a nested closure. 91 // Any error received from the event results in a nil Accessor together with that error being passed to the next closure. 92 // If the AccessorFactory is nil, DefaultAccessorFactory is used. If the next closure is nil, this function panics. 93 // 94 // An UpdatableAccessor may directly be used to receive events by passing Update as the next closure: 95 // ua := new(UpdatableAccessor) 96 // l := NewAccessorListener(f, ua.Update) 97 func NewAccessorListener(f service.AccessorFactory, next func(service.Accessor, error)) Listener { 98 if next == nil { 99 panic("A next closure is required to receive Accessors") 100 } 101 102 if f == nil { 103 f = service.DefaultAccessorFactory 104 } 105 106 return ListenerFunc(func(e Event) { 107 switch { 108 case e.Err != nil: 109 next(nil, e.Err) 110 111 case len(e.Instances) > 0: 112 next(f(e.Instances), nil) 113 114 default: 115 next(service.EmptyAccessor(), nil) 116 } 117 }) 118 } 119 120 func NewKeyAccessorListener(f service.AccessorFactory, key string, next func(string, service.Accessor, error)) Listener { 121 if next == nil { 122 panic("A next closure is required to receive Accessors") 123 } 124 125 if f == nil { 126 f = service.DefaultAccessorFactory 127 } 128 129 return ListenerFunc(func(e Event) { 130 switch { 131 case e.Err != nil: 132 next(key, nil, e.Err) 133 134 case len(e.Instances) > 0: 135 next(key, f(e.Instances), nil) 136 137 default: 138 next(key, service.EmptyAccessor(), nil) 139 } 140 }) 141 } 142 143 const ( 144 stateDeregistered uint32 = 0 145 stateRegistered uint32 = 1 146 ) 147 148 // NewRegistrarListener binds service registration to the lifecycle of a service discovery watch. 149 // Upon the first successful update, or on any successful update following one or more errors, the given 150 // registrar is registered. Any error that follows a successful update, or on the first error, results 151 // in deregistration. 152 func NewRegistrarListener(logger log.Logger, r sd.Registrar, initiallyRegistered bool) Listener { 153 if logger == nil { 154 logger = logging.DefaultLogger() 155 } 156 157 if r == nil { 158 panic("A registrar is required") 159 } 160 161 var state uint32 = stateDeregistered 162 if initiallyRegistered { 163 state = stateRegistered 164 } 165 166 return ListenerFunc(func(e Event) { 167 var message string 168 if e.Err != nil { 169 message = "deregistering on service discovery error" 170 } else if e.Stopped { 171 message = "deregistering due to monitor being stopped" 172 } else { 173 if atomic.CompareAndSwapUint32(&state, stateDeregistered, stateRegistered) { 174 logger.Log(level.Key(), level.InfoValue(), logging.MessageKey(), "registering on service discovery update") 175 r.Register() 176 } 177 178 return 179 } 180 181 if atomic.CompareAndSwapUint32(&state, stateRegistered, stateDeregistered) { 182 logger.Log(level.Key(), level.ErrorValue(), logging.MessageKey(), message, logging.ErrorKey(), e.Err, "stopped", e.Stopped) 183 r.Deregister() 184 } 185 }) 186 }