1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158
|
// Copyright 2014 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
// Package debugstatus provides facilities for inspecting information
// about a running HTTP service.
package debugstatus
import (
"fmt"
"sync"
"time"
"golang.org/x/net/context"
"gopkg.in/mgo.v2"
)
// Check collects the status check results from the given checkers.
func Check(ctx context.Context, checkers ...CheckerFunc) map[string]CheckResult {
var mu sync.Mutex
results := make(map[string]CheckResult, len(checkers))
var wg sync.WaitGroup
for _, c := range checkers {
c := c
wg.Add(1)
go func() {
defer wg.Done()
t0 := time.Now()
key, result := c(ctx)
result.Duration = time.Since(t0)
mu.Lock()
results[key] = result
mu.Unlock()
}()
}
wg.Wait()
return results
}
// CheckResult holds the result of a single status check.
type CheckResult struct {
// Name is the human readable name for the check.
Name string
// Value is the check result.
Value string
// Passed reports whether the check passed.
Passed bool
// Duration holds the duration that the
// status check took to run.
Duration time.Duration
}
// CheckerFunc represents a function returning the check machine friendly key
// and the result.
type CheckerFunc func(ctx context.Context) (key string, result CheckResult)
// StartTime holds the time that the code started running.
var StartTime = time.Now().UTC()
// ServerStartTime reports the time when the application was started.
func ServerStartTime(context.Context) (key string, result CheckResult) {
return "server_started", CheckResult{
Name: "Server started",
Value: StartTime.String(),
Passed: true,
}
}
// Connection returns a status checker reporting whether the given Pinger is
// connected.
func Connection(p Pinger) CheckerFunc {
return func(context.Context) (key string, result CheckResult) {
result.Name = "MongoDB is connected"
if err := p.Ping(); err != nil {
result.Value = "Ping error: " + err.Error()
return "mongo_connected", result
}
result.Value = "Connected"
result.Passed = true
return "mongo_connected", result
}
}
// Pinger is an interface that wraps the Ping method.
// It is implemented by mgo.Session.
type Pinger interface {
Ping() error
}
var _ Pinger = (*mgo.Session)(nil)
// MongoCollections returns a status checker checking that all the
// expected Mongo collections are present in the database.
func MongoCollections(c Collector) CheckerFunc {
return func(context.Context) (key string, result CheckResult) {
key = "mongo_collections"
result.Name = "MongoDB collections"
names, err := c.CollectionNames()
if err != nil {
result.Value = "Cannot get collections: " + err.Error()
return key, result
}
var missing []string
for _, coll := range c.Collections() {
found := false
for _, name := range names {
if name == coll.Name {
found = true
break
}
}
if !found {
missing = append(missing, coll.Name)
}
}
if len(missing) == 0 {
result.Value = "All required collections exist"
result.Passed = true
return key, result
}
result.Value = fmt.Sprintf("Missing collections: %s", missing)
return key, result
}
}
// Collector is an interface that groups the methods used to check that
// a Mongo database has the expected collections.
// It is usually implemented by types extending mgo.Database to add the
// Collections() method.
type Collector interface {
// Collections returns the Mongo collections that we expect to exist in
// the Mongo database.
Collections() []*mgo.Collection
// CollectionNames returns the names of the collections actually present in
// the Mongo database.
CollectionNames() ([]string, error)
}
// Rename changes the key and/or result name returned by the given check.
// It is possible to pass an empty string to avoid changing one of the values.
// This means that if both key are name are empty, this closure is a no-op.
func Rename(newKey, newName string, check CheckerFunc) CheckerFunc {
return func(ctx context.Context) (key string, result CheckResult) {
key, result = check(ctx)
if newKey == "" {
newKey = key
}
if newName != "" {
result.Name = newName
}
return newKey, result
}
}
|