Skip to content

Commit

Permalink
Merge 52432ab into 9294456
Browse files Browse the repository at this point in the history
  • Loading branch information
bivas committed Jan 28, 2021
2 parents 9294456 + 52432ab commit 64837b7
Show file tree
Hide file tree
Showing 18 changed files with 562 additions and 138 deletions.
44 changes: 37 additions & 7 deletions .github/workflows/go.yaml
@@ -1,5 +1,5 @@
name: go-build
on: [push, pull_request]
on: [ push, pull_request ]
jobs:
build:
name: build ( ${{ matrix.go }} ), test, lint
Expand All @@ -21,17 +21,17 @@ jobs:
GOPROXY: "https://proxy.golang.org"
run: go build .

- name: Test
env:
GOPROXY: "https://proxy.golang.org"
run: go test -v -race -coverprofile=coverage.out ./...

- name: Install golangci-lint
run: curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh| sh -s -- -b $(go env GOPATH)/bin v1.20.0

- name: Run golangci-lint
run: golangci-lint run -E golint -E gosec -E gofmt

- name: Test
env:
GOPROXY: "https://proxy.golang.org"
run: go test -v -race -coverprofile=coverage.out ./...

- name: convert to lcov
if: ${{ matrix.go }} == '1.15'
run: |
Expand All @@ -43,4 +43,34 @@ jobs:
if: ${{ matrix.go }} == '1.15'
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
path-to-lcov: coverage.lcov
path-to-lcov: coverage.lcov
build-opencencus-check-listener:
name: build ( ${{ matrix.go }} ), test, lint for opencensus
runs-on: ubuntu-latest
strategy:
matrix:
go: [ '1.15', '1.14', '1.13' ]
steps:
- name: Check out source code
uses: actions/checkout@v2

- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go }}

- name: Build
env:
GOPROXY: "https://proxy.golang.org"
run: cd opencensus && go build .

- name: Install golangci-lint
run: curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh| sh -s -- -b $(go env GOPATH)/bin v1.20.0

- name: Run golangci-lint
run: cd opencensus && golangci-lint run -E golint -E gosec -E gofmt

- name: Test
env:
GOPROXY: "https://proxy.golang.org"
run: cd opencensus && go test -v -race -coverprofile=coverage.out ./...
57 changes: 50 additions & 7 deletions README.md
Expand Up @@ -90,7 +90,8 @@ gosundheit.New(options ...Option)
```
The optional parameters of `options` allows the user to configure the Health Service by passing configuration functions (implementing `Option` signature).
All options are marked with the prefix `WithX`. Available options:
- `WithCheckListener`
- `WithCheckListeners` - enables you to act on check registration, start and completed events
- `WithHealthListeners` - enables you to act on changes in the health service results

### Built-in Checks
The library comes with a set of built-in checks.
Expand Down Expand Up @@ -321,6 +322,10 @@ For example, lets add a logging listener to our health repository:
```go
type checkEventsLogger struct{}

func (l checkEventsLogger) OnCheckRegistered(name string, res gosundheit.Result) {
log.Printf("Check %q registered with initial result: %v\n", name, res)
}

func (l checkEventsLogger) OnCheckStarted(name string) {
log.Printf("Check %q started...\n", name)
}
Expand All @@ -332,13 +337,33 @@ func (l checkEventsLogger) OnCheckCompleted(name string, res gosundheit.Result)

To register your listener:
```go
h := gosundheit.New(gosundheit.WithCheckListener(&checkEventsLogger))
h := gosundheit.New(gosundheit.WithCheckListeners(&checkEventsLogger))
```

Please note that your `CheckListener` implementation must not block!

### HealthListener
It is something desired to track changes in registered checks results.
For example, you may want to log the amount of results monitored, or send metrics on these results.

The `gosundheit.HealthListener` interface allows you to hook this custom logic.

