├── .travis.yml ├── README.md ├── LICENSE ├── healthchecks_test.go └── healthchecks.go /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.3 4 | - tip 5 | notifications: 6 | # See http://about.travis-ci.org/docs/user/build-configuration/ to learn more 7 | # about configuring notification recipients and more. 8 | email: 9 | recipients: 10 | - coda.hale@gmail.com 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | healthchecks 2 | ============ 3 | 4 | [![Build Status](https://travis-ci.org/codahale/healthchecks.png?branch=master)](https://travis-ci.org/codahale/healthchecks) 5 | 6 | A Go package which exposes health check results as expvars. 7 | 8 | For documentation, check [godoc](http://godoc.org/github.com/codahale/healthchecks). 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Coda Hale 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /healthchecks_test.go: -------------------------------------------------------------------------------- 1 | package healthchecks 2 | 3 | import ( 4 | "encoding/json" 5 | "expvar" 6 | "fmt" 7 | "reflect" 8 | "testing" 9 | "time" 10 | ) 11 | 12 | func TestHealthchecks(t *testing.T) { 13 | start := time.Now() 14 | v := expvar.Get("healthchecks").String() 15 | elapsed := time.Now().Sub(start) 16 | 17 | if elapsed.Seconds() > 1.2 { 18 | t.Errorf("Took %v, not running in parallel", elapsed) 19 | } 20 | 21 | var actual map[string]string 22 | if err := json.Unmarshal([]byte(v), &actual); err != nil { 23 | t.Fatal(err) 24 | } 25 | 26 | expected := map[string]string{ 27 | "good": "OK", 28 | "bad": "bad thing happen", 29 | "ugly": "well dang", 30 | "slow1": "OK", 31 | "slow2": "OK", 32 | } 33 | 34 | if !reflect.DeepEqual(actual, expected) { 35 | t.Errorf("Was %#v, but expected %#v", actual, expected) 36 | } 37 | } 38 | 39 | func init() { 40 | Add("good", func() error { 41 | return nil 42 | }) 43 | 44 | Add("bad", func() error { 45 | return fmt.Errorf("bad thing happen") 46 | }) 47 | 48 | Add("ugly", func() error { 49 | panic("well dang") 50 | }) 51 | 52 | Add("slow1", func() error { 53 | time.Sleep(1 * time.Second) 54 | return nil 55 | }) 56 | 57 | Add("slow2", func() error { 58 | time.Sleep(1 * time.Second) 59 | return nil 60 | }) 61 | } 62 | -------------------------------------------------------------------------------- /healthchecks.go: -------------------------------------------------------------------------------- 1 | // Package healthchecks provides functionality to add application-level 2 | // healthchecks which evaluate the health of components at runtime and expose 3 | // the results as expvars. 4 | package healthchecks 5 | 6 | import ( 7 | "expvar" 8 | "fmt" 9 | "sync" 10 | ) 11 | 12 | // A Healthcheck checks the functionality of a component of your application and 13 | // returns and error if anything is wrong. 14 | type Healthcheck func() error 15 | 16 | // Add adds the given healthcheck to the set of known healthchecks. 17 | func Add(name string, healthcheck Healthcheck) { 18 | healthchecksMutex.Lock() 19 | defer healthchecksMutex.Unlock() 20 | 21 | healthchecks[name] = healthcheck 22 | } 23 | 24 | func execAll() map[string]string { 25 | healthchecksMutex.Lock() 26 | defer healthchecksMutex.Unlock() 27 | 28 | var m sync.Mutex 29 | var wg sync.WaitGroup 30 | wg.Add(len(healthchecks)) 31 | 32 | results := make(map[string]string, len(healthchecks)) 33 | for name, healthcheck := range healthchecks { 34 | go func(name string, healthcheck Healthcheck) { 35 | defer wg.Done() 36 | 37 | res := exec(healthcheck) 38 | 39 | m.Lock() 40 | defer m.Unlock() 41 | 42 | results[name] = res 43 | }(name, healthcheck) 44 | } 45 | wg.Wait() 46 | return results 47 | } 48 | 49 | func exec(hc Healthcheck) (s string) { 50 | defer func() { 51 | err := recover() 52 | if err != nil { 53 | s = fmt.Sprintf("%v", err) 54 | } 55 | }() 56 | 57 | err := hc() 58 | if err != nil { 59 | return err.Error() 60 | } 61 | return "OK" 62 | } 63 | 64 | var ( 65 | healthchecks = make(map[string]Healthcheck) 66 | healthchecksMutex sync.Mutex 67 | ) 68 | 69 | func init() { 70 | expvar.Publish("healthchecks", expvar.Func(func() interface{} { 71 | return execAll() 72 | })) 73 | } 74 | --------------------------------------------------------------------------------