3
0

Add healthcheck package

This commit is contained in:
Denis Arh 2020-07-11 13:24:58 +02:00
parent c12d537f00
commit 8903278b29
3 changed files with 202 additions and 0 deletions

118
pkg/healthcheck/check.go Normal file
View File

@ -0,0 +1,118 @@
package healthcheck
import (
"bytes"
"context"
"fmt"
"io"
"strings"
)
type (
checkFn func(ctx context.Context) error
Meta struct {
Label string
Description string
}
check struct {
fn checkFn
*Meta
}
result struct {
err error
*Meta
}
results []*result
checks struct {
cc []*check
}
)
var (
defaults *checks
)
func init() {
defaults = New()
}
func Defaults() *checks {
return defaults
}
func New() *checks {
return &checks{cc: []*check{}}
}
// Add appends new check
func (c *checks) Add(fn checkFn, label string, description ...string) {
c.cc = append(c.cc, &check{fn, &Meta{Label: label, Description: strings.Join(description, "")}})
}
func (c checks) Run(ctx context.Context) results {
var rr = make([]*result, len(c.cc))
for i, c := range c.cc {
rr[i] = &result{c.fn(ctx), c.Meta}
}
return rr
}
func (rr results) Healthy() bool {
for _, c := range rr {
if c.err != nil {
return false
}
}
return true
}
func (rr results) String() string {
buf := &bytes.Buffer{}
rr.WriteTo(buf)
return buf.String()
}
func (rr results) WriteTo(w io.Writer) {
var (
p = func(f string, aa ...interface{}) {
_, _ = fmt.Fprintf(w, f, aa...)
}
)
for _, r := range rr {
if r.IsHealthy() {
p("PASS")
} else {
p("FAIL")
}
p(" %s", r.Label)
if !r.IsHealthy() {
p(": %v", r.Error())
}
p("\n")
}
}
func (r *result) IsHealthy() bool {
return r != nil && r.err == nil
}
func (r *result) Error() string {
if r == nil || r.err == nil {
return ""
}
return r.err.Error()
}

View File

@ -0,0 +1,68 @@
package healthcheck
import (
"context"
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
func Test_Healthy(t *testing.T) {
tests := []struct {
name string
checks []*check
healthy bool
string string
}{
{
"should be healthy with handle for stringer output",
[]*check{{func(ctx context.Context) error { return nil }, &Meta{Label: "check01"}}},
true,
"PASS check01\n",
},
{
"should handle multiple healthy checks",
[]*check{
{func(ctx context.Context) error { return nil }, &Meta{Label: "check01"}},
{func(ctx context.Context) error { return nil }, &Meta{Label: "check02"}},
},
true,
"PASS check01\nPASS check02\n",
},
{
"should handle healthy and unhealthy checks",
[]*check{
{func(ctx context.Context) error { return nil }, &Meta{Label: "check01"}},
{func(ctx context.Context) error { return fmt.Errorf("x") }, &Meta{Label: "check02"}},
{func(ctx context.Context) error { return nil }, &Meta{Label: "check03"}},
},
false,
"PASS check01\nFAIL check02: x\nPASS check03\n",
},
{
"should handle labels",
[]*check{
{func(ctx context.Context) error { return nil }, &Meta{Label: "check01"}},
{func(ctx context.Context) error { return nil }, &Meta{Label: "Pretty check"}},
},
true,
"PASS check01\nPASS Pretty check\n",
},
{
"should handle empty check list",
[]*check{},
true,
"",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
a := assert.New(t)
r := (&checks{cc: tt.checks}).Run(context.Background())
a.Equal(tt.healthy, r.Healthy(), "healthy result failed")
a.Equal(tt.string, r.String(), "stringer output match failed")
})
}
}

View File

@ -0,0 +1,16 @@
package healthcheck
import "net/http"
func HttpHandler() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
results := Defaults().Run(r.Context())
if results.Healthy() {
w.WriteHeader(http.StatusOK)
} else {
w.WriteHeader(http.StatusInternalServerError)
}
results.WriteTo(w)
}
}