For example, lets add a logging listener:
```go
type healthLogger struct{}

func (l healthLogger) OnResultsUpdated(results map[string]Result) {
log.Printf("There are %d results, general health is %t\n", len(results), allHealthy(results))
}
```

To register your listener:
```go
h := gosundheit.New(gosundheit.WithHealthListeners(&checkHealthLogger))
```

## Metrics
The library exposes the following OpenCensus view for your convenience:
The library can expose metrics using a `CheckListener`. At the moment, OpenCensus is available and exposes the following metrics:
* `health/check_status_by_name` - An aggregated health status gauge (0/1 for fail/pass) at the time of sampling.
The aggregation uses the following tags:
* `check=allChecks` - all checks aggregation
Expand All @@ -355,12 +380,30 @@ The views can be registered like so:
```go
import (
"github.com/AppsFlyer/go-sundheit"
"github.com/AppsFlyer/go-sundheit/opencensus"
"go.opencensus.io/stats/view"
)

h := gosundheit.New()
// This listener can act both as check and health listener for reporting metrics
oc := opencencus.NewMetricsListener()
h := gosundheit.New(gosundheit.WithCheckListener(oc), gosundheit.WithHealthListener(oc))
// ...
view.Register(gosundheit.DefaultHealthViews...)
view.Register(opencencus.DefaultHealthViews...)
// or register individual views. For example:
view.Register(gosundheit.ViewCheckExecutionTime, gosundheit.ViewCheckStatusByName, ...)
view.Register(opencencus.ViewCheckExecutionTime, opencencus.ViewCheckStatusByName, ...)
```

### Classification

It is sometimes required to report metrics for different check types (e.g. setup, liveness, readiness).
To report metrics using `classification` tag - it's possible to initialize the OpenCencus listener with classification:

```go
// startup
opencencus.NewMetricsListener(opencencus.WithStartupClassification())
// liveness
opencencus.NewMetricsListener(opencencus.WithLivenessClassification())
// readiness
opencencus.NewMetricsListener(opencencus.WithReadinessClassification())
// custom
opencencus.NewMetricsListener(opencencus.WithClassification("custom"))
```
25 changes: 20 additions & 5 deletions check_listener.go
Expand Up @@ -6,6 +6,10 @@ package gosundheit
// It's OK to log in the implementation and it's OK to add metrics, but it's not OK to run anything that
// takes long time to complete such as network IO etc.
type CheckListener interface {
// OnCheckRegistered is called when the check with the specified name has registered.
// Result argument is for reporting the first run state of the check
OnCheckRegistered(name string, result Result)

// OnCheckStarted is called when a check with the specified name has started
OnCheckStarted(name string)

Expand All @@ -14,11 +18,22 @@ type CheckListener interface {
OnCheckCompleted(name string, result Result)
}

type noopCheckListener struct{}
type CheckListeners []CheckListener

func (noop noopCheckListener) OnCheckStarted(_ string) {}
func (c CheckListeners) OnCheckRegistered(name string, result Result) {
for _, listener := range c {
listener.OnCheckRegistered(name, result)
}
}

func (noop noopCheckListener) OnCheckCompleted(_ string, _ Result) {}
func (c CheckListeners) OnCheckStarted(name string) {
for _, listener := range c {
listener.OnCheckStarted(name)
}
}

// make sure noopCheckListener implements the CheckListener interface
var _ CheckListener = noopCheckListener{}
func (c CheckListeners) OnCheckCompleted(name string, result Result) {
for _, listener := range c {
listener.OnCheckCompleted(name, result)
}
}
4 changes: 0 additions & 4 deletions go.mod
Expand Up @@ -4,11 +4,7 @@ go 1.15

require (
github.com/fortytw2/leaktest v1.3.0
github.com/kr/pretty v0.1.0 // indirect
github.com/pkg/errors v0.8.1
github.com/stretchr/objx v0.2.0 // indirect
github.com/stretchr/testify v1.4.0
go.opencensus.io v0.22.1
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
gopkg.in/yaml.v2 v2.2.4 // indirect
)
1 change: 1 addition & 0 deletions go.sum
@@ -1,4 +1,5 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/AppsFlyer/go-sundheit v0.2.0/go.mod h1:rCRkVTMQo7/krF7xQ9X0XEF1an68viFR6/Gy02q+4ds=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
Expand Down
38 changes: 14 additions & 24 deletions health.go
Expand Up @@ -6,7 +6,6 @@ import (
"time"

"github.com/pkg/errors"
"go.opencensus.io/stats"
)

// Health is the API for registering / deregistering health checks, and for fetching the health checks results.
Expand Down Expand Up @@ -46,7 +45,8 @@ func New(opts ...Option) Health {
type health struct {
results map[string]Result
checkTasks map[string]checkTask
checksListener CheckListener
checksListener CheckListeners
healthListener HealthListeners
lock sync.RWMutex
}

Expand All @@ -61,7 +61,8 @@ func (h *health) RegisterCheck(cfg *Config) error {
initialErr = fmt.Errorf(initialResultMsg)
}

h.updateResult(cfg.Check.Name(), initialResultMsg, 0, initialErr, time.Now())
result := h.updateResult(cfg.Check.Name(), initialResultMsg, 0, initialErr, time.Now())
h.checksListener.OnCheckRegistered(cfg.Check.Name(), result)
h.scheduleCheck(h.createCheckTask(cfg), cfg)
return nil
}
Expand Down Expand Up @@ -97,17 +98,24 @@ func (h *health) scheduleCheck(task *checkTask, cfg *Config) {
if !h.runCheckOrStop(task, time.After(cfg.InitialDelay)) {
return
}

h.reportResults()
// scheduled recurring execution
task.ticker = time.NewTicker(cfg.ExecutionPeriod)
for {
if !h.runCheckOrStop(task, task.ticker.C) {
return
}
h.reportResults()
}
}()
}

