├── .editorconfig ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── custom.md │ └── feature_request.md ├── pull_request_template.md └── workflows │ ├── build.yaml │ ├── test.yaml │ └── traffic2badge.yaml ├── .gitignore ├── .vscode └── launch.json ├── LICENSE ├── Makefile ├── README.md ├── _config.yml ├── docker-compose.yaml ├── docs └── DEVELOPING.md ├── go.mod ├── go.sum └── pkg └── healthcheck ├── health_checker.go ├── health_checker_test.go └── structs.go /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | # Use 4 spaces for the Python files 13 | [*.py] 14 | indent_size = 4 15 | max_line_length = 80 16 | 17 | # The JSON files contain newlines inconsistently 18 | [*.json] 19 | insert_final_newline = ignore 20 | 21 | # Minified JavaScript files shouldn't be changed 22 | [**.min.js] 23 | indent_style = ignore 24 | insert_final_newline = ignore 25 | 26 | # Makefiles always use tabs for indentation 27 | [Makefile] 28 | indent_style = tab 29 | 30 | # Batch files use tabs for indentation 31 | [*.bat] 32 | indent_style = tab 33 | 34 | [*.md] 35 | trim_trailing_whitespace = false 36 | 37 | [*.{go,mod,sum}] 38 | indent_style = tab 39 | indent_size = 4 40 | 41 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: 'bug' 6 | assignees: '@gritzkoo' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 16 | **Expected behavior** 17 | A clear and concise description of what you expected to happen. 18 | 19 | **Screenshots** 20 | If applicable, add screenshots to help explain your problem. 21 | 22 | **Additional context** 23 | Add any other context about the problem here. 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/custom.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Custom issue template 3 | about: Describe this issue template's purpose here. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | # Golang Health Checker PR 2 | 3 | Thanks for helping upgrade this package! 4 | 5 | ## What king of changes this PR produces? 6 | 7 | - [ ] Bug fix 8 | - [ ] go.mod dependency updates 9 | - [ ] Refactoring 10 | - [ ] CI related changes 11 | - [ ] New Feature 12 | - [ ] Documentation updates 13 | - [ ] Others, please describe: ... 14 | 15 | // 16 | 17 | ## Describe what problems this PR is doing! 18 | 19 | // 20 | 21 | ## This PR produce a `BREAKING CHAGES` ? 22 | 23 | - [ ] Yes (please comment below the changes!) 24 | - [ ] No 25 | 26 | // 27 | -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: build 2 | on: [push] 3 | jobs: 4 | build: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - name: checkout 8 | uses: actions/checkout@v4 9 | - name: setup golang 10 | uses: actions/setup-go@v1 11 | with: 12 | go-version: "1.23" 13 | - name: build package 14 | # env: 15 | # CGO_ENABLED: 0 16 | # GOOS: linux 17 | run: | 18 | go mod tidy 19 | go build -race -a -installsuffix cgo -o entrypoint pkg/**/*.go 20 | -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: test 2 | on: [push] 3 | jobs: 4 | test: 5 | runs-on: ubuntu-latest 6 | strategy: 7 | matrix: 8 | redis-version: [6] 9 | go: ["1.24"] 10 | steps: 11 | - uses: actions/checkout@v4 12 | - name: Sets up a Memcached server 13 | uses: niden/actions-memcached@v7 14 | - name: Start Redis 15 | uses: supercharge/redis-github-action@1.1.0 16 | with: 17 | redis-version: ${{ matrix.redis-version }} 18 | - uses: actions/setup-go@v5 19 | with: 20 | go-version: ${{ matrix.go }} 21 | - name: install deps 22 | run: go mod tidy 23 | - name: run test 24 | run: go test -race -v -coverprofile=profile.cov ./... 25 | 26 | - name: Send coverage to coverall.io 27 | uses: shogo82148/actions-goveralls@v1 28 | with: 29 | path-to-profile: profile.cov 30 | flag-name: Go-${{ matrix.go }} 31 | parallel: true 32 | # notifies that all test jobs are finished. 33 | 34 | finish: 35 | needs: test 36 | runs-on: ubuntu-latest 37 | steps: 38 | - uses: shogo82148/actions-goveralls@v1 39 | with: 40 | parallel-finished: true 41 | 42 | release-on-push: 43 | runs-on: ubuntu-latest 44 | needs: 45 | - test 46 | - finish 47 | if: github.event_name == 'push' && github.ref == 'refs/heads/master' 48 | env: 49 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 50 | steps: 51 | - uses: rymndhng/release-on-push-action@master 52 | id: release_package 53 | with: 54 | bump_version_scheme: patch 55 | - name: setup go server 56 | uses: actions/setup-go@v1 57 | with: 58 | go-version: "1.24" 59 | - name: update go.pkg.dev 60 | env: 61 | GOPROXY: https://proxy.golang.org 62 | GO111MODULE: "on" 63 | VERSION: ${{steps.release_package.outputs.version}} 64 | run: | 65 | go mod init pipeline 66 | go get github.com/${GITHUB_REPOSITORY}@${VERSION} 67 | -------------------------------------------------------------------------------- /.github/workflows/traffic2badge.yaml: -------------------------------------------------------------------------------- 1 | name: traffic2badge 2 | on: 3 | workflow_dispatch: 4 | push: 5 | branches: 6 | - main 7 | schedule: 8 | - cron: '1 0 * * *' #UTC 9 | 10 | jobs: 11 | run: 12 | name: Make GitHub Traffic to Badge 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout Code 16 | uses: actions/checkout@v4 17 | 18 | - name: Get Commit Message 19 | id: message 20 | uses: actions/github-script@v7 21 | env: 22 | FULL_COMMIT_MESSAGE: '${{ github.event.head_commit.message }}' 23 | with: 24 | result-encoding: string 25 | script: | 26 | var message = `${process.env.FULL_COMMIT_MESSAGE}`; 27 | core.info(message); 28 | if (message != '') return message; 29 | var time = new Date(Date.now()).toISOString(); 30 | core.info(time); 31 | return `Get traffic data at ${time}`; 32 | # - name: Get current year 33 | # id: date 34 | # run: echo "::set-output name=date::$(date +'%Y')" 35 | - name: Set Traffic 36 | id: traffic 37 | uses: yi-Xu-0100/traffic-to-badge@v1.4.0 38 | with: 39 | my_token: ${{ secrets.TRAFFIC_TOKEN }} 40 | #(default) static_list: ${{ github.repository }} 41 | #(default) traffic_branch: traffic 42 | #(default) views_color: brightgreen 43 | #(default) clones_color: brightgreen 44 | #(default) views_week_color: brightgreen 45 | #(default) clones_week_color: brightgreen 46 | #(default) total_views_color: brightgreen 47 | #(default) total_clones_color: brightgreen 48 | #(default) total_views_week_color: brightgreen 49 | #(default) total_clones_week_color: brightgreen 50 | #(default) logo: github 51 | #year: ${{ steps.date.outputs.date }} 52 | 53 | - name: Deploy 54 | uses: peaceiris/actions-gh-pages@v4 55 | with: 56 | github_token: ${{ secrets.GITHUB_TOKEN }} 57 | publish_branch: ${{ steps.traffic.outputs.traffic_branch }} 58 | publish_dir: ${{ steps.traffic.outputs.traffic_path }} 59 | user_name: 'github-actions[bot]' 60 | user_email: 'github-actions[bot]@users.noreply.github.com' 61 | full_commit_message: ${{ steps.message.outputs.result }} 62 | 63 | - name: Show Traffic Data 64 | run: | 65 | echo ${{ steps.traffic.outputs.traffic_branch }} 66 | echo ${{ steps.traffic.outputs.traffic_path }} 67 | cd ${{ steps.traffic.outputs.traffic_path }} 68 | ls -a 69 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | coverage.out 2 | profile.cov 3 | ./healthchecker 4 | .vscode 5 | main.go 6 | *.out 7 | *.log 8 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [{ 7 | "name": "Launch", 8 | "type": "go", 9 | "request": "launch", 10 | "host": "127.0.0.1", 11 | "mode": "debug", 12 | "program": "${workspaceFolder}", 13 | "env": {}, 14 | "args": [], 15 | "showLog": true 16 | } 17 | 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Gritzko Daniel Kleiner 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | go test -coverprofile=profile.cov ./... 3 | coverage: test 4 | go tool cover -html=profile.cov 5 | build: 6 | CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o healthchecker pkg/**/*.go 7 | view-docs: 8 | godoc -http=:8331 9 | run: 10 | go run main.go 11 | install-godoc: 12 | go install golang.org/x/tools/cmd/godoc@latest 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # golang-health-checker 2 | 3 | [![test](https://github.com/gritzkoo/golang-health-checker/actions/workflows/test.yaml/badge.svg)](https://github.com/gritzkoo/golang-health-checker/actions/workflows/test.yaml) 4 | [![build](https://github.com/gritzkoo/golang-health-checker/actions/workflows/build.yaml/badge.svg)](https://github.com/gritzkoo/golang-health-checker/actions/workflows/build.yaml) 5 | [![Coverage Status](https://coveralls.io/repos/github/gritzkoo/golang-health-checker/badge.svg?branch=master)](https://coveralls.io/github/gritzkoo/golang-health-checker?branch=master) 6 | ![views](https://raw.githubusercontent.com/gritzkoo/golang-health-checker/traffic/traffic-golang-health-checker/views.svg) 7 | ![views per week](https://raw.githubusercontent.com/gritzkoo/golang-health-checker/traffic/traffic-golang-health-checker/views_per_week.svg) 8 | ![clones](https://raw.githubusercontent.com/gritzkoo/golang-health-checker/traffic/traffic-golang-health-checker/clones.svg) 9 | ![clones per week](https://raw.githubusercontent.com/gritzkoo/golang-health-checker/traffic/traffic-golang-health-checker/clones_per_week.svg) 10 | [![Go Reference](https://pkg.go.dev/badge/github.com/gritzkoo/golang-health-checker/pkg/healthcheck.svg)](https://pkg.go.dev/github.com/gritzkoo/golang-health-checker/pkg/healthcheck) 11 | ![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/gritzkoo/golang-health-checker) 12 | ![GitHub repo size](https://img.shields.io/github/repo-size/gritzkoo/golang-health-checker) 13 | ![GitHub](https://img.shields.io/github/license/gritzkoo/golang-health-checker) 14 | ![GitHub issues](https://img.shields.io/github/issues/gritzkoo/golang-health-checker) 15 | [![Go Report Card](https://goreportcard.com/badge/github.com/gritzkoo/golang-health-checker)](https://goreportcard.com/report/github.com/gritzkoo/golang-health-checker) 16 | 17 | A simple package to allow you to track your application healthy providing two ways of checking: 18 | 19 | *__Simple__*: will return a "fully functional" string and with this, you can check if your application is online and responding without any integration check 20 | 21 | *__Detailed__*: will return a detailed status for any integration configuration informed on the integrations, just like in the examples below 22 | 23 | ___ 24 | >This package has a `lightweight` version with no extra dependencies. If you are looking to something more simple, please check [golnag-health-checker-lw on github](https://github.com/gritzkoo/golang-health-checker-lw "golang health checker lightweight") or [golang-health-checke-lw at go.pkg.dev](https://pkg.go.dev/github.com/gritzkoo/golang-health-checker-lw "golang health checker lightweight at go.pkg.dev") 25 | ___ 26 | 27 | ## How to install 28 | 29 | If you are just starting a Go project you must start a go.mod file like below 30 | 31 | ```sh 32 | go mod init github.com/my/repo 33 | ``` 34 | 35 | Or else, if you already have a started project, just run the command below 36 | 37 | ```sh 38 | go get github.com/gritzkoo/golang-health-checker 39 | ``` 40 | 41 | ## How to use 42 | 43 | In this example, we will use the Echo web server to show how to import and use *Simple* and *Detailed* calls. 44 | 45 | If you want to check the full options in configurations, look at this [IntegrationConfig struct](https://github.com/gritzkoo/golang-health-checker/blob/master/pkg/healthcheck/structs.go#L45-L54) 46 | 47 | ### Available integrations 48 | 49 | - [x] Redis 50 | - [x] Memcached 51 | - [x] Web integration (https) 52 | - [x] Customized test functions 53 | 54 | ```go 55 | package main 56 | 57 | import ( 58 | "net/http" 59 | 60 | "github.com/gin-gonic/gin" 61 | "github.com/gritzkoo/golang-health-checker/pkg/healthcheck" 62 | ) 63 | 64 | func main() { 65 | // all the content below is just an example 66 | // Gin instance 67 | e := gin.Default() 68 | 69 | // example of simple call 70 | e.GET("/health-check/liveness", func(c *gin.Context) { 71 | c.JSON(http.StatusOK, healthcheck.HealthCheckerSimple()) 72 | }) 73 | // example of detailed call 74 | e.GET("/health-check/readiness", func(c *gin.Context) { 75 | // define all integrations of your application with type healthcheck.ApplicationConfig 76 | myApplicationConfig := healthcheck.ApplicationConfig{ // check the full list of available props in structs.go 77 | Name: "You APP Name", // optional prop 78 | Version: "V1.0.0", // optional prop 79 | Integrations: []healthcheck.IntegrationConfig{ // mandatory prop 80 | { 81 | Type: healthcheck.Redis, // this prop will determine the kind of check, the list of types available in structs.go 82 | Name: "redis-user-db", // the name of you integration to display in response 83 | Host: "redis", // you can pass host:port and omit Port attribute 84 | Port: "6379", 85 | DB: 0, // default value is 0 86 | }, { 87 | Type: healthcheck.Memcached, // this prop will determine the kind of check, the list of types available in structs.go 88 | Name: "Memcached server", // the name of you integration to display in response 89 | Host: "memcache", // you can pass host:port and omit Port attribute 90 | Port: "11211", 91 | }, { 92 | Type: healthcheck.Web, // this prop will determine the kind of check, the list of types available in structs.go 93 | Name: "Github Integration", // the name of you integration to display in response 94 | Host: "https://github.com/status", // you can pass host:port and omit Port attribute 95 | TimeOut: 5, // default value to web call is 10s 96 | Headers: []healthcheck.HTTPHeader{ // to customize headers to perform a GET request 97 | { 98 | Key: "Accept", 99 | Value: "application/json", 100 | }, 101 | }, 102 | }, { 103 | Type: "unknown", // this prop will determine the kind of check, the list of types available in structs.go 104 | Name: "Github Integration", // the name of you integration to display in response 105 | Host: "https://github.com/status", // you can pass host:port and omit Port attribute 106 | TimeOut: 5, // default value to web call is 10s 107 | Headers: []healthcheck.HTTPHeader{ // to customize headers to perform a GET request 108 | { 109 | Key: "Accept", 110 | Value: "application/json", 111 | }, 112 | }, 113 | }, { 114 | Type: healthcheck.Custom, // this prop will determine the kind of check, the list of types available in structs.go 115 | Name: "Testing my customized function", // the name of you integration to display in response 116 | Host: "Some info to help debug", 117 | Handle: func() error { 118 | // do wherever test you need using the code of your 119 | // aplication and return an error or nil 120 | // good examples of use is test DB connections, AWS services like SQS SNS S3 DYNAMODB, ETC 121 | return nil 122 | }, 123 | }, 124 | }, 125 | } 126 | c.JSON(http.StatusOK, healthcheck.HealthCheckerDetailed(myApplicationConfig)) 127 | }) 128 | // Start server 129 | e.Run(":8888") 130 | } 131 | 132 | ``` 133 | 134 | This simple call will return a JSON as below 135 | 136 | ```json 137 | { 138 | "status": "fully functional" 139 | } 140 | ``` 141 | 142 | And detailed call will return a JSON as below 143 | 144 | ```json 145 | { 146 | "name": "You APP Name", 147 | "status": false, # here is the main status of your application when one of the integrations fails.. false will return 148 | "version": "V1.0.0", 149 | "date": "2021-08-27 08:18:06.762044096 -0300 -03 m=+24.943851850", 150 | "duration": 0.283596049, 151 | "integrations": [ 152 | { 153 | "name": "Github Integration", 154 | "kind": "unknown", 155 | "status": false, 156 | "response_time": 0, 157 | "url": "https://github.com/status", 158 | "errors": "unsuported type of:unknown" 159 | }, 160 | { 161 | "name": "Testing my customized function", 162 | "kind": "Customized test function", 163 | "status": true, 164 | "response_time": 0, 165 | "url": "Some info to help debug" 166 | }, 167 | { 168 | "name": "Memcached server", 169 | "kind": "Memcached DB", 170 | "status": true, 171 | "response_time": 4, 172 | "url": "localhost:11211" 173 | }, 174 | { 175 | "name": "redis-user-db", 176 | "kind": "Redis DB", 177 | "status": true, 178 | "response_time": 0.000845594, 179 | "url": "localhost:6379" 180 | }, 181 | { 182 | "name": "Github Integration", 183 | "kind": "Web service API", 184 | "status": true, 185 | "response_time": 0.283513713, 186 | "url": "https://github.com/status" 187 | } 188 | ] 189 | } 190 | ``` 191 | 192 | ## Kubernetes liveness and readiness probing 193 | 194 | And then, you could call these endpoints manually to see your application health, but, if you are using modern kubernetes deployment, you can config your chart to check your application with the setup below: 195 | 196 | ```yaml 197 | apiVersion: apps/v1 198 | kind: Deployment 199 | metadata: 200 | name: my-golang-app 201 | spec: 202 | selector: 203 | matchLabels: 204 | app: my-golang-app 205 | template: 206 | metadata: 207 | labels: 208 | app: my-golang-app 209 | spec: 210 | containers: 211 | - name: my-golang-app 212 | image: your-app-image:tag 213 | resources: 214 | request: 215 | cpu: 10m 216 | memory: 5Mi 217 | limits: 218 | cpu: 50m 219 | memory: 50Mi 220 | livenessProbe: 221 | httpGet: 222 | path: /health-check/liveness 223 | port: 8888 224 | scheme: http 225 | initialDelaySeconds: 5 226 | periodSeconds: 10 227 | timeoutSeconds: 5 228 | failureThreshold: 2 229 | successThreshold: 1 230 | readinessProbe: 231 | httpGet: 232 | path: /health-check/liveness 233 | port: 8888 234 | scheme: http 235 | initialDelaySeconds: 5 236 | periodSeconds: 10 237 | timeoutSeconds: 5 238 | failureThreshold: 2 239 | successThreshold: 1 240 | ports: 241 | - containerPort: 8888 242 | ``` 243 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-hacker -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | test: 3 | &default 4 | container_name: golang_app 5 | image: golang:1.24-alpine 6 | working_dir: /app 7 | command: sh -c "go test -v ./..." 8 | environment: 9 | CGO_ENABLED: 0 10 | GOOS: linux 11 | REDIS_HOST: redis 12 | MEMCACHE_HOST: memcache 13 | 14 | volumes: 15 | - .:/app 16 | - godir:/go 17 | ports: 18 | - 8888:8888 19 | depends_on: 20 | - redis 21 | - memcache 22 | 23 | app: 24 | <<: *default 25 | container_name: golang_app 26 | command: go run main.go 27 | 28 | redis: 29 | image: redis:latest 30 | ports: 31 | - 6379:6379 32 | 33 | memcache: 34 | image: memcached:latest 35 | ports: 36 | - 11211:11211 37 | 38 | volumes: 39 | godir: {} 40 | -------------------------------------------------------------------------------- /docs/DEVELOPING.md: -------------------------------------------------------------------------------- 1 | # How it works 2 | 3 | You can *__git clone__* this repo and try 4 | 5 | ```sh 6 | docker-compose build 7 | docker-compose up app 8 | ``` 9 | 10 | And using you browser you can call: 11 | * [Simple Call](http://localhost:8888/health-check/simple) 12 | * [Detailed Call](http://localhost:8888/health-check/detailed) 13 | 14 | And to run tests 15 | 16 | ```sh 17 | docker-compose run test 18 | ``` 19 | 20 | ## The next steps to this package is adding compatibility with the integrations below: 21 | 22 | * [x] Redis 23 | * [x] Memcache 24 | * [x] Web API calls 25 | * [ ] Mongodb 26 | * [ ] Mysql 27 | * [ ] Postgres 28 | * [ ] RabbitMQ 29 | 30 | There are no plans to add more integrations for now, but if you want to colaborate, open a PR or a issue 31 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gritzkoo/golang-health-checker 2 | 3 | go 1.24 4 | 5 | require ( 6 | github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf 7 | github.com/go-redis/redis v6.15.9+incompatible 8 | gopkg.in/go-playground/assert.v1 v1.2.1 9 | ) 10 | 11 | require ( 12 | github.com/fsnotify/fsnotify v1.4.9 // indirect 13 | github.com/google/go-cmp v0.7.0 // indirect 14 | github.com/nxadm/tail v1.4.8 // indirect 15 | github.com/onsi/ginkgo v1.16.5 // indirect 16 | github.com/onsi/gomega v1.37.0 // indirect 17 | golang.org/x/net v0.39.0 // indirect 18 | golang.org/x/sys v0.32.0 // indirect 19 | golang.org/x/text v0.24.0 // indirect 20 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect 21 | gopkg.in/yaml.v3 v3.0.1 // indirect 22 | ) 23 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf h1:TqhNAT4zKbTdLa62d2HDBFdvgSbIGB3eJE8HqhgiL9I= 2 | github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c= 3 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 6 | github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= 7 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= 8 | github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg= 9 | github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= 10 | github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= 11 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 12 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 13 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 14 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 15 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 16 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 17 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 18 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 19 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 20 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 21 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 22 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 23 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 24 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 25 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 26 | github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= 27 | github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= 28 | github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= 29 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 30 | github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= 31 | github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= 32 | github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= 33 | github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= 34 | github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= 35 | github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= 36 | github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= 37 | github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y= 38 | github.com/onsi/gomega v1.37.0/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0= 39 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 40 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 41 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 42 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 43 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 44 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 45 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 46 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 47 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 48 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 49 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 50 | golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 51 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 52 | golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA= 53 | golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I= 54 | golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= 55 | golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= 56 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 57 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 58 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 59 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 60 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 61 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 62 | golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 63 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 64 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 65 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 66 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 67 | golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 68 | golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= 69 | golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 70 | golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= 71 | golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 72 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 73 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 74 | golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= 75 | golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= 76 | golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= 77 | golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= 78 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 79 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 80 | golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 81 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 82 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 83 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 84 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 85 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 86 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 87 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 88 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 89 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 90 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 91 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 92 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 93 | gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= 94 | gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= 95 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 96 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 97 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 98 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 99 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 100 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 101 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 102 | -------------------------------------------------------------------------------- /pkg/healthcheck/health_checker.go: -------------------------------------------------------------------------------- 1 | package healthcheck 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "sync" 7 | "time" 8 | 9 | "github.com/bradfitz/gomemcache/memcache" 10 | "github.com/go-redis/redis" 11 | ) 12 | 13 | // HealthCheckerSimple performs a simple check of the application 14 | func HealthCheckerSimple() ApplicationHealthSimple { 15 | return ApplicationHealthSimple{ 16 | Status: "fully functional", 17 | } 18 | } 19 | 20 | // HealthCheckerDetailed perform a check for every integration informed 21 | func HealthCheckerDetailed(config ApplicationConfig) ApplicationHealthDetailed { 22 | var ( 23 | start = time.Now() 24 | wg sync.WaitGroup 25 | checklist = make(chan Integration, len(config.Integrations)) 26 | result = ApplicationHealthDetailed{ 27 | Name: config.Name, 28 | Version: config.Version, 29 | Status: true, 30 | Date: start.Format(time.RFC3339), 31 | Duration: 0, 32 | Integrations: []Integration{}, 33 | } 34 | ) 35 | wg.Add(len(config.Integrations)) 36 | for _, v := range config.Integrations { 37 | switch v.Type { 38 | case Redis: 39 | go checkRedisClient(v, &result, &wg, checklist) 40 | case Memcached: 41 | go checkMemcachedClient(v, &result, &wg, checklist) 42 | case Web: 43 | go checkWebServiceClient(v, &result, &wg, checklist) 44 | case Custom: 45 | go CheckCustom(v, &result, &wg, checklist) 46 | default: 47 | go defaultAction(v, &result, &wg, checklist) 48 | } 49 | } 50 | go func() { 51 | wg.Wait() 52 | close(checklist) 53 | }() 54 | result.Duration = time.Since(start).Seconds() 55 | for chk := range checklist { 56 | result.Integrations = append(result.Integrations, chk) 57 | } 58 | return result 59 | } 60 | 61 | func checkRedisClient(config IntegrationConfig, result *ApplicationHealthDetailed, wg *sync.WaitGroup, checklist chan Integration) { 62 | defer (*wg).Done() 63 | var ( 64 | start = time.Now() 65 | myStatus = true 66 | host = validateHost(config) 67 | DB = 0 68 | errorMessage = "" 69 | ) 70 | if config.DB > 0 { 71 | DB = config.DB 72 | } 73 | rdb := redis.NewClient(&redis.Options{ 74 | Addr: host, 75 | Password: config.Auth.Password, // no password set 76 | DB: DB, // use default DB 77 | }) 78 | response, err := rdb.Ping().Result() 79 | rdb.Close() 80 | if err != nil { 81 | myStatus = false 82 | result.Status = false 83 | errorMessage = fmt.Sprintf("response: %s error message: %s", response, err.Error()) 84 | } 85 | checklist <- Integration{ 86 | Name: config.Name, 87 | Kind: RedisIntegration, 88 | Status: myStatus, 89 | ResponseTime: time.Since(start).Seconds(), 90 | URL: host, 91 | Error: errorMessage, 92 | } 93 | } 94 | 95 | func checkMemcachedClient(config IntegrationConfig, result *ApplicationHealthDetailed, wg *sync.WaitGroup, checklist chan Integration) { 96 | defer (*wg).Done() 97 | var ( 98 | start = time.Now() 99 | myStatus = true 100 | host = validateHost(config) 101 | errorMessage = "" 102 | ) 103 | mcClient := memcache.New(host) 104 | err := mcClient.Ping() 105 | if err != nil { 106 | myStatus = false 107 | result.Status = false 108 | errorMessage = err.Error() 109 | } 110 | checklist <- Integration{ 111 | Name: config.Name, 112 | Kind: MemcachedIntegration, 113 | Status: myStatus, 114 | ResponseTime: time.Since(start).Seconds(), 115 | URL: host, 116 | Error: errorMessage, 117 | } 118 | } 119 | 120 | func checkWebServiceClient(config IntegrationConfig, result *ApplicationHealthDetailed, wg *sync.WaitGroup, checklist chan Integration) { 121 | defer (*wg).Done() 122 | var ( 123 | host = validateHost(config) 124 | timeout = 10 125 | myStatus = true 126 | start = time.Now() 127 | errorMessage = "" 128 | ) 129 | if config.TimeOut > 0 { 130 | timeout = config.TimeOut 131 | } 132 | client := http.Client{ 133 | Timeout: time.Second * time.Duration(timeout), 134 | } 135 | request, _ := http.NewRequest("GET", host, nil) 136 | 137 | if len(config.Headers) > 0 { 138 | for _, v := range config.Headers { 139 | request.Header.Add(v.Key, v.Value) 140 | } 141 | } 142 | response, err := client.Do(request) 143 | if err != nil { 144 | myStatus = false 145 | result.Status = false 146 | errorMessage = err.Error() 147 | } else if response.StatusCode != 200 { 148 | myStatus = false 149 | result.Status = false 150 | errorMessage = fmt.Sprintf("Expected request status code 200 got %d", response.StatusCode) 151 | } 152 | checklist <- Integration{ 153 | Name: config.Name, 154 | Kind: WebServiceIntegration, 155 | Status: myStatus, 156 | ResponseTime: time.Since(start).Seconds(), 157 | URL: host, 158 | Error: errorMessage, 159 | } 160 | } 161 | 162 | func CheckCustom(config IntegrationConfig, result *ApplicationHealthDetailed, wg *sync.WaitGroup, checklist chan Integration) { 163 | defer (*wg).Done() 164 | var ( 165 | myStatus = true 166 | start = time.Now() 167 | host = validateHost(config) 168 | errorMessage = "" 169 | ) 170 | tmp := config.Handle() 171 | if tmp != nil { 172 | myStatus = false 173 | result.Status = false 174 | errorMessage = tmp.Error() 175 | } 176 | checklist <- Integration{ 177 | Name: config.Name, 178 | Kind: CustomizedTestFunction, 179 | Status: myStatus, 180 | ResponseTime: time.Since(start).Seconds(), 181 | URL: host, 182 | Error: errorMessage, 183 | } 184 | } 185 | 186 | func defaultAction(config IntegrationConfig, result *ApplicationHealthDetailed, wg *sync.WaitGroup, checklist chan Integration) { 187 | defer (*wg).Done() 188 | result.Status = false 189 | checklist <- Integration{ 190 | Name: config.Name, 191 | Kind: config.Type, 192 | Status: false, 193 | ResponseTime: 0, 194 | URL: config.Host, 195 | Error: fmt.Sprintf("unsuported type of: %v", config.Type), 196 | } 197 | } 198 | 199 | func validateHost(config IntegrationConfig) string { 200 | var host = config.Host 201 | if config.Port != "" { 202 | host = host + ":" + config.Port 203 | } 204 | return host 205 | } 206 | -------------------------------------------------------------------------------- /pkg/healthcheck/health_checker_test.go: -------------------------------------------------------------------------------- 1 | package healthcheck 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "testing" 7 | 8 | "gopkg.in/go-playground/assert.v1" 9 | ) 10 | 11 | func getenv(key string, fallback string) string { 12 | val := os.Getenv(key) 13 | if val != "" { 14 | return val 15 | } 16 | return fallback 17 | } 18 | 19 | var ( 20 | REDIS_HOST = getenv("REDIS_HOST", "localhost") 21 | MEMCACHE_HOST = getenv("MEMCACHE_HOST", "localhost") 22 | ) 23 | 24 | func TestSimpleChecker(t *testing.T) { 25 | result := HealthCheckerSimple().Status 26 | 27 | assert.Equal(t, result, "fully functional") 28 | } 29 | 30 | type detailedListProvider struct { 31 | Expected bool 32 | FakeHttp bool 33 | Config IntegrationConfig 34 | } 35 | 36 | var detailedDataProvider = []detailedListProvider{ 37 | { 38 | Expected: true, 39 | Config: IntegrationConfig{ 40 | Type: Redis, 41 | Name: "go-test-redis", 42 | DB: 1, 43 | Host: REDIS_HOST, 44 | Port: "6379", 45 | }, 46 | }, { 47 | Expected: false, 48 | Config: IntegrationConfig{ 49 | Type: Redis, 50 | Name: "go-test-redis", 51 | DB: 1, 52 | Host: REDIS_HOST, 53 | Port: "63", 54 | }, 55 | }, { 56 | Expected: true, 57 | Config: IntegrationConfig{ 58 | Type: Memcached, 59 | Name: "go-test-memcached", 60 | Host: MEMCACHE_HOST, 61 | Port: "11211", 62 | }, 63 | }, { 64 | Expected: false, 65 | Config: IntegrationConfig{ 66 | Type: Memcached, 67 | Name: "go-test-memcached", 68 | Host: MEMCACHE_HOST, 69 | Port: "11", 70 | }, 71 | }, { 72 | Expected: true, 73 | Config: IntegrationConfig{ 74 | Type: Web, 75 | Name: "go-test-web1", 76 | Host: "https://github.com/status", 77 | Headers: []HTTPHeader{ 78 | { 79 | Key: "Accept", 80 | Value: "application/json", 81 | }, 82 | }, 83 | }, 84 | }, { 85 | Expected: false, 86 | Config: IntegrationConfig{ 87 | Type: Web, 88 | Name: "go-test-with no 200", 89 | Host: "https://google.com/status", 90 | TimeOut: 1, 91 | }, 92 | }, { 93 | Expected: false, 94 | Config: IntegrationConfig{ 95 | Type: Web, 96 | Name: "go-test-with-error", 97 | Host: "tcp://jsfiddle.net", 98 | TimeOut: 1, 99 | }, 100 | }, { 101 | Expected: false, 102 | Config: IntegrationConfig{ 103 | Type: "unknow", 104 | Name: "go-test-unknow", 105 | }, 106 | }, { 107 | Expected: true, 108 | Config: IntegrationConfig{ 109 | Type: Custom, 110 | Name: "testing-custom-func-success", 111 | Handle: func() error { 112 | return nil 113 | }, 114 | }, 115 | }, { 116 | Expected: false, 117 | Config: IntegrationConfig{ 118 | Type: Custom, 119 | Name: "testing-custom-func-with-error", 120 | Handle: func() error { 121 | return fmt.Errorf("error triggered by testing") 122 | }, 123 | }, 124 | }, 125 | } 126 | 127 | func TestDetailedChecker(t *testing.T) { 128 | for _, v := range detailedDataProvider { 129 | config := ApplicationConfig{ 130 | Name: "test", 131 | Version: "test", 132 | Integrations: []IntegrationConfig{ 133 | v.Config, 134 | }, 135 | } 136 | 137 | result := HealthCheckerDetailed(config) 138 | condition := v.Expected == result.Status 139 | printstring := "ok" 140 | if !condition { 141 | printstring = "nok" 142 | } 143 | fmt.Println("Running config:", v.Config.Name, " and result: ", printstring) 144 | assert.Equal(t, result.Status, v.Expected) 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /pkg/healthcheck/structs.go: -------------------------------------------------------------------------------- 1 | package healthcheck 2 | 3 | // ApplicationHealthDetailed used to check all application integrations and return status of each of then 4 | type ApplicationHealthDetailed struct { 5 | Name string `json:"name,omitempty"` 6 | Status bool `json:"status"` 7 | Version string `json:"version,omitempty"` 8 | Date string `json:"date"` 9 | Duration float64 `json:"duration"` 10 | Integrations []Integration `json:"integrations"` 11 | } 12 | 13 | // ApplicationHealthSimple used to simple return a string 'OK' 14 | type ApplicationHealthSimple struct { 15 | Status string `json:"status"` 16 | } 17 | 18 | // Integration is the type result for requests 19 | type Integration struct { 20 | Name string `json:"name"` 21 | Kind string `json:"kind"` 22 | Status bool `json:"status"` 23 | ResponseTime float64 `json:"response_time"` //in seconds 24 | URL string `json:"url"` 25 | Error string `json:"error,omitempty"` // error.Error() 26 | } 27 | 28 | // Auth is a default struct to map user/pass protocol 29 | type Auth struct { 30 | User string 31 | Password string 32 | } 33 | 34 | // ApplicationConfig is a config contract to init health caller 35 | type ApplicationConfig struct { 36 | Name string `json:"name"` 37 | Version string `json:"version"` 38 | Integrations []IntegrationConfig `json:"integrations"` 39 | } 40 | 41 | // IntegrationConfig used to inform each integration config 42 | type IntegrationConfig struct { 43 | Type string `json:"Type"` // must be web | redis | memcache 44 | Name string `json:"name"` 45 | Host string `json:"host"` // yes you can concat host:port here 46 | Port string `json:"port,omitempty"` 47 | Headers []HTTPHeader `json:"headers,omitempty"` 48 | DB int `json:"db,omitempty"` // default value is 0 49 | TimeOut int `json:"timeout,omitempty"` // default value: 10 50 | Auth Auth `json:"auth,omitempty"` 51 | Handle func() error // custom func validtion 52 | } 53 | 54 | // HTTPHeader used to setup webservices integrations 55 | type HTTPHeader struct { 56 | Key string `json:"key,omitempty"` 57 | Value string `json:"Value,omitempty"` 58 | } 59 | 60 | // Mapped types for IntegrationConfig 61 | const ( 62 | Redis = "redis" 63 | Memcached = "memcached" 64 | Web = "web" 65 | Custom = "custom" 66 | ) 67 | 68 | // Mapped typs for kinds of integrations 69 | const ( 70 | RedisIntegration = "Redis DB" 71 | MemcachedIntegration = "Memcached DB" 72 | WebServiceIntegration = "Web service API" 73 | CustomizedTestFunction = "Customized test function" 74 | ) 75 | --------------------------------------------------------------------------------