func (h *Health) safeGetStates() map[string]State {
	h.statesLock.Lock()
	defer h.statesLock.Unlock()
	statesCopy := make(map[string]State, 0)
	for k, v := range h.states {
		statesCopy[k] = v
	}
	return statesCopy
}
get all states in a concurrency-safe manner
package health
import (
	"errors"
	"sync"
	"time"
	"github.com/InVisionApp/go-logger"
)
var (
	ErrNoAddCfgWhenActive = errors.New("Unable to add new check configuration(s) while healthcheck is active")
	ErrAlreadyRunning = errors.New("Healthcheck is already running - nothing to start")
	ErrAlreadyStopped = errors.New("Healthcheck is not running - nothing to stop")
	ErrEmptyConfigs = errors.New("Configs appears to be empty - nothing to add")
)
type IHealth interface {
	AddChecks(cfgs []*Config) error
	AddCheck(cfg *Config) error
	Start() error
	Stop() error
	State() (map[string]State, bool, error)
	Failed() bool
}
type ICheckable interface {
	Status() (interface{}, error)
}
type IStatusListener interface {
	HealthCheckFailed(entry *State)
	HealthCheckRecovered(entry *State, recordedFailures int64, failureDurationSeconds float64)
}
type Config struct {
	Name string
	Checker ICheckable
	Interval time.Duration
	Fatal bool
	OnComplete func(state *State)
}
type State struct {
	Name string `json:"name"`
	Status string `json:"status"`
	Err string `json:"error,omitempty"`
	Fatal bool `json:"fatal,omitempty"`
	Details interface{} `json:"details,omitempty"`  
	CheckTime time.Time `json:"check_time"`
	ContiguousFailures int64     `json:"num_failures"`      
	TimeOfFirstFailure time.Time `json:"first_failure_at"`  
}
func (s *State) isFailure() bool {
	return s.Status == "failed"
}
type Health struct {
	Logger log.Logger
	StatusListener IStatusListener
	active     *sBool  
	configs    []*Config
	states     map[string]State
	statesLock sync.Mutex
	runners    map[string]chan struct{}  
}
func New() *Health {
	return &Health{
		Logger:     log.NewSimple(),
		configs:    make([]*Config, 0),
		states:     make(map[string]State, 0),
		runners:    make(map[string]chan struct{}, 0),
		active:     newBool(),
		statesLock: sync.Mutex{},
	}
}
func (h *Health) DisableLogging() {
	h.Logger = log.NewNoop()
}
func (h *Health) AddChecks(cfgs []*Config) error {
	if h.active.val() {
		return ErrNoAddCfgWhenActive
	}
	h.configs = append(h.configs, cfgs...)
	return nil
}
func (h *Health) AddCheck(cfg *Config) error {
	if h.active.val() {
		return ErrNoAddCfgWhenActive
	}
	h.configs = append(h.configs, cfg)
	return nil
}
func (h *Health) Start() error {
	if h.active.val() {
		return ErrAlreadyRunning
	}
	if len(h.configs) < 1 {
		return nil
	}
	for _, c := range h.configs {
		h.Logger.WithFields(log.Fields{"name": c.Name}).Debug("Starting checker")
		ticker := time.NewTicker(c.Interval)
		stop := make(chan struct{})
		h.startRunner(c, ticker, stop)
		h.runners[c.Name] = stop
	}
	h.active.setTrue()
	return nil
}
func (h *Health) Stop() error {
	if !h.active.val() {
		return ErrAlreadyStopped
	}
	for name, stop := range h.runners {
		h.Logger.WithFields(log.Fields{"name": name}).Debug("Stopping checker")
		close(stop)
	}
	h.runners = make(map[string]chan struct{}, 0)
	h.safeResetStates()
	return nil
}
func (h *Health) State() (map[string]State, bool, error) {
	return h.safeGetStates(), h.Failed(), nil
}
func (h *Health) Failed() bool {
	for _, val := range h.safeGetStates() {
		if val.Fatal && val.isFailure() {
			return true
		}
	}
	return false
}
func (h *Health) startRunner(cfg *Config, ticker *time.Ticker, stop <-chan struct{}) {
	checkFunc := func() {
		data, err := cfg.Checker.Status()
		stateEntry := &State{
			Name:      cfg.Name,
			Status:    "ok",
			Details:   data,
			CheckTime: time.Now(),
			Fatal:     cfg.Fatal,
		}
		if err != nil {
			h.Logger.WithFields(log.Fields{
				"check": cfg.Name,
				"fatal": cfg.Fatal,
				"err":   err,
			}).Error("healthcheck has failed")
			stateEntry.Err = err.Error()
			stateEntry.Status = "failed"
		}
		h.safeUpdateState(stateEntry)
		if cfg.OnComplete != nil {
			go cfg.OnComplete(stateEntry)
		}
	}
	go func() {
		defer ticker.Stop()
		checkFunc()
	RunLoop:
		for {
			select {
			case <-ticker.C:
				checkFunc()
			case <-stop:
				break RunLoop
			}
		}
		h.Logger.WithFields(log.Fields{"name": cfg.Name}).Debug("Checker exiting")
	}()
}
func (h *Health) safeResetStates() {
	h.statesLock.Lock()
	defer h.statesLock.Unlock()
	h.states = make(map[string]State, 0)
}
func (h *Health) safeUpdateState(stateEntry *State) {
	h.handleStatusListener(stateEntry)
	h.statesLock.Lock()
	defer h.statesLock.Unlock()
	h.states[stateEntry.Name] = *stateEntry
}