func (h *health) reportResults() {
h.lock.RLock()
defer h.lock.RUnlock()
h.healthListener.OnResultsUpdated(copyResultsMap(h.results))
}

func (h *health) runCheckOrStop(task *checkTask, timerChan <-chan time.Time) bool {
select {
case <-task.stopChan:
Expand Down Expand Up @@ -141,8 +149,8 @@ func (h *health) DeregisterAll() {
h.lock.RLock()
defer h.lock.RUnlock()

for k := range h.checkTasks {
h.Deregister(k)
for _, task := range h.checkTasks {
task.stopChan <- true
}
}

Expand Down Expand Up @@ -198,23 +206,5 @@ func (h *health) updateResult(
}

h.results[name] = result
h.recordStats(name, result)

return result
}

func (h *health) recordStats(checkName string, result Result) {
thisCheckCtx := createMonitoringCtx(checkName, result.IsHealthy())
stats.Record(thisCheckCtx, mCheckDuration.M(float64(result.Duration)/float64(time.Millisecond)))
stats.Record(thisCheckCtx, mCheckStatus.M(status(result.IsHealthy()).asInt64()))

allHealthy := allHealthy(h.results)
allChecksCtx := createMonitoringCtx(ValAllChecks, allHealthy)
stats.Record(allChecksCtx, mCheckStatus.M(status(allHealthy).asInt64()))
}

func (h *health) WithCheckListener(listener CheckListener) {
if listener != nil {
h.checksListener = listener
}
}
13 changes: 13 additions & 0 deletions health_listener.go
@@ -0,0 +1,13 @@
package gosundheit

type HealthListener interface {
OnResultsUpdated(results map[string]Result)
}

type HealthListeners []HealthListener

func (h HealthListeners) OnResultsUpdated(results map[string]Result) {
for _, listener := range h {
listener.OnResultsUpdated(results)
}
}

0 comments on commit 64837b7

Please sign in to comment.