mirror of
https://github.com/foomo/keel.git
synced 2025-10-16 12:35:34 +00:00
Merge pull request #193 from foomo/service-goroutine
Add goroutine service
This commit is contained in:
commit
bbd90886cc
10
.github/workflows/release.yml
vendored
10
.github/workflows/release.yml
vendored
@ -6,15 +6,14 @@ on:
|
||||
- v*.*.*
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
GOFLAGS: -mod=readonly
|
||||
GOPROXY: https://proxy.golang.org
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@ -22,8 +21,7 @@ jobs:
|
||||
|
||||
- uses: actions/setup-go@v4
|
||||
with:
|
||||
check-latest: true
|
||||
go-version-file: 'go.mod'
|
||||
go-version: 'stable'
|
||||
|
||||
- uses: goreleaser/goreleaser-action@v4
|
||||
with:
|
||||
|
||||
26
.github/workflows/test.yml
vendored
26
.github/workflows/test.yml
vendored
@ -4,15 +4,8 @@ on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
merge_group:
|
||||
branches: [ main ]
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
GOFLAGS: -mod=readonly
|
||||
GOPROXY: https://proxy.golang.org
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
@ -21,15 +14,24 @@ jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-go@v4
|
||||
with:
|
||||
check-latest: true
|
||||
go-version-file: 'go.mod'
|
||||
go-version: 'stable'
|
||||
|
||||
- uses: gotesttools/gotestfmt-action@v2
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- uses: golangci/golangci-lint-action@v3
|
||||
with:
|
||||
version: latest
|
||||
args: --timeout=5m
|
||||
|
||||
- name: Run tests
|
||||
run: go test -v ./...
|
||||
- run: make test
|
||||
|
||||
- uses: coverallsapp/github-action@v2
|
||||
with:
|
||||
file: coverage.out
|
||||
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,5 +1,6 @@
|
||||
.*
|
||||
*.log
|
||||
*.out
|
||||
!.github/
|
||||
!.husky/
|
||||
!.editorconfig
|
||||
@ -7,6 +8,4 @@
|
||||
!.golangci.yml
|
||||
!.goreleaser.yml
|
||||
!.husky.yaml
|
||||
/coverage.out
|
||||
/coverage.html
|
||||
/tmp/
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
run:
|
||||
timeout: 5m
|
||||
skip-dirs:
|
||||
- tmp
|
||||
|
||||
linters-settings:
|
||||
# https://golangci-lint.run/usage/linters/#revive
|
||||
@ -61,7 +62,7 @@ linters:
|
||||
- gosec # (gas): Inspects source code for security problems [fast: false, auto-fix: false]
|
||||
- grouper # An analyzer to analyze expression groups. [fast: true, auto-fix: false]
|
||||
- importas # Enforces consistent import aliases [fast: false, auto-fix: false]
|
||||
- maintidx # maintidx measures the maintainability index of each function. [fast: true, auto-fix: false]
|
||||
#- maintidx # maintidx measures the maintainability index of each function. [fast: true, auto-fix: false]
|
||||
- makezero # Finds slice declarations with non-zero initial length [fast: false, auto-fix: false]
|
||||
- misspell # Finds commonly misspelled English words in comments [fast: true, auto-fix: true]
|
||||
- nakedret # Finds naked returns in functions greater than a specified function length [fast: true, auto-fix: false]
|
||||
|
||||
@ -2,33 +2,4 @@ builds:
|
||||
- skip: true
|
||||
|
||||
changelog:
|
||||
filters:
|
||||
exclude:
|
||||
- "^wip"
|
||||
- "^test"
|
||||
- "^docs"
|
||||
- "^chore"
|
||||
- "^style"
|
||||
- "go mod tidy"
|
||||
- "merge conflict"
|
||||
- "Merge pull request"
|
||||
- "Merge remote-tracking branch"
|
||||
- "Merge branch"
|
||||
groups:
|
||||
- title: Features
|
||||
regexp: '^.*?feat(\([[:word:]]+\))??!?:.+$'
|
||||
order: 0
|
||||
- title: Dependency updates
|
||||
regexp: '^.*?(feat|fix)\(deps\)!?:.+$'
|
||||
order: 100
|
||||
- title: "Bug fixes"
|
||||
regexp: '^.*?fix(\([[:word:]]+\))??!?:.+$'
|
||||
order: 150
|
||||
- title: "Security"
|
||||
regexp: '^.*?sec(\([[:word:]]+\))??!?:.+$'
|
||||
order: 200
|
||||
- title: "Performace"
|
||||
regexp: '^.*?perf(\([[:word:]]+\))??!?:.+$'
|
||||
order: 250
|
||||
- title: Other
|
||||
order: 999
|
||||
use: github-native
|
||||
|
||||
@ -9,9 +9,7 @@ hooks:
|
||||
lint-staged:
|
||||
'*.go':
|
||||
- goimports -l -w
|
||||
- gofmt -l -w
|
||||
|
||||
lint-commit:
|
||||
email: '^(.+@bestbytes.com)$'
|
||||
types: '^(feat|fix|build|chore|docs|perf|refactor|revert|style|test|wip)$'
|
||||
header: '^(?P<type>\w+)(\((?P<scope>[\w/.-]+)\))?(?P<breaking>!)?:( +)?(?P<header>.+)'
|
||||
|
||||
9
Makefile
9
Makefile
@ -27,14 +27,7 @@ doc:
|
||||
.PHONY: test
|
||||
## Run tests
|
||||
test:
|
||||
@go test -v ./...
|
||||
|
||||
.PHONY: test.cover
|
||||
## Run tests with coverage
|
||||
test.cover:
|
||||
@go test -v -coverprofile=coverage.out ./...
|
||||
@go tool cover -func=coverage.out
|
||||
@go tool cover -html=coverage.out
|
||||
@go test -coverprofile=coverage.out -race -json ./... | gotestfmt
|
||||
|
||||
.PHONY: lint
|
||||
## Run linter
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
# keel
|
||||
|
||||
[](https://github.com/foomo/keel/actions/workflows/test.yml)
|
||||
[](https://goreportcard.com/report/github.com/foomo/keel)
|
||||
[](https://godoc.org/github.com/foomo/keel)
|
||||
[](https://github.com/marketplace/actions/super-linter)
|
||||
[](https://coveralls.io/github/foomo/keel?branch=main)
|
||||
[](https://godoc.org/github.com/foomo/keel)
|
||||
|
||||
> Opinionated way to run services.
|
||||
|
||||
@ -25,6 +26,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/foomo/keel"
|
||||
"github.com/foomo/keel/service"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@ -39,7 +41,7 @@ func main() {
|
||||
svs := newService()
|
||||
|
||||
svr.AddService(
|
||||
keel.NewServiceHTTP(l, "demo", ":8080", svs),
|
||||
service.NewHTTP(l, "demo", "localhost:8080", svs),
|
||||
)
|
||||
|
||||
svr.Run()
|
||||
|
||||
56
closer.go
56
closer.go
@ -1,37 +1,29 @@
|
||||
package keel
|
||||
|
||||
import "context"
|
||||
import (
|
||||
"github.com/foomo/keel/interfaces"
|
||||
)
|
||||
|
||||
type closer struct {
|
||||
handle func(context.Context) error
|
||||
}
|
||||
|
||||
func NewCloserFn(handle func(context.Context) error) closer {
|
||||
return closer{
|
||||
handle: handle,
|
||||
func IsCloser(v any) bool {
|
||||
switch v.(type) {
|
||||
case interfaces.Closer,
|
||||
interfaces.ErrorCloser,
|
||||
interfaces.CloserWithContext,
|
||||
interfaces.ErrorCloserWithContext,
|
||||
interfaces.Shutdowner,
|
||||
interfaces.ErrorShutdowner,
|
||||
interfaces.ShutdownerWithContext,
|
||||
interfaces.ErrorShutdownerWithContext,
|
||||
interfaces.Stopper,
|
||||
interfaces.ErrorStopper,
|
||||
interfaces.StopperWithContext,
|
||||
interfaces.ErrorStopperWithContext,
|
||||
interfaces.Unsubscriber,
|
||||
interfaces.ErrorUnsubscriber,
|
||||
interfaces.UnsubscriberWithContext,
|
||||
interfaces.ErrorUnsubscriberWithContext:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (h healther) Close(ctx context.Context) error {
|
||||
return h.handle(ctx)
|
||||
}
|
||||
|
||||
// Closer interface
|
||||
type Closer interface {
|
||||
Close()
|
||||
}
|
||||
|
||||
// ErrorCloser interface
|
||||
type ErrorCloser interface {
|
||||
Close() error
|
||||
}
|
||||
|
||||
// CloserWithContext interface
|
||||
type CloserWithContext interface {
|
||||
Close(ctx context.Context)
|
||||
}
|
||||
|
||||
// ErrorCloserWithContext interface
|
||||
type ErrorCloserWithContext interface {
|
||||
Close(ctx context.Context) error
|
||||
}
|
||||
|
||||
132
config/config.go
132
config/config.go
@ -11,7 +11,10 @@ import (
|
||||
|
||||
// config holds the global configuration
|
||||
var (
|
||||
config *viper.Viper
|
||||
config *viper.Viper
|
||||
requiredKeys []string
|
||||
defaults = map[string]interface{}{}
|
||||
types = map[string]string{}
|
||||
)
|
||||
|
||||
// Init sets up the configuration
|
||||
@ -28,254 +31,224 @@ func Config() *viper.Viper {
|
||||
}
|
||||
|
||||
func GetBool(c *viper.Viper, key string, fallback bool) func() bool {
|
||||
c = ensure(c)
|
||||
c.SetDefault(key, fallback)
|
||||
setDefault(c, key, "bool", fallback)
|
||||
return func() bool {
|
||||
return c.GetBool(key)
|
||||
}
|
||||
}
|
||||
|
||||
func MustGetBool(c *viper.Viper, key string, fallback bool) func() bool {
|
||||
c = ensure(c)
|
||||
must(c, key)
|
||||
func MustGetBool(c *viper.Viper, key string) func() bool {
|
||||
must(c, key, "bool")
|
||||
return func() bool {
|
||||
return c.GetBool(key)
|
||||
}
|
||||
}
|
||||
|
||||
func GetInt(c *viper.Viper, key string, fallback int) func() int {
|
||||
c.SetDefault(key, fallback)
|
||||
setDefault(c, key, "int", fallback)
|
||||
return func() int {
|
||||
return c.GetInt(key)
|
||||
}
|
||||
}
|
||||
|
||||
func MustGetInt(c *viper.Viper, key string) func() int {
|
||||
must(c, key)
|
||||
must(c, key, "int")
|
||||
return func() int {
|
||||
return c.GetInt(key)
|
||||
}
|
||||
}
|
||||
|
||||
func GetInt32(c *viper.Viper, key string, fallback int32) func() int32 {
|
||||
c = ensure(c)
|
||||
c.SetDefault(key, fallback)
|
||||
setDefault(c, key, "int32", fallback)
|
||||
return func() int32 {
|
||||
return c.GetInt32(key)
|
||||
}
|
||||
}
|
||||
|
||||
func MustGetInt32(c *viper.Viper, key string) func() int32 {
|
||||
c = ensure(c)
|
||||
must(c, key)
|
||||
must(c, key, "int32")
|
||||
return func() int32 {
|
||||
return c.GetInt32(key)
|
||||
}
|
||||
}
|
||||
|
||||
func GetInt64(c *viper.Viper, key string, fallback int64) func() int64 {
|
||||
c = ensure(c)
|
||||
c.SetDefault(key, fallback)
|
||||
setDefault(c, key, "int64", fallback)
|
||||
return func() int64 {
|
||||
return c.GetInt64(key)
|
||||
}
|
||||
}
|
||||
|
||||
func MustGetInt64(c *viper.Viper, key string) func() int64 {
|
||||
c = ensure(c)
|
||||
must(c, key)
|
||||
must(c, key, "int64")
|
||||
return func() int64 {
|
||||
return c.GetInt64(key)
|
||||
}
|
||||
}
|
||||
|
||||
func GetUint(c *viper.Viper, key string, fallback uint) func() uint {
|
||||
c = ensure(c)
|
||||
c.SetDefault(key, fallback)
|
||||
setDefault(c, key, "uint", fallback)
|
||||
return func() uint {
|
||||
return c.GetUint(key)
|
||||
}
|
||||
}
|
||||
|
||||
func MustGetUint(c *viper.Viper, key string) func() uint {
|
||||
c = ensure(c)
|
||||
must(c, key)
|
||||
must(c, key, "uint")
|
||||
return func() uint {
|
||||
return c.GetUint(key)
|
||||
}
|
||||
}
|
||||
|
||||
func GetUint32(c *viper.Viper, key string, fallback uint32) func() uint32 {
|
||||
c = ensure(c)
|
||||
c.SetDefault(key, fallback)
|
||||
setDefault(c, key, "uint32", fallback)
|
||||
return func() uint32 {
|
||||
return c.GetUint32(key)
|
||||
}
|
||||
}
|
||||
|
||||
func MustGetUint32(c *viper.Viper, key string) func() uint32 {
|
||||
c = ensure(c)
|
||||
must(c, key)
|
||||
must(c, key, "uint32")
|
||||
return func() uint32 {
|
||||
return c.GetUint32(key)
|
||||
}
|
||||
}
|
||||
|
||||
func GetUint64(c *viper.Viper, key string, fallback uint64) func() uint64 {
|
||||
c = ensure(c)
|
||||
c.SetDefault(key, fallback)
|
||||
setDefault(c, key, "uint64", fallback)
|
||||
return func() uint64 {
|
||||
return c.GetUint64(key)
|
||||
}
|
||||
}
|
||||
|
||||
func MustGetUint64(c *viper.Viper, key string) func() uint64 {
|
||||
c = ensure(c)
|
||||
must(c, key)
|
||||
must(c, key, "uint64")
|
||||
return func() uint64 {
|
||||
return c.GetUint64(key)
|
||||
}
|
||||
}
|
||||
|
||||
func GetFloat64(c *viper.Viper, key string, fallback float64) func() float64 {
|
||||
c = ensure(c)
|
||||
c.SetDefault(key, fallback)
|
||||
setDefault(c, key, "float64", fallback)
|
||||
return func() float64 {
|
||||
return c.GetFloat64(key)
|
||||
}
|
||||
}
|
||||
|
||||
func MustGetFloat64(c *viper.Viper, key string) func() float64 {
|
||||
c = ensure(c)
|
||||
must(c, key)
|
||||
must(c, key, "float64")
|
||||
return func() float64 {
|
||||
return c.GetFloat64(key)
|
||||
}
|
||||
}
|
||||
|
||||
func GetString(c *viper.Viper, key, fallback string) func() string {
|
||||
c = ensure(c)
|
||||
c.SetDefault(key, fallback)
|
||||
setDefault(c, key, "string", fallback)
|
||||
return func() string {
|
||||
return c.GetString(key)
|
||||
}
|
||||
}
|
||||
|
||||
func MustGetString(c *viper.Viper, key string) func() string {
|
||||
c = ensure(c)
|
||||
must(c, key)
|
||||
must(c, key, "string")
|
||||
return func() string {
|
||||
return c.GetString(key)
|
||||
}
|
||||
}
|
||||
|
||||
func GetTime(c *viper.Viper, key string, fallback time.Time) func() time.Time {
|
||||
c = ensure(c)
|
||||
c.SetDefault(key, fallback)
|
||||
setDefault(c, key, "time.Time", fallback)
|
||||
return func() time.Time {
|
||||
return c.GetTime(key)
|
||||
}
|
||||
}
|
||||
|
||||
func MustGetTime(c *viper.Viper, key string) func() time.Time {
|
||||
c = ensure(c)
|
||||
must(c, key)
|
||||
must(c, key, "time.Time")
|
||||
return func() time.Time {
|
||||
return c.GetTime(key)
|
||||
}
|
||||
}
|
||||
|
||||
func GetDuration(c *viper.Viper, key string, fallback time.Duration) func() time.Duration {
|
||||
c = ensure(c)
|
||||
c.SetDefault(key, fallback)
|
||||
setDefault(c, key, "time.Duration", fallback)
|
||||
return func() time.Duration {
|
||||
return c.GetDuration(key)
|
||||
}
|
||||
}
|
||||
|
||||
func MustGetDuration(c *viper.Viper, key string) func() time.Duration {
|
||||
c = ensure(c)
|
||||
must(c, key)
|
||||
must(c, key, "time.Duration")
|
||||
return func() time.Duration {
|
||||
return c.GetDuration(key)
|
||||
}
|
||||
}
|
||||
|
||||
func GetIntSlice(c *viper.Viper, key string, fallback []int) func() []int {
|
||||
c = ensure(c)
|
||||
c.SetDefault(key, fallback)
|
||||
setDefault(c, key, "[]int", fallback)
|
||||
return func() []int {
|
||||
return c.GetIntSlice(key)
|
||||
}
|
||||
}
|
||||
|
||||
func MustGetIntSlice(c *viper.Viper, key string) func() []int {
|
||||
c = ensure(c)
|
||||
must(c, key)
|
||||
must(c, key, "[]int")
|
||||
return func() []int {
|
||||
return c.GetIntSlice(key)
|
||||
}
|
||||
}
|
||||
|
||||
func GetStringSlice(c *viper.Viper, key string, fallback []string) func() []string {
|
||||
c = ensure(c)
|
||||
c.SetDefault(key, fallback)
|
||||
setDefault(c, key, "[]string", fallback)
|
||||
return func() []string {
|
||||
return c.GetStringSlice(key)
|
||||
}
|
||||
}
|
||||
|
||||
func MustGetStringSlice(c *viper.Viper, key string) func() []string {
|
||||
c = ensure(c)
|
||||
must(c, key)
|
||||
must(c, key, "[]string")
|
||||
return func() []string {
|
||||
return c.GetStringSlice(key)
|
||||
}
|
||||
}
|
||||
|
||||
func GetStringMap(c *viper.Viper, key string, fallback map[string]interface{}) func() map[string]interface{} {
|
||||
c = ensure(c)
|
||||
c.SetDefault(key, fallback)
|
||||
setDefault(c, key, "map[string]interface{}", fallback)
|
||||
return func() map[string]interface{} {
|
||||
return c.GetStringMap(key)
|
||||
}
|
||||
}
|
||||
|
||||
func MustGetStringMap(c *viper.Viper, key string) func() map[string]interface{} {
|
||||
c = ensure(c)
|
||||
must(c, key)
|
||||
must(c, key, "map[string]interface{}")
|
||||
return func() map[string]interface{} {
|
||||
return c.GetStringMap(key)
|
||||
}
|
||||
}
|
||||
|
||||
func GetStringMapString(c *viper.Viper, key string, fallback map[string]string) func() map[string]string {
|
||||
c = ensure(c)
|
||||
c.SetDefault(key, fallback)
|
||||
setDefault(c, key, "map[string]string", fallback)
|
||||
return func() map[string]string {
|
||||
return c.GetStringMapString(key)
|
||||
}
|
||||
}
|
||||
|
||||
func MustGetStringMapString(c *viper.Viper, key string) func() map[string]string {
|
||||
c = ensure(c)
|
||||
must(c, key)
|
||||
must(c, key, "map[string]string")
|
||||
return func() map[string]string {
|
||||
return c.GetStringMapString(key)
|
||||
}
|
||||
}
|
||||
|
||||
func GetStringMapStringSlice(c *viper.Viper, key string, fallback map[string][]string) func() map[string][]string {
|
||||
c = ensure(c)
|
||||
c.SetDefault(key, fallback)
|
||||
setDefault(c, key, "map[string][]string", fallback)
|
||||
return func() map[string][]string {
|
||||
return c.GetStringMapStringSlice(key)
|
||||
}
|
||||
}
|
||||
|
||||
func MustGetStringMapStringSlice(c *viper.Viper, key string) func() map[string][]string {
|
||||
c = ensure(c)
|
||||
must(c, key)
|
||||
must(c, key, "map[string][]string")
|
||||
return func() map[string][]string {
|
||||
return c.GetStringMapStringSlice(key)
|
||||
}
|
||||
@ -316,6 +289,25 @@ func GetStruct(c *viper.Viper, key string, fallback interface{}) (func(v interfa
|
||||
}, nil
|
||||
}
|
||||
|
||||
func RequiredKeys() []string {
|
||||
return requiredKeys
|
||||
}
|
||||
|
||||
func Defaults() map[string]interface{} {
|
||||
return defaults
|
||||
}
|
||||
|
||||
func Types() map[string]string {
|
||||
return types
|
||||
}
|
||||
|
||||
func TypeOf(key string) string {
|
||||
if v, ok := types[key]; ok {
|
||||
return v
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func ensure(c *viper.Viper) *viper.Viper {
|
||||
if c == nil {
|
||||
c = config
|
||||
@ -323,7 +315,10 @@ func ensure(c *viper.Viper) *viper.Viper {
|
||||
return c
|
||||
}
|
||||
|
||||
func must(c *viper.Viper, key string) {
|
||||
func must(c *viper.Viper, key, typeof string) {
|
||||
c = ensure(c)
|
||||
types[key] = typeof
|
||||
requiredKeys = append(requiredKeys, key)
|
||||
if !c.IsSet(key) {
|
||||
panic(fmt.Sprintf("missing required config key: %s", key))
|
||||
}
|
||||
@ -339,3 +334,10 @@ func decode(input, output interface{}) error {
|
||||
}
|
||||
return decoder.Decode(input)
|
||||
}
|
||||
|
||||
func setDefault(c *viper.Viper, key, typeof string, fallback any) {
|
||||
c = ensure(c)
|
||||
c.SetDefault(key, fallback)
|
||||
defaults[key] = fallback
|
||||
types[key] = typeof
|
||||
}
|
||||
|
||||
71
config/readme.go
Normal file
71
config/readme.go
Normal file
@ -0,0 +1,71 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/foomo/keel/markdown"
|
||||
)
|
||||
|
||||
func Readme() string {
|
||||
var configRows [][]string
|
||||
var remoteRows [][]string
|
||||
c := Config()
|
||||
md := &markdown.Markdown{}
|
||||
|
||||
{
|
||||
keys := c.AllKeys()
|
||||
for _, key := range keys {
|
||||
var fallback interface{}
|
||||
if v, ok := defaults[key]; ok {
|
||||
fallback = v
|
||||
}
|
||||
configRows = append(configRows, []string{
|
||||
markdown.Code(key),
|
||||
markdown.Code(TypeOf(key)),
|
||||
"",
|
||||
markdown.Code(fmt.Sprintf("%v", fallback)),
|
||||
})
|
||||
}
|
||||
|
||||
for _, key := range requiredKeys {
|
||||
configRows = append(configRows, []string{
|
||||
markdown.Code(key),
|
||||
markdown.Code(TypeOf(key)),
|
||||
markdown.Code("true"),
|
||||
"",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
for _, remote := range remotes {
|
||||
remoteRows = append(remoteRows, []string{
|
||||
markdown.Code(remote.provider),
|
||||
markdown.Code(remote.path),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if len(configRows) > 0 || len(remoteRows) > 0 {
|
||||
md.Println("### Config")
|
||||
md.Println("")
|
||||
}
|
||||
|
||||
if len(configRows) > 0 {
|
||||
md.Println("List of all registered config variables with their defaults.")
|
||||
md.Println("")
|
||||
md.Table([]string{"Key", "Type", "Required", "Default"}, configRows)
|
||||
md.Println("")
|
||||
}
|
||||
|
||||
if len(remoteRows) > 0 {
|
||||
md.Println("#### Remotes")
|
||||
md.Println("")
|
||||
md.Println("List of remote config providers that are being watched.")
|
||||
md.Println("")
|
||||
md.Table([]string{"Provider", "Path"}, remoteRows)
|
||||
md.Println("")
|
||||
}
|
||||
|
||||
return md.String()
|
||||
}
|
||||
@ -6,6 +6,12 @@ import (
|
||||
_ "github.com/spf13/viper/remote"
|
||||
)
|
||||
|
||||
var remotes []struct {
|
||||
provider string
|
||||
endpoint string
|
||||
path string
|
||||
}
|
||||
|
||||
func WithRemoteConfig(c *viper.Viper, provider, endpoint string, path string) error {
|
||||
if err := c.AddRemoteProvider(provider, endpoint, path); err != nil {
|
||||
return err
|
||||
@ -19,5 +25,11 @@ func WithRemoteConfig(c *viper.Viper, provider, endpoint string, path string) er
|
||||
return errors.Wrap(err, "failed to watch remote config")
|
||||
}
|
||||
|
||||
remotes = append(remotes, struct {
|
||||
provider string
|
||||
endpoint string
|
||||
path string
|
||||
}{provider: provider, endpoint: endpoint, path: path})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
55
env/env.go
vendored
55
env/env.go
vendored
@ -3,10 +3,17 @@ package env
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
defaults = map[string]interface{}{}
|
||||
requiredKeys []string
|
||||
types = map[string]string{}
|
||||
)
|
||||
|
||||
// Exists return true if env var is defined
|
||||
func Exists(key string) bool {
|
||||
_, ok := os.LookupEnv(key)
|
||||
@ -15,13 +22,20 @@ func Exists(key string) bool {
|
||||
|
||||
// MustExists panics if not exists
|
||||
func MustExists(key string) {
|
||||
if _, ok := os.LookupEnv(key); !ok {
|
||||
panic(fmt.Sprintf("required environment variable %s does not exist", key))
|
||||
if !Exists(key) {
|
||||
panic(fmt.Sprintf("required environment variable `%s` does not exist", key))
|
||||
}
|
||||
if !slices.Contains(requiredKeys, key) {
|
||||
requiredKeys = append(requiredKeys, key)
|
||||
}
|
||||
}
|
||||
|
||||
// Get env var or fallback
|
||||
func Get(key, fallback string) string {
|
||||
defaults[key] = fallback
|
||||
if _, ok := types[key]; !ok {
|
||||
types[key] = "string"
|
||||
}
|
||||
if v, ok := os.LookupEnv(key); ok {
|
||||
return v
|
||||
}
|
||||
@ -36,6 +50,9 @@ func MustGet(key string) string {
|
||||
|
||||
// GetInt env var or fallback as int
|
||||
func GetInt(key string, fallback int) int {
|
||||
if _, ok := types[key]; !ok {
|
||||
types[key] = "int"
|
||||
}
|
||||
if value, err := strconv.Atoi(Get(key, "")); err == nil {
|
||||
return value
|
||||
}
|
||||
@ -50,6 +67,9 @@ func MustGetInt(key string) int {
|
||||
|
||||
// GetInt64 env var or fallback as int64
|
||||
func GetInt64(key string, fallback int64) int64 {
|
||||
if _, ok := types[key]; !ok {
|
||||
types[key] = "int64"
|
||||
}
|
||||
if value, err := strconv.ParseInt(Get(key, ""), 10, 64); err == nil {
|
||||
return value
|
||||
}
|
||||
@ -64,6 +84,9 @@ func MustGetInt64(key string) int64 {
|
||||
|
||||
// GetFloat64 env var or fallback as float64
|
||||
func GetFloat64(key string, fallback float64) float64 {
|
||||
if _, ok := types[key]; !ok {
|
||||
types[key] = "float64"
|
||||
}
|
||||
if value, err := strconv.ParseFloat(Get(key, ""), 64); err == nil {
|
||||
return value
|
||||
}
|
||||
@ -78,6 +101,9 @@ func MustGetFloat64(key string) float64 {
|
||||
|
||||
// GetBool env var or fallback as bool
|
||||
func GetBool(key string, fallback bool) bool {
|
||||
if _, ok := types[key]; !ok {
|
||||
types[key] = "bool"
|
||||
}
|
||||
if val, err := strconv.ParseBool(Get(key, "")); err == nil {
|
||||
return val
|
||||
}
|
||||
@ -92,6 +118,9 @@ func MustGetBool(key string) bool {
|
||||
|
||||
// GetStringSlice env var or fallback as []string
|
||||
func GetStringSlice(key string, fallback []string) []string {
|
||||
if _, ok := types[key]; !ok {
|
||||
types[key] = "[]string"
|
||||
}
|
||||
if v := Get(key, ""); v != "" {
|
||||
return strings.Split(v, ",")
|
||||
}
|
||||
@ -106,6 +135,9 @@ func MustGetStringSlice(key string) []string {
|
||||
|
||||
// GetIntSlice env var or fallback as []string
|
||||
func GetIntSlice(key string, fallback []int) []int {
|
||||
if _, ok := types[key]; !ok {
|
||||
types[key] = "[]int"
|
||||
}
|
||||
if v := Get(key, ""); v != "" {
|
||||
elements := strings.Split(v, ",")
|
||||
ret := make([]int, len(elements))
|
||||
@ -125,3 +157,22 @@ func MustGetGetIntSlice(key string) []int {
|
||||
MustExists(key)
|
||||
return GetIntSlice(key, nil)
|
||||
}
|
||||
|
||||
func RequiredKeys() []string {
|
||||
return requiredKeys
|
||||
}
|
||||
|
||||
func Defaults() map[string]interface{} {
|
||||
return defaults
|
||||
}
|
||||
|
||||
func Types() map[string]string {
|
||||
return types
|
||||
}
|
||||
|
||||
func TypeOf(key string) string {
|
||||
if v, ok := types[key]; ok {
|
||||
return v
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
43
env/readme.go
vendored
Normal file
43
env/readme.go
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/foomo/keel/markdown"
|
||||
)
|
||||
|
||||
func Readme() string {
|
||||
var rows [][]string
|
||||
md := &markdown.Markdown{}
|
||||
|
||||
{
|
||||
for key, fallback := range defaults {
|
||||
rows = append(rows, []string{
|
||||
markdown.Code(key),
|
||||
markdown.Code(TypeOf(key)),
|
||||
"",
|
||||
markdown.Code(fmt.Sprintf("%v", fallback)),
|
||||
})
|
||||
}
|
||||
|
||||
for _, key := range requiredKeys {
|
||||
rows = append(rows, []string{
|
||||
markdown.Code(key),
|
||||
markdown.Code(TypeOf(key)),
|
||||
markdown.Code("true"),
|
||||
"",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if len(rows) > 0 {
|
||||
md.Println("### Env")
|
||||
md.Println("")
|
||||
md.Println("List of all accessed environment variables.")
|
||||
md.Println("")
|
||||
md.Table([]string{"Key", "Type", "Required", "Default"}, rows)
|
||||
md.Println("")
|
||||
}
|
||||
|
||||
return md.String()
|
||||
}
|
||||
@ -5,6 +5,5 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
ErrServerNotRunning = errors.New("server not running")
|
||||
ErrServiceNotRunning = errors.New("service not running")
|
||||
ErrServerNotRunning = errors.New("server not running")
|
||||
)
|
||||
|
||||
@ -7,6 +7,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/foomo/keel/service"
|
||||
|
||||
"github.com/foomo/keel"
|
||||
"github.com/foomo/keel/config"
|
||||
@ -85,7 +86,7 @@ func main() {
|
||||
})
|
||||
|
||||
svr.AddService(
|
||||
keel.NewServiceHTTP(l, "demo", "localhost:8081", svs),
|
||||
service.NewHTTP(l, "demo", "localhost:8081", svs),
|
||||
)
|
||||
|
||||
svr.Run()
|
||||
|
||||
@ -6,6 +6,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/foomo/keel/service"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/foomo/keel"
|
||||
@ -32,7 +33,7 @@ func main() {
|
||||
})
|
||||
|
||||
svr.AddService(
|
||||
keel.NewServiceHTTP(l, "demo", "localhost:8080", svs),
|
||||
service.NewHTTP(l, "demo", "localhost:8080", svs),
|
||||
)
|
||||
|
||||
svr.Run()
|
||||
|
||||
@ -8,6 +8,8 @@ import (
|
||||
|
||||
"github.com/foomo/keel"
|
||||
"github.com/foomo/keel/examples/healthz/handler"
|
||||
"github.com/foomo/keel/healthz"
|
||||
"github.com/foomo/keel/service"
|
||||
)
|
||||
|
||||
// See k8s for probe documentation
|
||||
@ -46,7 +48,7 @@ func main() {
|
||||
svr.AddReadinessHealthzers(rh)
|
||||
|
||||
// add inline probe e.g. in case you start go routines
|
||||
svr.AddAlwaysHealthzers(keel.NewHealthzerFn(func(ctx context.Context) error {
|
||||
svr.AddAlwaysHealthzers(healthz.NewHealthzerFn(func(ctx context.Context) error {
|
||||
l.Info("healther fn")
|
||||
return nil
|
||||
}))
|
||||
@ -69,7 +71,7 @@ func main() {
|
||||
|
||||
// add services
|
||||
svr.AddService(
|
||||
keel.NewServiceHTTP(l, "demo", "localhost:8080", svs),
|
||||
service.NewHTTP(l, "demo", "localhost:8080", svs),
|
||||
)
|
||||
|
||||
// start serer
|
||||
|
||||
@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/foomo/keel"
|
||||
"github.com/foomo/keel/log"
|
||||
"github.com/foomo/keel/service"
|
||||
)
|
||||
|
||||
type CustomError struct {
|
||||
@ -46,7 +47,7 @@ func main() {
|
||||
})
|
||||
|
||||
svr.AddService(
|
||||
keel.NewServiceHTTP(l, "demo", "localhost:8080", svs),
|
||||
service.NewHTTP(l, "demo", "localhost:8080", svs),
|
||||
)
|
||||
|
||||
svr.Run()
|
||||
|
||||
@ -6,6 +6,7 @@ import (
|
||||
"github.com/foomo/keel"
|
||||
"github.com/foomo/keel/log"
|
||||
"github.com/foomo/keel/net/http/middleware"
|
||||
"github.com/foomo/keel/service"
|
||||
httputils "github.com/foomo/keel/utils/net/http"
|
||||
)
|
||||
|
||||
@ -29,7 +30,7 @@ func main() {
|
||||
log.Must(l, err, "failed to hash password")
|
||||
|
||||
svr.AddService(
|
||||
keel.NewServiceHTTP(l, "demo", "localhost:8080", svs,
|
||||
service.NewHTTP(l, "demo", "localhost:8080", svs,
|
||||
middleware.BasicAuth(
|
||||
username,
|
||||
passwordHash,
|
||||
|
||||
@ -6,6 +6,7 @@ import (
|
||||
"github.com/foomo/keel"
|
||||
keelhttp "github.com/foomo/keel/net/http"
|
||||
"github.com/foomo/keel/net/http/middleware"
|
||||
"github.com/foomo/keel/service"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@ -22,7 +23,7 @@ func main() {
|
||||
})
|
||||
|
||||
svr.AddService(
|
||||
keel.NewServiceHTTP(l, "demo", "localhost:8080", svs,
|
||||
service.NewHTTP(l, "demo", "localhost:8080", svs,
|
||||
middleware.CORS(
|
||||
middleware.CORSWithAllowOrigins("example.com"),
|
||||
middleware.CORSWithAllowMethods(http.MethodGet, http.MethodPost),
|
||||
|
||||
@ -6,6 +6,7 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/foomo/keel/service"
|
||||
jwt2 "github.com/golang-jwt/jwt"
|
||||
"go.uber.org/zap"
|
||||
|
||||
@ -75,7 +76,7 @@ func main() {
|
||||
})
|
||||
|
||||
svr.AddService(
|
||||
keel.NewServiceHTTP(l, "demo", "localhost:8080", svs,
|
||||
service.NewHTTP(l, "demo", "localhost:8080", svs,
|
||||
middleware.Skip(
|
||||
middleware.JWT(
|
||||
jwtInst,
|
||||
|
||||
@ -5,6 +5,7 @@ import (
|
||||
"crypto/rsa"
|
||||
"net/http"
|
||||
|
||||
"github.com/foomo/keel/service"
|
||||
jwt2 "github.com/golang-jwt/jwt"
|
||||
|
||||
"github.com/foomo/keel"
|
||||
@ -66,7 +67,7 @@ func main() {
|
||||
})
|
||||
|
||||
svr.AddService(
|
||||
keel.NewServiceHTTP(l, "demo", "localhost:8080", svs,
|
||||
service.NewHTTP(l, "demo", "localhost:8080", svs,
|
||||
middleware.Skip(
|
||||
middleware.JWT(
|
||||
jwtInst,
|
||||
|
||||
@ -6,6 +6,7 @@ import (
|
||||
"github.com/foomo/keel"
|
||||
keelhttp "github.com/foomo/keel/net/http"
|
||||
"github.com/foomo/keel/net/http/middleware"
|
||||
"github.com/foomo/keel/service"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@ -22,7 +23,7 @@ func main() {
|
||||
})
|
||||
|
||||
svr.AddService(
|
||||
keel.NewServiceHTTP(l, "demo", "localhost:8080", svs,
|
||||
service.NewHTTP(l, "demo", "localhost:8080", svs,
|
||||
middleware.Logger(),
|
||||
),
|
||||
)
|
||||
|
||||
@ -5,6 +5,7 @@ import (
|
||||
|
||||
"github.com/foomo/keel"
|
||||
"github.com/foomo/keel/net/http/middleware"
|
||||
"github.com/foomo/keel/service"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@ -23,7 +24,7 @@ func main() {
|
||||
})
|
||||
|
||||
svr.AddService(
|
||||
keel.NewServiceHTTP(l, "demo", "localhost:8080", svs,
|
||||
service.NewHTTP(l, "demo", "localhost:8080", svs,
|
||||
middleware.Recover(
|
||||
middleware.RecoverWithDisablePrintStack(true),
|
||||
),
|
||||
|
||||
@ -6,6 +6,7 @@ import (
|
||||
"github.com/foomo/keel"
|
||||
keelhttp "github.com/foomo/keel/net/http"
|
||||
"github.com/foomo/keel/net/http/middleware"
|
||||
"github.com/foomo/keel/service"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@ -27,7 +28,7 @@ func main() {
|
||||
})
|
||||
|
||||
svr.AddService(
|
||||
keel.NewServiceHTTP(l, "demo", "localhost:8080", svs,
|
||||
service.NewHTTP(l, "demo", "localhost:8080", svs,
|
||||
middleware.RequestID(
|
||||
middleware.RequestIDWithSetResponseHeader(true),
|
||||
middleware.RequestIDWithGenerator(requestIDGenerator),
|
||||
|
||||
@ -6,6 +6,7 @@ import (
|
||||
|
||||
"github.com/foomo/keel"
|
||||
"github.com/foomo/keel/net/http/middleware"
|
||||
"github.com/foomo/keel/service"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@ -27,7 +28,7 @@ func main() {
|
||||
})
|
||||
|
||||
svr.AddService(
|
||||
keel.NewServiceHTTP(l, "demo", "localhost:8080", svs,
|
||||
service.NewHTTP(l, "demo", "localhost:8080", svs,
|
||||
middleware.ResponseTime(
|
||||
// automatically set cookie if not exists
|
||||
middleware.ResponseTimeWithMaxDuration(time.Millisecond*500),
|
||||
|
||||
@ -8,6 +8,7 @@ import (
|
||||
keelhttp "github.com/foomo/keel/net/http"
|
||||
"github.com/foomo/keel/net/http/cookie"
|
||||
"github.com/foomo/keel/net/http/middleware"
|
||||
"github.com/foomo/keel/service"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@ -44,7 +45,7 @@ func main() {
|
||||
})
|
||||
|
||||
svr.AddService(
|
||||
keel.NewServiceHTTP(l, "demo", "localhost:8080", svs,
|
||||
service.NewHTTP(l, "demo", "localhost:8080", svs,
|
||||
middleware.SessionID(
|
||||
// automatically set cookie if not exists
|
||||
middleware.SessionIDWithSetCookie(true),
|
||||
|
||||
@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/foomo/keel/service"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/foomo/keel"
|
||||
@ -28,7 +29,7 @@ func main() {
|
||||
|
||||
svr.AddServices(
|
||||
// with URI blacklist
|
||||
keel.NewServiceHTTP(l, "demo", "localhost:8080", svs,
|
||||
service.NewHTTP(l, "demo", "localhost:8080", svs,
|
||||
middleware.Skip(
|
||||
func(l *zap.Logger, name string, next http.Handler) http.Handler {
|
||||
return http.NotFoundHandler()
|
||||
@ -38,7 +39,7 @@ func main() {
|
||||
),
|
||||
|
||||
// with URI whitelist
|
||||
keel.NewServiceHTTP(l, "demo", ":8081", svs,
|
||||
service.NewHTTP(l, "demo", "localhost:8081", svs,
|
||||
middleware.Skip(
|
||||
func(l *zap.Logger, name string, next http.Handler) http.Handler {
|
||||
return http.NotFoundHandler()
|
||||
|
||||
@ -5,6 +5,7 @@ import (
|
||||
|
||||
"github.com/foomo/keel"
|
||||
"github.com/foomo/keel/net/http/middleware"
|
||||
"github.com/foomo/keel/service"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@ -23,7 +24,7 @@ func main() {
|
||||
})
|
||||
|
||||
svr.AddService(
|
||||
keel.NewServiceHTTP(l, "demo", "localhost:8080", svs,
|
||||
service.NewHTTP(l, "demo", "localhost:8080", svs,
|
||||
middleware.Telemetry(
|
||||
middleware.TelemetryWithInjectPropagationHeader(true),
|
||||
),
|
||||
|
||||
@ -5,6 +5,7 @@ import (
|
||||
|
||||
"github.com/foomo/keel"
|
||||
"github.com/foomo/keel/net/http/middleware"
|
||||
"github.com/foomo/keel/service"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@ -26,7 +27,7 @@ func main() {
|
||||
tokenProvider := middleware.CookieTokenProvider("keel-token")
|
||||
|
||||
svr.AddService(
|
||||
keel.NewServiceHTTP(l, "demo", "localhost:8080", svs,
|
||||
service.NewHTTP(l, "demo", "localhost:8080", svs,
|
||||
middleware.TokenAuth(
|
||||
token,
|
||||
middleware.TokenAuthWithTokenProvider(tokenProvider),
|
||||
|
||||
@ -5,6 +5,7 @@ import (
|
||||
|
||||
"github.com/foomo/keel"
|
||||
"github.com/foomo/keel/net/http/middleware"
|
||||
"github.com/foomo/keel/service"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@ -29,7 +30,7 @@ func main() {
|
||||
)
|
||||
|
||||
svr.AddService(
|
||||
keel.NewServiceHTTP(l, "demo", "localhost:8080", svs,
|
||||
service.NewHTTP(l, "demo", "localhost:8080", svs,
|
||||
middleware.TokenAuth(
|
||||
token,
|
||||
middleware.TokenAuthWithTokenProvider(tokenProvider),
|
||||
|
||||
@ -6,6 +6,7 @@ import (
|
||||
|
||||
"github.com/foomo/keel"
|
||||
"github.com/foomo/keel/config"
|
||||
"github.com/foomo/keel/service"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@ -42,7 +43,7 @@ func main() {
|
||||
|
||||
// curl localhost:8080
|
||||
svr.AddService(
|
||||
keel.NewServiceHTTP(l, "demo", "localhost:8080", http.HandlerFunc(
|
||||
service.NewHTTP(l, "demo", "localhost:8080", http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Println("current foo:", fooFn()) //nolint:forbidigo
|
||||
}),
|
||||
|
||||
@ -4,6 +4,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/foomo/keel"
|
||||
"github.com/foomo/keel/service"
|
||||
)
|
||||
|
||||
func server() {
|
||||
@ -26,7 +27,7 @@ func server() {
|
||||
})
|
||||
|
||||
svr.AddService(
|
||||
keel.NewServiceHTTP(l, "demo", "localhost:8080", svs),
|
||||
service.NewHTTP(l, "demo", "localhost:8080", svs),
|
||||
)
|
||||
|
||||
svr.Run()
|
||||
|
||||
@ -8,6 +8,7 @@ import (
|
||||
keelhttp "github.com/foomo/keel/net/http"
|
||||
"github.com/foomo/keel/net/http/middleware"
|
||||
"github.com/foomo/keel/net/http/roundtripware"
|
||||
"github.com/foomo/keel/service"
|
||||
httputils "github.com/foomo/keel/utils/net/http"
|
||||
)
|
||||
|
||||
@ -52,7 +53,7 @@ func main() {
|
||||
})
|
||||
|
||||
svr.AddService(
|
||||
keel.NewServiceHTTP(l, "demo", "localhost:8080", svs,
|
||||
service.NewHTTP(l, "demo", "localhost:8080", svs,
|
||||
// add middleware
|
||||
middleware.RequestID(),
|
||||
// add middleware
|
||||
|
||||
@ -4,6 +4,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/foomo/keel"
|
||||
"github.com/foomo/keel/service"
|
||||
)
|
||||
|
||||
func server() {
|
||||
@ -27,7 +28,7 @@ func server() {
|
||||
})
|
||||
|
||||
svr.AddService(
|
||||
keel.NewServiceHTTP(l, "demo", "localhost:8080", svs),
|
||||
service.NewHTTP(l, "demo", "localhost:8080", svs),
|
||||
)
|
||||
|
||||
svr.Run()
|
||||
|
||||
@ -5,6 +5,7 @@ import (
|
||||
|
||||
"github.com/foomo/keel"
|
||||
"github.com/foomo/keel/config"
|
||||
"github.com/foomo/keel/service"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@ -23,7 +24,7 @@ func main() {
|
||||
})
|
||||
|
||||
svr.AddServices(
|
||||
keel.NewServiceHTTP(l, "demo", "localhost:8080",
|
||||
service.NewHTTP(l, "demo", "localhost:8080",
|
||||
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
c.Set("service.enabled", !enabled())
|
||||
w.WriteHeader(http.StatusOK)
|
||||
@ -32,7 +33,7 @@ func main() {
|
||||
),
|
||||
keel.NewServiceEnabler(l, "service-enabler",
|
||||
func() keel.Service {
|
||||
return keel.NewServiceHTTP(l, "service", "localhost:8081", svs)
|
||||
return service.NewHTTP(l, "service", "localhost:8081", svs)
|
||||
},
|
||||
enabled,
|
||||
),
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/foomo/keel"
|
||||
@ -29,24 +28,11 @@ func main() {
|
||||
keel.WithHTTPPProfService(false),
|
||||
)
|
||||
|
||||
l := svr.Logger()
|
||||
|
||||
// alternatively you can add them manually
|
||||
// svr.AddServices(keel.NewDefaultServiceHTTPZap())
|
||||
// svr.AddServices(keel.NewDefaultServiceHTTPViper())
|
||||
// svr.AddServices(keel.NewDefaultServiceHTTPPProf())
|
||||
// svr.AddServices(keel.NewDefaultServiceHTTPPrometheus())
|
||||
|
||||
// create demo service
|
||||
svs := http.NewServeMux()
|
||||
svs.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write([]byte("OK"))
|
||||
})
|
||||
|
||||
svr.AddService(
|
||||
keel.NewServiceHTTP(l, "demo", ":8080", svs),
|
||||
)
|
||||
|
||||
svr.Run()
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/foomo/keel/service"
|
||||
"github.com/nats-io/nats.go"
|
||||
"github.com/pkg/errors"
|
||||
"go.uber.org/zap"
|
||||
@ -89,7 +90,7 @@ func main() {
|
||||
svr.AddClosers(subscription, stream)
|
||||
|
||||
svr.AddService(
|
||||
keel.NewServiceHTTP(l, "demo", "localhost:8080", svs),
|
||||
service.NewHTTP(l, "demo", "localhost:8080", svs),
|
||||
)
|
||||
|
||||
svr.Run()
|
||||
|
||||
@ -4,6 +4,7 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/foomo/keel/service"
|
||||
"github.com/nats-io/nats.go"
|
||||
|
||||
"github.com/foomo/keel"
|
||||
@ -73,7 +74,7 @@ func main() {
|
||||
svr.AddClosers(subscription, stream.Conn())
|
||||
|
||||
svr.AddService(
|
||||
keel.NewServiceHTTP(l, "demo", "localhost:8080", svs),
|
||||
service.NewHTTP(l, "demo", "localhost:8080", svs),
|
||||
)
|
||||
|
||||
svr.Run()
|
||||
|
||||
@ -4,6 +4,9 @@ import (
|
||||
"math/rand"
|
||||
"net/http"
|
||||
|
||||
"github.com/foomo/keel/service"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/metric/instrument"
|
||||
|
||||
@ -58,6 +61,14 @@ func main() {
|
||||
})
|
||||
}
|
||||
|
||||
promauto.NewCounter(prometheus.CounterOpts{
|
||||
Namespace: "foo",
|
||||
Subsystem: "",
|
||||
Name: "bar",
|
||||
Help: "blubb",
|
||||
ConstLabels: nil,
|
||||
})
|
||||
|
||||
{ // up down
|
||||
upDown, err := meter.SyncInt64().UpDownCounter(
|
||||
"a.updown",
|
||||
@ -92,7 +103,7 @@ func main() {
|
||||
}
|
||||
|
||||
svr.AddService(
|
||||
keel.NewServiceHTTP(l, "demo", "localhost:8080", svs,
|
||||
service.NewHTTP(l, "demo", "localhost:8080", svs,
|
||||
middleware.Telemetry(),
|
||||
middleware.Recover(),
|
||||
),
|
||||
|
||||
1
go.mod
1
go.mod
@ -5,6 +5,7 @@ go 1.20
|
||||
require (
|
||||
github.com/avast/retry-go v3.0.0+incompatible
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/fbiville/markdown-table-formatter v0.3.0
|
||||
github.com/foomo/gotsrpc/v2 v2.7.2
|
||||
github.com/go-logr/logr v1.2.4
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible
|
||||
|
||||
2
go.sum
2
go.sum
@ -117,6 +117,8 @@ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv
|
||||
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
||||
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
|
||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||
github.com/fbiville/markdown-table-formatter v0.3.0 h1:PIm1UNgJrFs8q1htGTw+wnnNYvwXQMMMIKNZop2SSho=
|
||||
github.com/fbiville/markdown-table-formatter v0.3.0/go.mod h1:q89TDtSEVDdTaufgSbfHpNVdPU/bmfvqNkrC5HagmLY=
|
||||
github.com/felixge/httpsnoop v1.0.2 h1:+nS9g82KMXccJ/wp0zyRW9ZBHFETmMGtkk+2CTTrW4o=
|
||||
github.com/felixge/httpsnoop v1.0.2/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/foomo/gotsrpc/v2 v2.7.2 h1:a94V/a8LSssq+aRN3Fv1lJPjWoyMilOvRq+yEaDTHVM=
|
||||
|
||||
20
healthz.go
Normal file
20
healthz.go
Normal file
@ -0,0 +1,20 @@
|
||||
package keel
|
||||
|
||||
import (
|
||||
"github.com/foomo/keel/healthz"
|
||||
"github.com/foomo/keel/interfaces"
|
||||
)
|
||||
|
||||
func IsHealthz(v any) bool {
|
||||
switch v.(type) {
|
||||
case healthz.BoolHealthzer,
|
||||
healthz.BoolHealthzerWithContext,
|
||||
healthz.ErrorHealthzer,
|
||||
healthz.ErrorHealthzWithContext,
|
||||
interfaces.ErrorPinger,
|
||||
interfaces.ErrorPingerWithContext:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
1
healthz/docs.go
Normal file
1
healthz/docs.go
Normal file
@ -0,0 +1 @@
|
||||
package healthz
|
||||
@ -1,4 +1,4 @@
|
||||
package keel
|
||||
package healthz
|
||||
|
||||
import "context"
|
||||
|
||||
@ -16,6 +16,10 @@ func (h healther) Healthz(ctx context.Context) error {
|
||||
return h.handle(ctx)
|
||||
}
|
||||
|
||||
func (h healther) Close(ctx context.Context) error {
|
||||
return h.handle(ctx)
|
||||
}
|
||||
|
||||
// BoolHealthzer interface
|
||||
type BoolHealthzer interface {
|
||||
Healthz() bool
|
||||
@ -1,31 +1,31 @@
|
||||
package keel
|
||||
package healthz
|
||||
|
||||
// HealthzType type
|
||||
// Type type
|
||||
// https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/
|
||||
type HealthzType string
|
||||
type Type string
|
||||
|
||||
const (
|
||||
// HealthzTypeAlways will run on any checks
|
||||
HealthzTypeAlways HealthzType = "always"
|
||||
// HealthzTypeStartup will run on /healthz/startup checks
|
||||
// TypeAlways will run on any checks
|
||||
TypeAlways Type = "always"
|
||||
// TypeStartup will run on /healthz/startup checks
|
||||
// > The kubelet uses startup probes to know when a container application has started. If such a probe is configured,
|
||||
// > it disables liveness and readiness checks until it succeeds, making sure those probes don't interfere with the
|
||||
// > application startup. This can be used to adopt liveness checks on slow starting containers, avoiding them getting
|
||||
// > killed by the kubelet before they are up and running.
|
||||
HealthzTypeStartup HealthzType = "startup"
|
||||
// HealthzTypeReadiness will run on /healthz/readiness checks
|
||||
TypeStartup Type = "startup"
|
||||
// TypeReadiness will run on /healthz/readiness checks
|
||||
// > The kubelet uses readiness probes to know when a container is ready to start accepting traffic.
|
||||
// > A Pod is considered ready when all of its containers are ready. One use of this signal is to control
|
||||
// > which Pods are used as backends for Services. When a Pod is not ready, it is removed from Service load balancers.
|
||||
HealthzTypeReadiness HealthzType = "readiness"
|
||||
// HealthzTypeLiveness will run on /healthz/liveness checks
|
||||
TypeReadiness Type = "readiness"
|
||||
// TypeLiveness will run on /healthz/liveness checks
|
||||
// > The kubelet uses liveness probes to know when to restart a container. For example, liveness probes could catch
|
||||
// > a deadlock, where an application is running, but unable to make progress. Restarting a container in such a state
|
||||
// > can help to make the application more available despite bugs.
|
||||
HealthzTypeLiveness HealthzType = "liveness"
|
||||
TypeLiveness Type = "liveness"
|
||||
)
|
||||
|
||||
// String interface
|
||||
func (t HealthzType) String() string {
|
||||
func (t Type) String() string {
|
||||
return string(t)
|
||||
}
|
||||
25
interfaces/closer.go
Normal file
25
interfaces/closer.go
Normal file
@ -0,0 +1,25 @@
|
||||
package interfaces
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
// Closer interface
|
||||
type Closer interface {
|
||||
Close()
|
||||
}
|
||||
|
||||
// ErrorCloser interface
|
||||
type ErrorCloser interface {
|
||||
Close() error
|
||||
}
|
||||
|
||||
// CloserWithContext interface
|
||||
type CloserWithContext interface {
|
||||
Close(ctx context.Context)
|
||||
}
|
||||
|
||||
// ErrorCloserWithContext interface
|
||||
type ErrorCloserWithContext interface {
|
||||
Close(ctx context.Context) error
|
||||
}
|
||||
1
interfaces/doc.go
Normal file
1
interfaces/doc.go
Normal file
@ -0,0 +1 @@
|
||||
package interfaces
|
||||
6
interfaces/namer.go
Normal file
6
interfaces/namer.go
Normal file
@ -0,0 +1,6 @@
|
||||
package interfaces
|
||||
|
||||
// Namer interface
|
||||
type Namer interface {
|
||||
Name() string
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
package keel
|
||||
package interfaces
|
||||
|
||||
import "context"
|
||||
|
||||
20
interfaces/readmer.go
Normal file
20
interfaces/readmer.go
Normal file
@ -0,0 +1,20 @@
|
||||
package interfaces
|
||||
|
||||
// Readmer interface
|
||||
type Readmer interface {
|
||||
Readme() string
|
||||
}
|
||||
|
||||
type ReadmeHandler struct {
|
||||
Value func() string
|
||||
}
|
||||
|
||||
func (r ReadmeHandler) Readme() string {
|
||||
return r.Value()
|
||||
}
|
||||
|
||||
func ReadmeFunc(v func() string) ReadmeHandler {
|
||||
return ReadmeHandler{
|
||||
Value: v,
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
package keel
|
||||
package interfaces
|
||||
|
||||
import "context"
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
package keel
|
||||
package interfaces
|
||||
|
||||
import "context"
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
package keel
|
||||
package interfaces
|
||||
|
||||
import "context"
|
||||
|
||||
11
log/fields_keel.go
Normal file
11
log/fields_keel.go
Normal file
@ -0,0 +1,11 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
)
|
||||
|
||||
const (
|
||||
KeelServiceTypeKey = attribute.Key("keel.service.type")
|
||||
KeelServiceNameKey = attribute.Key("keel.service.name")
|
||||
KeelServiceInstKey = attribute.Key("keel.service.inst")
|
||||
)
|
||||
@ -15,6 +15,8 @@ func FPeerService(name string) zap.Field {
|
||||
}
|
||||
|
||||
const (
|
||||
ServiceTypeKey = "service_type"
|
||||
|
||||
// ServiceNameKey represents the NameKey of the service.
|
||||
ServiceNameKey = "service_name"
|
||||
|
||||
@ -35,6 +37,10 @@ const (
|
||||
ServiceVersionKey = "service_version"
|
||||
)
|
||||
|
||||
func FServiceType(name string) zap.Field {
|
||||
return zap.String(ServiceTypeKey, name)
|
||||
}
|
||||
|
||||
func FServiceName(name string) zap.Field {
|
||||
return zap.String(ServiceNameKey, name)
|
||||
}
|
||||
|
||||
12
log/with.go
12
log/with.go
@ -7,6 +7,7 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
"go.uber.org/zap"
|
||||
|
||||
@ -20,6 +21,17 @@ func With(l *zap.Logger, fields ...zap.Field) *zap.Logger {
|
||||
return l.With(fields...)
|
||||
}
|
||||
|
||||
func WithAttributes(l *zap.Logger, attrs ...attribute.KeyValue) *zap.Logger {
|
||||
if l == nil {
|
||||
l = Logger()
|
||||
}
|
||||
fields := make([]zap.Field, len(attrs))
|
||||
for i, attr := range attrs {
|
||||
fields[i] = zap.Any(strings.ReplaceAll(string(attr.Key), ".", "_"), attr.Value.AsInterface())
|
||||
}
|
||||
return l.With(fields...)
|
||||
}
|
||||
|
||||
func WithError(l *zap.Logger, err error) *zap.Logger {
|
||||
return With(l, FErrorType(err), FError(err))
|
||||
}
|
||||
|
||||
40
markdown/markdown.go
Normal file
40
markdown/markdown.go
Normal file
@ -0,0 +1,40 @@
|
||||
package markdown
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
markdowntable "github.com/fbiville/markdown-table-formatter/pkg/markdown"
|
||||
)
|
||||
|
||||
// Markdown output helper
|
||||
type Markdown struct {
|
||||
value string
|
||||
}
|
||||
|
||||
func (s *Markdown) Println(a ...any) {
|
||||
s.value += fmt.Sprintln(a...)
|
||||
}
|
||||
|
||||
func (s *Markdown) Printf(format string, a ...any) {
|
||||
s.Println(fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
func (s *Markdown) Print(a ...any) {
|
||||
s.value += fmt.Sprint(a...)
|
||||
}
|
||||
|
||||
func (s *Markdown) String() string {
|
||||
return s.value
|
||||
}
|
||||
|
||||
func (s *Markdown) Table(headers []string, rows [][]string) {
|
||||
table, err := markdowntable.NewTableFormatterBuilder().
|
||||
WithAlphabeticalSortIn(markdowntable.ASCENDING_ORDER).
|
||||
WithPrettyPrint().
|
||||
Build(headers...).
|
||||
Format(rows)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
s.Print(table)
|
||||
}
|
||||
28
markdown/utils.go
Normal file
28
markdown/utils.go
Normal file
@ -0,0 +1,28 @@
|
||||
package markdown
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func Code(v string) string {
|
||||
if v == "" {
|
||||
return ""
|
||||
}
|
||||
return "`" + v + "`"
|
||||
}
|
||||
|
||||
func Name(v any) string {
|
||||
if i, ok := v.(interface {
|
||||
Name() string
|
||||
}); ok {
|
||||
return i.Name()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func String(v any) string {
|
||||
if i, ok := v.(fmt.Stringer); ok {
|
||||
return i.String()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
32
metrics/readme.go
Normal file
32
metrics/readme.go
Normal file
@ -0,0 +1,32 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"github.com/foomo/keel/markdown"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
func Readme() string {
|
||||
md := markdown.Markdown{}
|
||||
var rows [][]string
|
||||
|
||||
if gatherer, err := prometheus.DefaultGatherer.Gather(); err == nil {
|
||||
for _, value := range gatherer {
|
||||
rows = append(rows, []string{
|
||||
markdown.Code(value.GetName()),
|
||||
value.GetType().String(),
|
||||
value.GetHelp(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if len(rows) > 0 {
|
||||
md.Println("### Metrics")
|
||||
md.Println("")
|
||||
md.Println("List of all registered metrics than are being exposed.")
|
||||
md.Println("")
|
||||
md.Table([]string{"Name", "Type", "Description"}, rows)
|
||||
md.Println("")
|
||||
}
|
||||
|
||||
return md.String()
|
||||
}
|
||||
62
net/stream/jetstream/readme.go
Normal file
62
net/stream/jetstream/readme.go
Normal file
@ -0,0 +1,62 @@
|
||||
package jetstream
|
||||
|
||||
import (
|
||||
"github.com/foomo/keel/markdown"
|
||||
)
|
||||
|
||||
type (
|
||||
publisher struct {
|
||||
Namespace string
|
||||
Stream string
|
||||
Subject string
|
||||
}
|
||||
subscriber struct {
|
||||
Namespace string
|
||||
Stream string
|
||||
Subject string
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
publishers []publisher
|
||||
subscribers []subscriber
|
||||
)
|
||||
|
||||
func Readme() string {
|
||||
if len(publishers) == 0 && len(subscribers) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
var rows [][]string
|
||||
md := &markdown.Markdown{}
|
||||
md.Println("### NATS")
|
||||
md.Println("")
|
||||
md.Println("List of all registered nats publishers & subscribers.")
|
||||
md.Println("")
|
||||
|
||||
if len(publishers) > 0 {
|
||||
for _, value := range publishers {
|
||||
rows = append(rows, []string{
|
||||
markdown.Code(value.Namespace),
|
||||
markdown.Code(value.Stream),
|
||||
markdown.Code(value.Subject),
|
||||
markdown.Code("publish"),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if len(subscribers) > 0 {
|
||||
for _, value := range subscribers {
|
||||
rows = append(rows, []string{
|
||||
markdown.Code(value.Namespace),
|
||||
markdown.Code(value.Stream),
|
||||
markdown.Code(value.Subject),
|
||||
markdown.Code("subscribe"),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
md.Table([]string{"Namespace", "Stream", "Subject", "Type"}, rows)
|
||||
|
||||
return md.String()
|
||||
}
|
||||
@ -2,6 +2,7 @@ package jetstream
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"github.com/nats-io/nats.go"
|
||||
@ -280,6 +281,20 @@ func (s *Stream) Publisher(subject string, opts ...PublisherOption) *Publisher {
|
||||
opt(pub)
|
||||
}
|
||||
}
|
||||
|
||||
{ // append to recoreded publishers
|
||||
value := publisher{
|
||||
Stream: s.name,
|
||||
Namespace: s.namespace,
|
||||
Subject: subject,
|
||||
}
|
||||
if !slices.ContainsFunc(publishers, func(p publisher) bool {
|
||||
return p.Stream == value.Stream && p.Namespace == value.Namespace && p.Subject == value.Subject
|
||||
}) {
|
||||
publishers = append(publishers, value)
|
||||
}
|
||||
}
|
||||
|
||||
return pub
|
||||
}
|
||||
|
||||
@ -295,6 +310,20 @@ func (s *Stream) Subscriber(subject string, opts ...SubscriberOption) *Subscribe
|
||||
opt(sub)
|
||||
}
|
||||
}
|
||||
|
||||
{ // append to recoreded publishers
|
||||
value := subscriber{
|
||||
Stream: s.name,
|
||||
Namespace: s.namespace,
|
||||
Subject: subject,
|
||||
}
|
||||
if !slices.ContainsFunc(subscribers, func(p subscriber) bool {
|
||||
return p.Stream == value.Stream && p.Namespace == value.Namespace && p.Subject == value.Subject
|
||||
}) {
|
||||
subscribers = append(subscribers, value)
|
||||
}
|
||||
}
|
||||
|
||||
return sub
|
||||
}
|
||||
|
||||
|
||||
42
option.go
42
option.go
@ -5,6 +5,7 @@ import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/foomo/keel/service"
|
||||
"github.com/spf13/viper"
|
||||
"go.uber.org/zap"
|
||||
|
||||
@ -72,9 +73,9 @@ func WithShutdownTimeout(shutdownTimeout time.Duration) Option {
|
||||
func WithHTTPZapService(enabled bool) Option {
|
||||
return func(inst *Server) {
|
||||
if config.GetBool(inst.Config(), "service.zap.enabled", enabled)() {
|
||||
service := NewDefaultServiceHTTPZap()
|
||||
inst.initServices = append(inst.initServices, service)
|
||||
inst.AddAlwaysHealthzers(service)
|
||||
svs := service.NewDefaultHTTPZap(inst.Logger())
|
||||
inst.initServices = append(inst.initServices, svs)
|
||||
inst.AddAlwaysHealthzers(svs)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -83,9 +84,9 @@ func WithHTTPZapService(enabled bool) Option {
|
||||
func WithHTTPViperService(enabled bool) Option {
|
||||
return func(inst *Server) {
|
||||
if config.GetBool(inst.Config(), "service.viper.enabled", enabled)() {
|
||||
service := NewDefaultServiceHTTPViper()
|
||||
inst.initServices = append(inst.initServices, service)
|
||||
inst.AddAlwaysHealthzers(service)
|
||||
svs := service.NewDefaultHTTPViper(inst.Logger())
|
||||
inst.initServices = append(inst.initServices, svs)
|
||||
inst.AddAlwaysHealthzers(svs)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -149,9 +150,9 @@ func WithPrometheusMeter(enabled bool) Option {
|
||||
func WithHTTPPrometheusService(enabled bool) Option {
|
||||
return func(inst *Server) {
|
||||
if config.GetBool(inst.Config(), "service.prometheus.enabled", enabled)() {
|
||||
service := NewDefaultServiceHTTPPrometheus()
|
||||
inst.initServices = append(inst.initServices, service)
|
||||
inst.AddAlwaysHealthzers(service)
|
||||
svs := service.NewDefaultHTTPPrometheus(inst.Logger())
|
||||
inst.initServices = append(inst.initServices, svs)
|
||||
inst.AddAlwaysHealthzers(svs)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -160,9 +161,9 @@ func WithHTTPPrometheusService(enabled bool) Option {
|
||||
func WithHTTPPProfService(enabled bool) Option {
|
||||
return func(inst *Server) {
|
||||
if config.GetBool(inst.Config(), "service.pprof.enabled", enabled)() {
|
||||
service := NewDefaultServiceHTTPPProf()
|
||||
inst.initServices = append(inst.initServices, service)
|
||||
inst.AddAlwaysHealthzers(service)
|
||||
svs := service.NewDefaultHTTPPProf(inst.Logger())
|
||||
inst.initServices = append(inst.initServices, svs)
|
||||
inst.AddAlwaysHealthzers(svs)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -171,9 +172,20 @@ func WithHTTPPProfService(enabled bool) Option {
|
||||
func WithHTTPHealthzService(enabled bool) Option {
|
||||
return func(inst *Server) {
|
||||
if config.GetBool(inst.Config(), "service.healthz.enabled", enabled)() {
|
||||
service := NewDefaultServiceHTTPProbes(inst.probes())
|
||||
inst.initServices = append(inst.initServices, service)
|
||||
inst.AddAlwaysHealthzers(service)
|
||||
svs := service.NewDefaultHTTPProbes(inst.Logger(), inst.probes())
|
||||
inst.initServices = append(inst.initServices, svs)
|
||||
inst.AddAlwaysHealthzers(svs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WithHTTPReadmeService option with default value
|
||||
func WithHTTPReadmeService(enabled bool) Option {
|
||||
return func(inst *Server) {
|
||||
if config.GetBool(inst.Config(), "service.readme.enabled", enabled)() {
|
||||
svs := service.NewDefaultHTTPReadme(inst.Logger(), inst.readmers)
|
||||
inst.initServices = append(inst.initServices, svs)
|
||||
inst.AddAlwaysHealthzers(svs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ package keelmongo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
keelerrors "github.com/foomo/keel/errors"
|
||||
@ -120,11 +121,22 @@ func NewCollection(db *mongo.Database, name string, opts ...CollectionOption) (*
|
||||
}
|
||||
|
||||
col := db.Collection(name, o.CollectionOptions)
|
||||
if !slices.Contains(dbs[db.Name()], name) {
|
||||
dbs[db.Name()] = append(dbs[db.Name()], name)
|
||||
}
|
||||
|
||||
if len(o.Indexes) > 0 {
|
||||
if _, err := col.Indexes().CreateMany(o.IndexesContext, o.Indexes, o.CreateIndexesOptions); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, ok := indices[db.Name()]; !ok {
|
||||
indices[db.Name()] = map[string][]string{}
|
||||
}
|
||||
for _, index := range o.Indexes {
|
||||
if index.Options != nil && index.Options.Name != nil {
|
||||
indices[db.Name()][name] = append(indices[db.Name()][name], *index.Options.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &Collection{
|
||||
|
||||
42
persistence/mongo/readme.go
Normal file
42
persistence/mongo/readme.go
Normal file
@ -0,0 +1,42 @@
|
||||
package keelmongo
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/foomo/keel/markdown"
|
||||
)
|
||||
|
||||
var (
|
||||
dbs = map[string][]string{}
|
||||
indices = map[string]map[string][]string{}
|
||||
)
|
||||
|
||||
func Readme() string {
|
||||
var rows [][]string
|
||||
md := &markdown.Markdown{}
|
||||
|
||||
for db, collections := range dbs {
|
||||
for _, collection := range collections {
|
||||
var i string
|
||||
if v, ok := indices[db][collection]; ok {
|
||||
i += strings.Join(v, "`, `")
|
||||
}
|
||||
rows = append(rows, []string{
|
||||
markdown.Code(db),
|
||||
markdown.Code(collection),
|
||||
markdown.Code(i),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if len(rows) > 0 {
|
||||
md.Println("### Mongo")
|
||||
md.Println("")
|
||||
md.Println("List of all used mongo collections including the configured indices options.")
|
||||
md.Println("")
|
||||
md.Table([]string{"Database", "Collection", "Indices"}, rows)
|
||||
md.Println("")
|
||||
}
|
||||
|
||||
return md.String()
|
||||
}
|
||||
330
server.go
330
server.go
@ -6,11 +6,18 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"reflect"
|
||||
"slices"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/foomo/keel/healthz"
|
||||
"github.com/foomo/keel/interfaces"
|
||||
"github.com/foomo/keel/markdown"
|
||||
"github.com/foomo/keel/metrics"
|
||||
"github.com/foomo/keel/service"
|
||||
"github.com/go-logr/logr"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/viper"
|
||||
@ -31,33 +38,36 @@ import (
|
||||
|
||||
// Server struct
|
||||
type Server struct {
|
||||
services []Service
|
||||
initServices []Service
|
||||
meter metric.Meter
|
||||
meterProvider metric.MeterProvider
|
||||
tracer trace.Tracer
|
||||
traceProvider trace.TracerProvider
|
||||
shutdownSignals []os.Signal
|
||||
shutdownTimeout time.Duration
|
||||
running atomic.Bool
|
||||
syncClosers []interface{}
|
||||
syncClosersLock sync.RWMutex
|
||||
syncProbes map[HealthzType][]interface{}
|
||||
syncProbesLock sync.RWMutex
|
||||
ctx context.Context
|
||||
ctxCancel context.Context
|
||||
ctxCancelFn context.CancelFunc
|
||||
g *errgroup.Group
|
||||
gCtx context.Context
|
||||
l *zap.Logger
|
||||
c *viper.Viper
|
||||
services []Service
|
||||
initServices []Service
|
||||
meter metric.Meter
|
||||
meterProvider metric.MeterProvider
|
||||
tracer trace.Tracer
|
||||
traceProvider trace.TracerProvider
|
||||
shutdownSignals []os.Signal
|
||||
shutdownTimeout time.Duration
|
||||
running atomic.Bool
|
||||
syncClosers []interface{}
|
||||
syncClosersLock sync.RWMutex
|
||||
syncReadmers []interfaces.Readmer
|
||||
syncReadmersLock sync.RWMutex
|
||||
syncProbes map[healthz.Type][]interface{}
|
||||
syncProbesLock sync.RWMutex
|
||||
ctx context.Context
|
||||
ctxCancel context.Context
|
||||
ctxCancelFn context.CancelFunc
|
||||
g *errgroup.Group
|
||||
gCtx context.Context
|
||||
l *zap.Logger
|
||||
c *viper.Viper
|
||||
}
|
||||
|
||||
func NewServer(opts ...Option) *Server {
|
||||
inst := &Server{
|
||||
shutdownTimeout: 30 * time.Second,
|
||||
shutdownSignals: []os.Signal{os.Interrupt, syscall.SIGTERM},
|
||||
syncProbes: map[HealthzType][]interface{}{},
|
||||
syncReadmers: []interfaces.Readmer{},
|
||||
syncProbes: map[healthz.Type][]interface{}{},
|
||||
ctx: context.Background(),
|
||||
c: config.Config(),
|
||||
l: log.Logger(),
|
||||
@ -86,51 +96,51 @@ func NewServer(opts ...Option) *Server {
|
||||
for _, closer := range closers {
|
||||
l := inst.l.With(log.FName(fmt.Sprintf("%T", closer)))
|
||||
switch c := closer.(type) {
|
||||
case Closer:
|
||||
case interfaces.Closer:
|
||||
c.Close()
|
||||
case ErrorCloser:
|
||||
case interfaces.ErrorCloser:
|
||||
if err := c.Close(); err != nil {
|
||||
log.WithError(l, err).Error("failed to gracefully stop ErrorCloser")
|
||||
}
|
||||
case CloserWithContext:
|
||||
case interfaces.CloserWithContext:
|
||||
c.Close(timeoutCtx)
|
||||
case ErrorCloserWithContext:
|
||||
case interfaces.ErrorCloserWithContext:
|
||||
if err := c.Close(timeoutCtx); err != nil {
|
||||
log.WithError(l, err).Error("failed to gracefully stop ErrorCloserWithContext")
|
||||
}
|
||||
case Shutdowner:
|
||||
case interfaces.Shutdowner:
|
||||
c.Shutdown()
|
||||
case ErrorShutdowner:
|
||||
case interfaces.ErrorShutdowner:
|
||||
if err := c.Shutdown(); err != nil {
|
||||
log.WithError(l, err).Error("failed to gracefully stop ErrorShutdowner")
|
||||
}
|
||||
case ShutdownerWithContext:
|
||||
case interfaces.ShutdownerWithContext:
|
||||
c.Shutdown(timeoutCtx)
|
||||
case ErrorShutdownerWithContext:
|
||||
case interfaces.ErrorShutdownerWithContext:
|
||||
if err := c.Shutdown(timeoutCtx); err != nil {
|
||||
log.WithError(l, err).Error("failed to gracefully stop ErrorShutdownerWithContext")
|
||||
}
|
||||
case Stopper:
|
||||
case interfaces.Stopper:
|
||||
c.Stop()
|
||||
case ErrorStopper:
|
||||
case interfaces.ErrorStopper:
|
||||
if err := c.Stop(); err != nil {
|
||||
log.WithError(l, err).Error("failed to gracefully stop ErrorStopper")
|
||||
}
|
||||
case StopperWithContext:
|
||||
case interfaces.StopperWithContext:
|
||||
c.Stop(timeoutCtx)
|
||||
case ErrorStopperWithContext:
|
||||
case interfaces.ErrorStopperWithContext:
|
||||
if err := c.Stop(timeoutCtx); err != nil {
|
||||
log.WithError(l, err).Error("failed to gracefully stop ErrorStopperWithContext")
|
||||
}
|
||||
case Unsubscriber:
|
||||
case interfaces.Unsubscriber:
|
||||
c.Unsubscribe()
|
||||
case ErrorUnsubscriber:
|
||||
case interfaces.ErrorUnsubscriber:
|
||||
if err := c.Unsubscribe(); err != nil {
|
||||
log.WithError(l, err).Error("failed to gracefully stop ErrorUnsubscriber")
|
||||
}
|
||||
case UnsubscriberWithContext:
|
||||
case interfaces.UnsubscriberWithContext:
|
||||
c.Unsubscribe(timeoutCtx)
|
||||
case ErrorUnsubscriberWithContext:
|
||||
case interfaces.ErrorUnsubscriberWithContext:
|
||||
if err := c.Unsubscribe(timeoutCtx); err != nil {
|
||||
log.WithError(l, err).Error("failed to gracefully stop ErrorUnsubscriberWithContext")
|
||||
}
|
||||
@ -168,6 +178,12 @@ func NewServer(opts ...Option) *Server {
|
||||
|
||||
// add probe
|
||||
inst.AddAlwaysHealthzers(inst)
|
||||
inst.AddReadmers(
|
||||
interfaces.ReadmeFunc(env.Readme),
|
||||
interfaces.ReadmeFunc(config.Readme),
|
||||
inst,
|
||||
interfaces.ReadmeFunc(metrics.Readme),
|
||||
)
|
||||
|
||||
// start init services
|
||||
inst.startService(inst.initServices...)
|
||||
@ -207,20 +223,17 @@ func (s *Server) CancelContext() context.Context {
|
||||
|
||||
// AddService add a single service
|
||||
func (s *Server) AddService(service Service) {
|
||||
for _, value := range s.services {
|
||||
if value == service {
|
||||
return
|
||||
}
|
||||
if !slices.Contains(s.services, service) {
|
||||
s.services = append(s.services, service)
|
||||
s.AddAlwaysHealthzers(service)
|
||||
s.AddCloser(service)
|
||||
}
|
||||
s.services = append(s.services, service)
|
||||
s.AddAlwaysHealthzers(service)
|
||||
s.AddCloser(service)
|
||||
}
|
||||
|
||||
// AddServices adds multiple service
|
||||
func (s *Server) AddServices(services ...Service) {
|
||||
for _, service := range services {
|
||||
s.AddService(service)
|
||||
for _, value := range services {
|
||||
s.AddService(value)
|
||||
}
|
||||
}
|
||||
|
||||
@ -231,25 +244,9 @@ func (s *Server) AddCloser(closer interface{}) {
|
||||
return
|
||||
}
|
||||
}
|
||||
switch closer.(type) {
|
||||
case Closer,
|
||||
ErrorCloser,
|
||||
CloserWithContext,
|
||||
ErrorCloserWithContext,
|
||||
Shutdowner,
|
||||
ErrorShutdowner,
|
||||
ShutdownerWithContext,
|
||||
ErrorShutdownerWithContext,
|
||||
Stopper,
|
||||
ErrorStopper,
|
||||
StopperWithContext,
|
||||
ErrorStopperWithContext,
|
||||
Unsubscriber,
|
||||
ErrorUnsubscriber,
|
||||
UnsubscriberWithContext,
|
||||
ErrorUnsubscriberWithContext:
|
||||
if IsCloser(closer) {
|
||||
s.addClosers(closer)
|
||||
default:
|
||||
} else {
|
||||
s.l.Warn("unable to add closer", log.FValue(fmt.Sprintf("%T", closer)))
|
||||
}
|
||||
}
|
||||
@ -261,23 +258,29 @@ func (s *Server) AddClosers(closers ...interface{}) {
|
||||
}
|
||||
}
|
||||
|
||||
// AddReadmer adds a readmer to be added to the exposed readme
|
||||
func (s *Server) AddReadmer(readmer interfaces.Readmer) {
|
||||
s.addReadmers(readmer)
|
||||
}
|
||||
|
||||
// AddReadmers adds readmers to be added to the exposed readme
|
||||
func (s *Server) AddReadmers(readmers ...interfaces.Readmer) {
|
||||
for _, readmer := range readmers {
|
||||
s.AddReadmer(readmer)
|
||||
}
|
||||
}
|
||||
|
||||
// AddHealthzer adds a probe to be called on healthz checks
|
||||
func (s *Server) AddHealthzer(typ HealthzType, probe interface{}) {
|
||||
switch probe.(type) {
|
||||
case BoolHealthzer,
|
||||
BoolHealthzerWithContext,
|
||||
ErrorHealthzer,
|
||||
ErrorHealthzWithContext,
|
||||
ErrorPinger,
|
||||
ErrorPingerWithContext:
|
||||
func (s *Server) AddHealthzer(typ healthz.Type, probe interface{}) {
|
||||
if IsHealthz(probe) {
|
||||
s.addProbes(typ, probe)
|
||||
default:
|
||||
} else {
|
||||
s.l.Debug("not a healthz probe", log.FValue(fmt.Sprintf("%T", probe)))
|
||||
}
|
||||
}
|
||||
|
||||
// AddHealthzers adds the given probes to be called on healthz checks
|
||||
func (s *Server) AddHealthzers(typ HealthzType, probes ...interface{}) {
|
||||
func (s *Server) AddHealthzers(typ healthz.Type, probes ...interface{}) {
|
||||
for _, probe := range probes {
|
||||
s.AddHealthzer(typ, probe)
|
||||
}
|
||||
@ -285,22 +288,22 @@ func (s *Server) AddHealthzers(typ HealthzType, probes ...interface{}) {
|
||||
|
||||
// AddAlwaysHealthzers adds the probes to be called on any healthz checks
|
||||
func (s *Server) AddAlwaysHealthzers(probes ...interface{}) {
|
||||
s.AddHealthzers(HealthzTypeAlways, probes...)
|
||||
s.AddHealthzers(healthz.TypeAlways, probes...)
|
||||
}
|
||||
|
||||
// AddStartupHealthzers adds the startup probes to be called on healthz checks
|
||||
func (s *Server) AddStartupHealthzers(probes ...interface{}) {
|
||||
s.AddHealthzers(HealthzTypeStartup, probes...)
|
||||
s.AddHealthzers(healthz.TypeStartup, probes...)
|
||||
}
|
||||
|
||||
// AddLivenessHealthzers adds the liveness probes to be called on healthz checks
|
||||
func (s *Server) AddLivenessHealthzers(probes ...interface{}) {
|
||||
s.AddHealthzers(HealthzTypeLiveness, probes...)
|
||||
s.AddHealthzers(healthz.TypeLiveness, probes...)
|
||||
}
|
||||
|
||||
// AddReadinessHealthzers adds the readiness probes to be called on healthz checks
|
||||
func (s *Server) AddReadinessHealthzers(probes ...interface{}) {
|
||||
s.AddHealthzers(HealthzTypeReadiness, probes...)
|
||||
s.AddHealthzers(healthz.TypeReadiness, probes...)
|
||||
}
|
||||
|
||||
// IsCanceled returns true if the internal errgroup has been canceled
|
||||
@ -360,24 +363,51 @@ func (s *Server) addClosers(v ...interface{}) {
|
||||
s.syncClosers = append(s.syncClosers, v...)
|
||||
}
|
||||
|
||||
func (s *Server) probes() map[HealthzType][]interface{} {
|
||||
func (s *Server) readmers() []interfaces.Readmer {
|
||||
s.syncReadmersLock.RLock()
|
||||
defer s.syncReadmersLock.RUnlock()
|
||||
return s.syncReadmers
|
||||
}
|
||||
|
||||
func (s *Server) addReadmers(v ...interfaces.Readmer) {
|
||||
s.syncReadmersLock.Lock()
|
||||
defer s.syncReadmersLock.Unlock()
|
||||
s.syncReadmers = append(s.syncReadmers, v...)
|
||||
}
|
||||
|
||||
func (s *Server) probes() map[healthz.Type][]interface{} {
|
||||
s.syncProbesLock.RLock()
|
||||
defer s.syncProbesLock.RUnlock()
|
||||
return s.syncProbes
|
||||
}
|
||||
|
||||
func (s *Server) addProbes(typ HealthzType, v ...interface{}) {
|
||||
func (s *Server) addProbes(typ healthz.Type, v ...interface{}) {
|
||||
s.syncProbesLock.Lock()
|
||||
defer s.syncProbesLock.Unlock()
|
||||
s.syncProbes[typ] = append(s.syncProbes[typ], v...)
|
||||
}
|
||||
|
||||
// Readme returns the self-documenting string
|
||||
func (s *Server) Readme() string {
|
||||
md := &markdown.Markdown{}
|
||||
|
||||
md.Println(s.readmeServices())
|
||||
md.Println(s.readmeHealthz())
|
||||
md.Print(s.readmeCloser())
|
||||
|
||||
return md.String()
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// ~ Private methods
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
// startService starts the given services
|
||||
func (s *Server) startService(services ...Service) {
|
||||
for _, service := range services {
|
||||
service := service
|
||||
for _, value := range services {
|
||||
value := value
|
||||
s.g.Go(func() error {
|
||||
if err := service.Start(s.ctx); errors.Is(err, http.ErrServerClosed) {
|
||||
if err := value.Start(s.ctx); errors.Is(err, http.ErrServerClosed) {
|
||||
log.WithError(s.l, err).Debug("server has closed")
|
||||
} else if err != nil {
|
||||
log.WithError(s.l, err).Error("failed to start service")
|
||||
@ -387,3 +417,137 @@ func (s *Server) startService(services ...Service) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) readmeCloser() string {
|
||||
md := &markdown.Markdown{}
|
||||
closers := s.closers()
|
||||
rows := make([][]string, 0, len(closers))
|
||||
for _, value := range closers {
|
||||
t := reflect.TypeOf(value)
|
||||
var closer string
|
||||
switch value.(type) {
|
||||
case interfaces.Closer:
|
||||
closer = "Closer"
|
||||
case interfaces.ErrorCloser:
|
||||
closer = "ErrorCloser"
|
||||
case interfaces.CloserWithContext:
|
||||
closer = "CloserWithContext"
|
||||
case interfaces.ErrorCloserWithContext:
|
||||
closer = "ErrorCloserWithContext"
|
||||
case interfaces.Shutdowner:
|
||||
closer = "Shutdowner"
|
||||
case interfaces.ErrorShutdowner:
|
||||
closer = "ErrorShutdowner"
|
||||
case interfaces.ShutdownerWithContext:
|
||||
closer = "ShutdownerWithContext"
|
||||
case interfaces.ErrorShutdownerWithContext:
|
||||
closer = "ErrorShutdownerWithContext"
|
||||
case interfaces.Stopper:
|
||||
closer = "Stopper"
|
||||
case interfaces.ErrorStopper:
|
||||
closer = "ErrorStopper"
|
||||
case interfaces.StopperWithContext:
|
||||
closer = "StopperWithContext"
|
||||
case interfaces.ErrorStopperWithContext:
|
||||
closer = "ErrorStopperWithContext"
|
||||
case interfaces.Unsubscriber:
|
||||
closer = "Unsubscriber"
|
||||
case interfaces.ErrorUnsubscriber:
|
||||
closer = "ErrorUnsubscriber"
|
||||
case interfaces.UnsubscriberWithContext:
|
||||
closer = "UnsubscriberWithContext"
|
||||
case interfaces.ErrorUnsubscriberWithContext:
|
||||
closer = "ErrorUnsubscriberWithContext"
|
||||
}
|
||||
rows = append(rows, []string{
|
||||
markdown.Code(markdown.Name(value)),
|
||||
markdown.Code(t.String()),
|
||||
markdown.Code(closer),
|
||||
markdown.String(value),
|
||||
})
|
||||
}
|
||||
if len(rows) > 0 {
|
||||
md.Println("### Closers")
|
||||
md.Println("")
|
||||
md.Println("List of all registered closers that are being called during graceful shutdown.")
|
||||
md.Println("")
|
||||
md.Table([]string{"Name", "Type", "Closer", "Description"}, rows)
|
||||
md.Println("")
|
||||
}
|
||||
|
||||
return md.String()
|
||||
}
|
||||
|
||||
func (s *Server) readmeHealthz() string {
|
||||
var rows [][]string
|
||||
md := &markdown.Markdown{}
|
||||
|
||||
for k, probes := range s.probes() {
|
||||
for _, probe := range probes {
|
||||
t := reflect.TypeOf(probe)
|
||||
rows = append(rows, []string{
|
||||
markdown.Code(markdown.Name(probe)),
|
||||
markdown.Code(k.String()),
|
||||
markdown.Code(t.String()),
|
||||
markdown.String(probe),
|
||||
})
|
||||
}
|
||||
}
|
||||
if len(rows) > 0 {
|
||||
md.Println("### Health probes")
|
||||
md.Println("")
|
||||
md.Println("List of all registered healthz probes that are being called during startup and runtime.")
|
||||
md.Println("")
|
||||
md.Table([]string{"Name", "Probe", "Type", "Description"}, rows)
|
||||
}
|
||||
|
||||
return md.String()
|
||||
}
|
||||
|
||||
func (s *Server) readmeServices() string {
|
||||
md := &markdown.Markdown{}
|
||||
|
||||
{
|
||||
var rows [][]string
|
||||
for _, value := range s.initServices {
|
||||
if v, ok := value.(*service.HTTP); ok {
|
||||
t := reflect.TypeOf(v)
|
||||
rows = append(rows, []string{
|
||||
markdown.Code(v.Name()),
|
||||
markdown.Code(t.String()),
|
||||
markdown.String(v),
|
||||
})
|
||||
}
|
||||
}
|
||||
if len(rows) > 0 {
|
||||
md.Println("### Init Services")
|
||||
md.Println("")
|
||||
md.Println("List of all registered init services that are being immediately started.")
|
||||
md.Println("")
|
||||
md.Table([]string{"Name", "Type", "Address"}, rows)
|
||||
}
|
||||
}
|
||||
|
||||
md.Println("")
|
||||
|
||||
{
|
||||
var rows [][]string
|
||||
for _, value := range s.services {
|
||||
t := reflect.TypeOf(value)
|
||||
rows = append(rows, []string{
|
||||
markdown.Code(value.Name()),
|
||||
markdown.Code(t.String()),
|
||||
markdown.String(value),
|
||||
})
|
||||
}
|
||||
if len(rows) > 0 {
|
||||
md.Println("### Runtime Services")
|
||||
md.Println("")
|
||||
md.Println("List of all registered services that are being started.")
|
||||
md.Println("")
|
||||
md.Table([]string{"Name", "Type", "Description"}, rows)
|
||||
}
|
||||
}
|
||||
|
||||
return md.String()
|
||||
}
|
||||
|
||||
@ -10,6 +10,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/foomo/keel/service"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"go.uber.org/zap"
|
||||
@ -74,7 +75,7 @@ func (s *KeelTestSuite) TearDownSuite() {}
|
||||
|
||||
func (s *KeelTestSuite) TestServiceHTTP() {
|
||||
s.svr.AddServices(
|
||||
keel.NewServiceHTTP(s.l, "test", ":55000", s.mux),
|
||||
service.NewHTTP(s.l, "test", ":55000", s.mux),
|
||||
)
|
||||
|
||||
s.runServer()
|
||||
@ -86,8 +87,8 @@ func (s *KeelTestSuite) TestServiceHTTP() {
|
||||
|
||||
func (s *KeelTestSuite) TestServiceHTTPZap() {
|
||||
s.svr.AddServices(
|
||||
keel.NewServiceHTTPZap(s.l, "zap", ":9100", "/log"),
|
||||
keel.NewServiceHTTP(s.l, "test", ":55000", s.mux),
|
||||
service.NewHTTPZap(s.l, "zap", ":9100", "/log"),
|
||||
service.NewHTTP(s.l, "test", ":55000", s.mux),
|
||||
)
|
||||
|
||||
s.runServer()
|
||||
@ -141,7 +142,7 @@ func (s *KeelTestSuite) TestServiceHTTPZap() {
|
||||
|
||||
func (s *KeelTestSuite) TestGraceful() {
|
||||
s.svr.AddServices(
|
||||
keel.NewServiceHTTP(s.l, "test", ":55000", s.mux),
|
||||
service.NewHTTP(s.l, "test", ":55000", s.mux),
|
||||
)
|
||||
|
||||
s.runServer()
|
||||
|
||||
10
service/errors.go
Normal file
10
service/errors.go
Normal file
@ -0,0 +1,10 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrServiceNotRunning = errors.New("service not running")
|
||||
ErrServiceShutdown = errors.New("service shutdown")
|
||||
)
|
||||
110
service/goroutine.go
Normal file
110
service/goroutine.go
Normal file
@ -0,0 +1,110 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"github.com/foomo/keel/log"
|
||||
)
|
||||
|
||||
// GoRoutine struct
|
||||
type (
|
||||
GoRoutine struct {
|
||||
running atomic.Bool
|
||||
handler GoRoutineFn
|
||||
cancel context.CancelCauseFunc
|
||||
cancelLock sync.Mutex
|
||||
parallel int
|
||||
name string
|
||||
wg errgroup.Group
|
||||
l *zap.Logger
|
||||
}
|
||||
GoRoutineOption func(*GoRoutine)
|
||||
GoRoutineFn func(ctx context.Context, l *zap.Logger) error
|
||||
)
|
||||
|
||||
func NewGoRoutine(l *zap.Logger, name string, handler GoRoutineFn, opts ...GoRoutineOption) *GoRoutine {
|
||||
if l == nil {
|
||||
l = log.Logger()
|
||||
}
|
||||
// enrich the log
|
||||
l = log.WithAttributes(l,
|
||||
log.KeelServiceTypeKey.String("goroutine"),
|
||||
log.KeelServiceNameKey.String(name),
|
||||
)
|
||||
|
||||
inst := &GoRoutine{
|
||||
handler: handler,
|
||||
name: name,
|
||||
parallel: 1,
|
||||
l: l,
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(inst)
|
||||
}
|
||||
|
||||
return inst
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// ~ Options
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
func GoRoutineWithPralllel(v int) GoRoutineOption {
|
||||
return func(o *GoRoutine) {
|
||||
o.parallel = v
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// ~ Public methods
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
func (s *GoRoutine) Name() string {
|
||||
return s.name
|
||||
}
|
||||
|
||||
func (s *GoRoutine) Healthz() error {
|
||||
if !s.running.Load() {
|
||||
return ErrServiceNotRunning
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *GoRoutine) String() string {
|
||||
return fmt.Sprintf("parallel: `%d`", s.parallel)
|
||||
}
|
||||
|
||||
func (s *GoRoutine) Start(ctx context.Context) error {
|
||||
s.l.Info("starting keel service")
|
||||
ctx, cancel := context.WithCancelCause(ctx)
|
||||
s.cancelLock.Lock()
|
||||
s.cancel = cancel
|
||||
s.cancelLock.Unlock()
|
||||
for i := 0; i < s.parallel; i++ {
|
||||
i := i
|
||||
l := log.WithAttributes(s.l, log.KeelServiceInstKey.Int(i))
|
||||
s.wg.Go(func() error {
|
||||
return s.handler(ctx, l)
|
||||
})
|
||||
}
|
||||
s.running.Store(true)
|
||||
defer func() {
|
||||
s.running.Store(false)
|
||||
}()
|
||||
return s.wg.Wait()
|
||||
}
|
||||
|
||||
func (s *GoRoutine) Close(ctx context.Context) error {
|
||||
s.l.Info("stopping keel service")
|
||||
s.cancelLock.Lock()
|
||||
s.cancel(ErrServiceShutdown)
|
||||
s.cancelLock.Unlock()
|
||||
return s.wg.Wait()
|
||||
}
|
||||
50
service/goroutine_test.go
Normal file
50
service/goroutine_test.go
Normal file
@ -0,0 +1,50 @@
|
||||
package service_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/foomo/keel"
|
||||
"github.com/foomo/keel/service"
|
||||
"github.com/pkg/errors"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func ExampleNewGoRoutine() {
|
||||
var once sync.Once
|
||||
|
||||
svr := keel.NewServer(
|
||||
keel.WithLogger(zap.NewExample()),
|
||||
)
|
||||
|
||||
svr.AddService(
|
||||
service.NewGoRoutine(svr.Logger(), "demo", func(ctx context.Context, l *zap.Logger) error {
|
||||
for {
|
||||
// handle graceful shutdowns
|
||||
if err := ctx.Err(); errors.Is(context.Cause(ctx), service.ErrServiceShutdown) {
|
||||
l.Info("context has been canceled du to graceful shutdow")
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return errors.Wrap(err, "unexpected context error")
|
||||
}
|
||||
|
||||
l.Info("ping")
|
||||
time.Sleep(time.Second)
|
||||
once.Do(shutdown)
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
svr.Run()
|
||||
|
||||
// Output:
|
||||
// {"level":"info","msg":"starting keel server"}
|
||||
// {"level":"info","msg":"starting keel service","keel_service_type":"goroutine","keel_service_name":"demo"}
|
||||
// {"level":"info","msg":"ping","keel_service_type":"goroutine","keel_service_name":"demo","keel_service_inst":0}
|
||||
// {"level":"info","msg":"ping","keel_service_type":"goroutine","keel_service_name":"demo","keel_service_inst":0}
|
||||
// {"level":"debug","msg":"keel graceful shutdown"}
|
||||
// {"level":"info","msg":"stopping keel service","keel_service_type":"goroutine","keel_service_name":"demo"}
|
||||
// {"level":"info","msg":"context has been canceled du to graceful shutdow","keel_service_type":"goroutine","keel_service_name":"demo","keel_service_inst":0}
|
||||
// {"level":"info","msg":"keel server stopped"}
|
||||
}
|
||||
42
service/helper_test.go
Normal file
42
service/helper_test.go
Normal file
@ -0,0 +1,42 @@
|
||||
package service_test
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
// shutdown example after the given time
|
||||
func shutdownAfter(duration time.Duration) {
|
||||
go func() {
|
||||
time.Sleep(duration)
|
||||
shutdown()
|
||||
}()
|
||||
}
|
||||
|
||||
func shutdown() {
|
||||
if err := syscall.Kill(syscall.Getpid(), syscall.SIGINT); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func waitFor(addr string) {
|
||||
if _, err := net.DialTimeout("tcp", addr, 10*time.Second); err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func httpGet(url string) string {
|
||||
resp, err := http.Get(url) //nolint:all
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
b, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
@ -1,7 +1,8 @@
|
||||
package keel
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
@ -14,22 +15,25 @@ import (
|
||||
"github.com/foomo/keel/net/http/middleware"
|
||||
)
|
||||
|
||||
// ServiceHTTP struct
|
||||
type ServiceHTTP struct {
|
||||
// HTTP struct
|
||||
type HTTP struct {
|
||||
running atomic.Bool
|
||||
server *http.Server
|
||||
name string
|
||||
l *zap.Logger
|
||||
}
|
||||
|
||||
func NewServiceHTTP(l *zap.Logger, name, addr string, handler http.Handler, middlewares ...middleware.Middleware) *ServiceHTTP {
|
||||
func NewHTTP(l *zap.Logger, name, addr string, handler http.Handler, middlewares ...middleware.Middleware) *HTTP {
|
||||
if l == nil {
|
||||
l = log.Logger()
|
||||
}
|
||||
// enrich the log
|
||||
l = log.WithHTTPServerName(l, name)
|
||||
l = log.WithAttributes(l,
|
||||
log.KeelServiceTypeKey.String("http"),
|
||||
log.KeelServiceNameKey.String(name),
|
||||
)
|
||||
|
||||
return &ServiceHTTP{
|
||||
return &HTTP{
|
||||
server: &http.Server{
|
||||
Addr: addr,
|
||||
ErrorLog: zap.NewStdLog(l),
|
||||
@ -40,18 +44,22 @@ func NewServiceHTTP(l *zap.Logger, name, addr string, handler http.Handler, midd
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ServiceHTTP) Name() string {
|
||||
func (s *HTTP) Name() string {
|
||||
return s.name
|
||||
}
|
||||
|
||||
func (s *ServiceHTTP) Healthz() error {
|
||||
func (s *HTTP) Healthz() error {
|
||||
if !s.running.Load() {
|
||||
return ErrServiceNotRunning
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ServiceHTTP) Start(ctx context.Context) error {
|
||||
func (s *HTTP) String() string {
|
||||
return fmt.Sprintf("`%T` on `%s`", s.server.Handler, s.server.Addr)
|
||||
}
|
||||
|
||||
func (s *HTTP) Start(ctx context.Context) error {
|
||||
var fields []zap.Field
|
||||
if value := strings.Split(s.server.Addr, ":"); len(value) == 2 {
|
||||
ip, port := value[0], value[1]
|
||||
@ -60,20 +68,24 @@ func (s *ServiceHTTP) Start(ctx context.Context) error {
|
||||
}
|
||||
fields = append(fields, log.FNetHostIP(ip), log.FNetHostPort(port))
|
||||
}
|
||||
s.l.Info("starting http service", fields...)
|
||||
s.l.Info("starting keel service", fields...)
|
||||
s.server.BaseContext = func(_ net.Listener) context.Context { return ctx }
|
||||
s.server.RegisterOnShutdown(func() {
|
||||
s.running.Store(false)
|
||||
})
|
||||
s.running.Store(true)
|
||||
if err := s.server.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
|
||||
log.WithError(s.l, err).Error("service error")
|
||||
return err
|
||||
if err := s.server.ListenAndServe(); errors.Is(err, http.ErrServerClosed) {
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return errors.Wrap(err, "failed to start service")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ServiceHTTP) Close(ctx context.Context) error {
|
||||
s.l.Info("stopping http service")
|
||||
return s.server.Shutdown(ctx)
|
||||
func (s *HTTP) Close(ctx context.Context) error {
|
||||
s.l.Info("stopping keel service")
|
||||
if err := s.server.Shutdown(ctx); err != nil {
|
||||
return errors.Wrap(err, "failed to stop service")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
40
service/http_test.go
Normal file
40
service/http_test.go
Normal file
@ -0,0 +1,40 @@
|
||||
package service_test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/foomo/keel"
|
||||
"github.com/foomo/keel/service"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func ExampleNewHTTP() {
|
||||
svr := keel.NewServer(
|
||||
keel.WithLogger(zap.NewExample()),
|
||||
)
|
||||
|
||||
l := svr.Logger()
|
||||
|
||||
svr.AddService(
|
||||
service.NewHTTP(l, "demo", "localhost:8080", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write([]byte("OK"))
|
||||
})),
|
||||
)
|
||||
|
||||
go func() {
|
||||
waitFor("localhost:8080")
|
||||
l.Info(httpGet("http://localhost:8080"))
|
||||
shutdown()
|
||||
}()
|
||||
|
||||
svr.Run()
|
||||
|
||||
// Output:
|
||||
// {"level":"info","msg":"starting keel server"}
|
||||
// {"level":"info","msg":"starting keel service","keel_service_type":"http","keel_service_name":"demo","net_host_ip":"localhost","net_host_port":"8080"}
|
||||
// {"level":"info","msg":"OK"}
|
||||
// {"level":"debug","msg":"keel graceful shutdown"}
|
||||
// {"level":"info","msg":"stopping keel service","keel_service_type":"http","keel_service_name":"demo"}
|
||||
// {"level":"info","msg":"keel server stopped"}
|
||||
}
|
||||
@ -1,19 +1,21 @@
|
||||
package keel
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/foomo/keel/healthz"
|
||||
"github.com/foomo/keel/interfaces"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/foomo/keel/log"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultServiceHTTPHealthzName = "healthz"
|
||||
DefaultServiceHTTPHealthzAddr = ":9400"
|
||||
DefaultServiceHTTPHealthzPath = "/healthz"
|
||||
DefaultHTTPHealthzName = "healthz"
|
||||
DefaultHTTPHealthzAddr = ":9400"
|
||||
DefaultHTTPHealthzPath = "/healthz"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -24,7 +26,7 @@ var (
|
||||
ErrStartupProbeFailed = errors.New("startup probe failed")
|
||||
)
|
||||
|
||||
func NewServiceHTTPHealthz(l *zap.Logger, name, addr, path string, probes map[HealthzType][]interface{}) *ServiceHTTP {
|
||||
func NewHealthz(l *zap.Logger, name, addr, path string, probes map[healthz.Type][]interface{}) *HTTP {
|
||||
handler := http.NewServeMux()
|
||||
|
||||
unavailable := func(l *zap.Logger, w http.ResponseWriter, r *http.Request, err error) {
|
||||
@ -36,17 +38,17 @@ func NewServiceHTTPHealthz(l *zap.Logger, name, addr, path string, probes map[He
|
||||
|
||||
call := func(ctx context.Context, probe interface{}) (bool, error) {
|
||||
switch h := probe.(type) {
|
||||
case BoolHealthzer:
|
||||
case healthz.BoolHealthzer:
|
||||
return h.Healthz(), nil
|
||||
case BoolHealthzerWithContext:
|
||||
case healthz.BoolHealthzerWithContext:
|
||||
return h.Healthz(ctx), nil
|
||||
case ErrorHealthzer:
|
||||
case healthz.ErrorHealthzer:
|
||||
return true, h.Healthz()
|
||||
case ErrorHealthzWithContext:
|
||||
case healthz.ErrorHealthzWithContext:
|
||||
return true, h.Healthz(ctx)
|
||||
case ErrorPinger:
|
||||
case interfaces.ErrorPinger:
|
||||
return true, h.Ping()
|
||||
case ErrorPingerWithContext:
|
||||
case interfaces.ErrorPingerWithContext:
|
||||
return true, h.Ping(ctx)
|
||||
default:
|
||||
return false, ErrUnhandledHealthzProbe
|
||||
@ -55,7 +57,7 @@ func NewServiceHTTPHealthz(l *zap.Logger, name, addr, path string, probes map[He
|
||||
|
||||
handler.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
|
||||
for typ, values := range probes {
|
||||
if typ == HealthzTypeStartup {
|
||||
if typ == healthz.TypeStartup {
|
||||
continue
|
||||
}
|
||||
for _, p := range values {
|
||||
@ -72,12 +74,12 @@ func NewServiceHTTPHealthz(l *zap.Logger, name, addr, path string, probes map[He
|
||||
_, _ = w.Write([]byte("OK"))
|
||||
})
|
||||
|
||||
handler.HandleFunc(path+"/"+HealthzTypeLiveness.String(), func(w http.ResponseWriter, r *http.Request) {
|
||||
handler.HandleFunc(path+"/"+healthz.TypeLiveness.String(), func(w http.ResponseWriter, r *http.Request) {
|
||||
var ps []interface{}
|
||||
if p, ok := probes[HealthzTypeAlways]; ok {
|
||||
if p, ok := probes[healthz.TypeAlways]; ok {
|
||||
ps = append(ps, p...)
|
||||
}
|
||||
if p, ok := probes[HealthzTypeLiveness]; ok {
|
||||
if p, ok := probes[healthz.TypeLiveness]; ok {
|
||||
ps = append(ps, p...)
|
||||
}
|
||||
for _, p := range ps {
|
||||
@ -93,12 +95,12 @@ func NewServiceHTTPHealthz(l *zap.Logger, name, addr, path string, probes map[He
|
||||
_, _ = w.Write([]byte("OK"))
|
||||
})
|
||||
|
||||
handler.HandleFunc(path+"/"+HealthzTypeReadiness.String(), func(w http.ResponseWriter, r *http.Request) {
|
||||
handler.HandleFunc(path+"/"+healthz.TypeReadiness.String(), func(w http.ResponseWriter, r *http.Request) {
|
||||
var ps []interface{}
|
||||
if p, ok := probes[HealthzTypeAlways]; ok {
|
||||
if p, ok := probes[healthz.TypeAlways]; ok {
|
||||
ps = append(ps, p...)
|
||||
}
|
||||
if p, ok := probes[HealthzTypeReadiness]; ok {
|
||||
if p, ok := probes[healthz.TypeReadiness]; ok {
|
||||
ps = append(ps, p...)
|
||||
}
|
||||
for _, p := range ps {
|
||||
@ -114,12 +116,12 @@ func NewServiceHTTPHealthz(l *zap.Logger, name, addr, path string, probes map[He
|
||||
_, _ = w.Write([]byte("OK"))
|
||||
})
|
||||
|
||||
handler.HandleFunc(path+"/"+HealthzTypeStartup.String(), func(w http.ResponseWriter, r *http.Request) {
|
||||
handler.HandleFunc(path+"/"+healthz.TypeStartup.String(), func(w http.ResponseWriter, r *http.Request) {
|
||||
var ps []interface{}
|
||||
if p, ok := probes[HealthzTypeAlways]; ok {
|
||||
if p, ok := probes[healthz.TypeAlways]; ok {
|
||||
ps = append(ps, p...)
|
||||
}
|
||||
if p, ok := probes[HealthzTypeStartup]; ok {
|
||||
if p, ok := probes[healthz.TypeStartup]; ok {
|
||||
ps = append(ps, p...)
|
||||
}
|
||||
for _, p := range ps {
|
||||
@ -134,15 +136,15 @@ func NewServiceHTTPHealthz(l *zap.Logger, name, addr, path string, probes map[He
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write([]byte("OK"))
|
||||
})
|
||||
return NewServiceHTTP(l, name, addr, handler)
|
||||
return NewHTTP(l, name, addr, handler)
|
||||
}
|
||||
|
||||
func NewDefaultServiceHTTPProbes(probes map[HealthzType][]interface{}) *ServiceHTTP {
|
||||
return NewServiceHTTPHealthz(
|
||||
log.Logger(),
|
||||
DefaultServiceHTTPHealthzName,
|
||||
DefaultServiceHTTPHealthzAddr,
|
||||
DefaultServiceHTTPHealthzPath,
|
||||
func NewDefaultHTTPProbes(l *zap.Logger, probes map[healthz.Type][]interface{}) *HTTP {
|
||||
return NewHealthz(
|
||||
l,
|
||||
DefaultHTTPHealthzName,
|
||||
DefaultHTTPHealthzAddr,
|
||||
DefaultHTTPHealthzPath,
|
||||
probes,
|
||||
)
|
||||
}
|
||||
@ -1,21 +1,20 @@
|
||||
//go:build !pprof
|
||||
|
||||
package keel
|
||||
package service
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/foomo/keel/log"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultServiceHTTPPProfName = "pprof"
|
||||
DefaultServiceHTTPPProfAddr = "localhost:6060"
|
||||
DefaultServiceHTTPPProfPath = "/debug/pprof"
|
||||
DefaultHTTPPProfName = "pprof"
|
||||
DefaultHTTPPProfAddr = "localhost:6060"
|
||||
DefaultHTTPPProfPath = "/debug/pprof"
|
||||
)
|
||||
|
||||
func NewServiceHTTPPProf(l *zap.Logger, name, addr, path string) *ServiceHTTP {
|
||||
func NewHTTPPProf(l *zap.Logger, name, addr, path string) *HTTP {
|
||||
route := func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNotImplemented)
|
||||
_, _ = w.Write([]byte("To enable pprof, you need to build your binary with the `-tags=pprof` flag"))
|
||||
@ -26,14 +25,14 @@ func NewServiceHTTPPProf(l *zap.Logger, name, addr, path string) *ServiceHTTP {
|
||||
handler.HandleFunc(path+"/profile", route)
|
||||
handler.HandleFunc(path+"/symbol", route)
|
||||
handler.HandleFunc(path+"/trace", route)
|
||||
return NewServiceHTTP(l, name, addr, handler)
|
||||
return NewHTTP(l, name, addr, handler)
|
||||
}
|
||||
|
||||
func NewDefaultServiceHTTPPProf() *ServiceHTTP {
|
||||
return NewServiceHTTPPProf(
|
||||
log.Logger(),
|
||||
DefaultServiceHTTPPProfName,
|
||||
DefaultServiceHTTPPProfAddr,
|
||||
DefaultServiceHTTPPProfPath,
|
||||
func NewDefaultHTTPPProf(l *zap.Logger) *HTTP {
|
||||
return NewHTTPPProf(
|
||||
l,
|
||||
DefaultHTTPPProfName,
|
||||
DefaultHTTPPProfAddr,
|
||||
DefaultHTTPPProfPath,
|
||||
)
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
//go:build pprof
|
||||
// +build pprof
|
||||
|
||||
package keel
|
||||
package service
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
@ -12,12 +12,12 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultServiceHTTPPProfName = "pprof"
|
||||
DefaultServiceHTTPPProfAddr = "localhost:6060"
|
||||
DefaultServiceHTTPPProfPath = "/debug/pprof"
|
||||
DefaultHTTPPProfName = "pprof"
|
||||
DefaultHTTPPProfAddr = "localhost:6060"
|
||||
DefaultHTTPPProfPath = "/debug/pprof"
|
||||
)
|
||||
|
||||
func NewServiceHTTPPProf(l *zap.Logger, name, addr, path string) *ServiceHTTP {
|
||||
func NewHTTPPProf(l *zap.Logger, name, addr, path string) *ServiceHTTP {
|
||||
handler := http.NewServeMux()
|
||||
handler.HandleFunc(path+"/", pprof.Index)
|
||||
handler.HandleFunc(path+"/cmdline", pprof.Cmdline)
|
||||
@ -27,11 +27,11 @@ func NewServiceHTTPPProf(l *zap.Logger, name, addr, path string) *ServiceHTTP {
|
||||
return NewServiceHTTP(l, name, addr, handler)
|
||||
}
|
||||
|
||||
func NewDefaultServiceHTTPPProf() *ServiceHTTP {
|
||||
return NewServiceHTTPPProf(
|
||||
func NewDefaultHTTPPProf() *ServiceHTTP {
|
||||
return NewHTTPPProf(
|
||||
log.Logger(),
|
||||
DefaultServiceHTTPPProfName,
|
||||
DefaultServiceHTTPPProfAddr,
|
||||
DefaultServiceHTTPPProfPath,
|
||||
DefaultHTTPPProfName,
|
||||
DefaultHTTPPProfAddr,
|
||||
DefaultHTTPPProfPath,
|
||||
)
|
||||
}
|
||||
35
service/httpprometheus.go
Normal file
35
service/httpprometheus.go
Normal file
@ -0,0 +1,35 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultHTTPPrometheusName = "prometheus"
|
||||
DefaultHTTPPrometheusAddr = ":9200"
|
||||
DefaultHTTPPrometheusPath = "/metrics"
|
||||
)
|
||||
|
||||
func NewHTTPPrometheus(l *zap.Logger, name, addr, path string) *HTTP {
|
||||
handler := http.NewServeMux()
|
||||
handler.Handle(path, promhttp.HandlerFor(
|
||||
prometheus.DefaultGatherer,
|
||||
promhttp.HandlerOpts{
|
||||
EnableOpenMetrics: true,
|
||||
},
|
||||
))
|
||||
return NewHTTP(l, name, addr, handler)
|
||||
}
|
||||
|
||||
func NewDefaultHTTPPrometheus(l *zap.Logger) *HTTP {
|
||||
return NewHTTPPrometheus(
|
||||
l,
|
||||
DefaultHTTPPrometheusName,
|
||||
DefaultHTTPPrometheusAddr,
|
||||
DefaultHTTPPrometheusPath,
|
||||
)
|
||||
}
|
||||
44
service/httpreadme.go
Normal file
44
service/httpreadme.go
Normal file
@ -0,0 +1,44 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/foomo/keel/interfaces"
|
||||
"github.com/foomo/keel/markdown"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultHTTPReadmeName = "readme"
|
||||
DefaultHTTPReadmeAddr = "localhost:9001"
|
||||
DefaultHTTPReadmePath = "/readme"
|
||||
)
|
||||
|
||||
func NewHTTPReadme(l *zap.Logger, name, addr, path string, readmers func() []interfaces.Readmer) *HTTP {
|
||||
handler := http.NewServeMux()
|
||||
handler.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
w.Header().Add("Content-Type", "text/markdown")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
md := &markdown.Markdown{}
|
||||
for _, readmer := range readmers() {
|
||||
md.Print(readmer.Readme())
|
||||
}
|
||||
_, _ = w.Write([]byte(md.String()))
|
||||
default:
|
||||
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
|
||||
}
|
||||
})
|
||||
return NewHTTP(l, name, addr, handler)
|
||||
}
|
||||
|
||||
func NewDefaultHTTPReadme(l *zap.Logger, readmers func() []interfaces.Readmer) *HTTP {
|
||||
return NewHTTPReadme(
|
||||
l,
|
||||
DefaultHTTPReadmeName,
|
||||
DefaultHTTPReadmeAddr,
|
||||
DefaultHTTPReadmePath,
|
||||
readmers,
|
||||
)
|
||||
}
|
||||
191
service/httpreadme_test.go
Normal file
191
service/httpreadme_test.go
Normal file
@ -0,0 +1,191 @@
|
||||
package service_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/foomo/keel"
|
||||
"github.com/foomo/keel/config"
|
||||
"github.com/foomo/keel/env"
|
||||
"github.com/foomo/keel/service"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||
"go.opentelemetry.io/otel/metric/instrument"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func ExampleNewHTTPReadme() {
|
||||
// define vars so it does not panic
|
||||
_ = os.Setenv("EXAMPLE_REQUIRED_BOOL", "true")
|
||||
_ = os.Setenv("EXAMPLE_REQUIRED_STRING", "foo")
|
||||
|
||||
svr := keel.NewServer(
|
||||
keel.WithLogger(zap.NewNop()),
|
||||
keel.WithPrometheusMeter(true),
|
||||
keel.WithHTTPReadmeService(true),
|
||||
)
|
||||
|
||||
// access some env vars
|
||||
_ = env.Get("EXAMPLE_STRING", "demo")
|
||||
_ = env.GetBool("EXAMPLE_BOOL", false)
|
||||
_ = env.MustGet("EXAMPLE_REQUIRED_STRING")
|
||||
_ = env.MustGetBool("EXAMPLE_REQUIRED_BOOL")
|
||||
|
||||
l := svr.Logger()
|
||||
|
||||
c := svr.Config()
|
||||
// config with fallback
|
||||
_ = config.GetBool(c, "example.bool", false)
|
||||
_ = config.GetString(c, "example.string", "fallback")
|
||||
// required configs
|
||||
_ = config.MustGetBool(c, "example.required.bool")
|
||||
_ = config.MustGetString(c, "example.required.string")
|
||||
|
||||
m := svr.Meter()
|
||||
|
||||
// add metrics
|
||||
fooBarCounter := promauto.NewCounter(prometheus.CounterOpts{
|
||||
Name: "foo_bar_total",
|
||||
Help: "Foo bar metrics",
|
||||
})
|
||||
fooBazCounter, _ := m.SyncInt64().Counter("foo_baz_total", instrument.WithDescription("Foo baz metrics"))
|
||||
|
||||
fooBarCounter.Add(1)
|
||||
fooBazCounter.Add(svr.Context(), 1)
|
||||
|
||||
// add http service
|
||||
svr.AddService(service.NewHTTP(l, "demp-http", "localhost:8080", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write([]byte("OK"))
|
||||
})))
|
||||
|
||||
// add go routine service
|
||||
svr.AddService(service.NewGoRoutine(l, "demo-goroutine", func(ctx context.Context, l *zap.Logger) error {
|
||||
return nil
|
||||
}))
|
||||
|
||||
go func() {
|
||||
waitFor("localhost:9001")
|
||||
fmt.Print(httpGet("http://localhost:9001/readme"))
|
||||
shutdown()
|
||||
}()
|
||||
|
||||
svr.Run()
|
||||
|
||||
// Output:
|
||||
// ### Env
|
||||
//
|
||||
// List of all accessed environment variables.
|
||||
//
|
||||
// | Key | Type | Required | Default |
|
||||
// | ------------------------- | -------- | -------- | --------- |
|
||||
// | `EXAMPLE_BOOL` | `bool` | | |
|
||||
// | `EXAMPLE_REQUIRED_BOOL` | `bool` | | |
|
||||
// | `EXAMPLE_REQUIRED_BOOL` | `bool` | `true` | |
|
||||
// | `EXAMPLE_REQUIRED_STRING` | `string` | | |
|
||||
// | `EXAMPLE_REQUIRED_STRING` | `string` | `true` | |
|
||||
// | `EXAMPLE_STRING` | `string` | | `demo` |
|
||||
// | `LOG_DISABLE_CALLER` | `bool` | | |
|
||||
// | `LOG_DISABLE_STACKTRACE` | `bool` | | |
|
||||
// | `LOG_ENCODING` | `string` | | `json` |
|
||||
// | `LOG_LEVEL` | `string` | | `info` |
|
||||
// | `LOG_MODE` | `string` | | `prod` |
|
||||
// | `OTEL_ENABLED` | `bool` | | |
|
||||
// | `OTEL_SERVICE_NAME` | `string` | | `service` |
|
||||
//
|
||||
// ### Config
|
||||
//
|
||||
// List of all registered config variables with their defaults.
|
||||
//
|
||||
// | Key | Type | Required | Default |
|
||||
// | ------------------------- | -------- | -------- | ---------- |
|
||||
// | `example.bool` | `bool` | | `false` |
|
||||
// | `example.required.bool` | `bool` | `true` | |
|
||||
// | `example.required.string` | `string` | `true` | |
|
||||
// | `example.string` | `string` | | `fallback` |
|
||||
// | `otel.enabled` | `bool` | | `true` |
|
||||
// | `service.readme.enabled` | `bool` | | `true` |
|
||||
//
|
||||
// ### Init Services
|
||||
//
|
||||
// List of all registered init services that are being immediately started.
|
||||
//
|
||||
// | Name | Type | Address |
|
||||
// | -------- | --------------- | ------------------------------------ |
|
||||
// | `readme` | `*service.HTTP` | `*http.ServeMux` on `localhost:9001` |
|
||||
//
|
||||
// ### Runtime Services
|
||||
//
|
||||
// List of all registered services that are being started.
|
||||
//
|
||||
// | Name | Type | Description |
|
||||
// | ---------------- | -------------------- | -------------------------------------- |
|
||||
// | `demo-goroutine` | `*service.GoRoutine` | parallel: `1` |
|
||||
// | `demp-http` | `*service.HTTP` | `http.HandlerFunc` on `localhost:8080` |
|
||||
//
|
||||
// ### Health probes
|
||||
//
|
||||
// List of all registered healthz probes that are being called during startup and runtime.
|
||||
//
|
||||
// | Name | Probe | Type | Description |
|
||||
// | ---------------- | -------- | -------------------- | -------------------------------------- |
|
||||
// | | `always` | `*keel.Server` | |
|
||||
// | `demo-goroutine` | `always` | `*service.GoRoutine` | parallel: `1` |
|
||||
// | `demp-http` | `always` | `*service.HTTP` | `http.HandlerFunc` on `localhost:8080` |
|
||||
// | `readme` | `always` | `*service.HTTP` | `*http.ServeMux` on `localhost:9001` |
|
||||
//
|
||||
// ### Closers
|
||||
//
|
||||
// List of all registered closers that are being called during graceful shutdown.
|
||||
//
|
||||
// | Name | Type | Closer | Description |
|
||||
// | ---------------- | -------------------- | ------------------------ | -------------------------------------- |
|
||||
// | `demo-goroutine` | `*service.GoRoutine` | `ErrorCloserWithContext` | parallel: `1` |
|
||||
// | `demp-http` | `*service.HTTP` | `ErrorCloserWithContext` | `http.HandlerFunc` on `localhost:8080` |
|
||||
// | `readme` | `*service.HTTP` | `ErrorCloserWithContext` | `*http.ServeMux` on `localhost:9001` |
|
||||
//
|
||||
// ### Metrics
|
||||
//
|
||||
// List of all registered metrics than are being exposed.
|
||||
//
|
||||
// | Name | Type | Description |
|
||||
// | ---------------------------------- | ------- | ------------------------------------------------------------------ |
|
||||
// | `foo_bar_total` | COUNTER | Foo bar metrics |
|
||||
// | `foo_baz_total` | COUNTER | Foo baz metrics |
|
||||
// | `go_gc_duration_seconds` | SUMMARY | A summary of the pause duration of garbage collection cycles. |
|
||||
// | `go_goroutines` | GAUGE | Number of goroutines that currently exist. |
|
||||
// | `go_info` | GAUGE | Information about the Go environment. |
|
||||
// | `go_memstats_alloc_bytes_total` | COUNTER | Total number of bytes allocated, even if freed. |
|
||||
// | `go_memstats_alloc_bytes` | GAUGE | Number of bytes allocated and still in use. |
|
||||
// | `go_memstats_buck_hash_sys_bytes` | GAUGE | Number of bytes used by the profiling bucket hash table. |
|
||||
// | `go_memstats_frees_total` | COUNTER | Total number of frees. |
|
||||
// | `go_memstats_gc_sys_bytes` | GAUGE | Number of bytes used for garbage collection system metadata. |
|
||||
// | `go_memstats_heap_alloc_bytes` | GAUGE | Number of heap bytes allocated and still in use. |
|
||||
// | `go_memstats_heap_idle_bytes` | GAUGE | Number of heap bytes waiting to be used. |
|
||||
// | `go_memstats_heap_inuse_bytes` | GAUGE | Number of heap bytes that are in use. |
|
||||
// | `go_memstats_heap_objects` | GAUGE | Number of allocated objects. |
|
||||
// | `go_memstats_heap_released_bytes` | GAUGE | Number of heap bytes released to OS. |
|
||||
// | `go_memstats_heap_sys_bytes` | GAUGE | Number of heap bytes obtained from system. |
|
||||
// | `go_memstats_last_gc_time_seconds` | GAUGE | Number of seconds since 1970 of last garbage collection. |
|
||||
// | `go_memstats_lookups_total` | COUNTER | Total number of pointer lookups. |
|
||||
// | `go_memstats_mallocs_total` | COUNTER | Total number of mallocs. |
|
||||
// | `go_memstats_mcache_inuse_bytes` | GAUGE | Number of bytes in use by mcache structures. |
|
||||
// | `go_memstats_mcache_sys_bytes` | GAUGE | Number of bytes used for mcache structures obtained from system. |
|
||||
// | `go_memstats_mspan_inuse_bytes` | GAUGE | Number of bytes in use by mspan structures. |
|
||||
// | `go_memstats_mspan_sys_bytes` | GAUGE | Number of bytes used for mspan structures obtained from system. |
|
||||
// | `go_memstats_next_gc_bytes` | GAUGE | Number of heap bytes when next garbage collection will take place. |
|
||||
// | `go_memstats_other_sys_bytes` | GAUGE | Number of bytes used for other system allocations. |
|
||||
// | `go_memstats_stack_inuse_bytes` | GAUGE | Number of bytes in use by the stack allocator. |
|
||||
// | `go_memstats_stack_sys_bytes` | GAUGE | Number of bytes obtained from system for stack allocator. |
|
||||
// | `go_memstats_sys_bytes` | GAUGE | Number of bytes obtained from system. |
|
||||
// | `go_threads` | GAUGE | Number of OS threads created. |
|
||||
// | `process_cpu_seconds_total` | COUNTER | Total user and system CPU time spent in seconds. |
|
||||
// | `process_max_fds` | GAUGE | Maximum number of open file descriptors. |
|
||||
// | `process_open_fds` | GAUGE | Number of open file descriptors. |
|
||||
// | `process_resident_memory_bytes` | GAUGE | Resident memory size in bytes. |
|
||||
// | `process_start_time_seconds` | GAUGE | Start time of the process since unix epoch in seconds. |
|
||||
// | `process_virtual_memory_bytes` | GAUGE | Virtual memory size in bytes. |
|
||||
// | `process_virtual_memory_max_bytes` | GAUGE | Maximum amount of virtual memory available in bytes. |
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
package keel
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
@ -8,16 +8,15 @@ import (
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/foomo/keel/config"
|
||||
"github.com/foomo/keel/log"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultServiceHTTPViperName = "viper"
|
||||
DefaultServiceHTTPViperAddr = "localhost:9300"
|
||||
DefaultServiceHTTPViperPath = "/config"
|
||||
DefaultHTTPViperName = "viper"
|
||||
DefaultHTTPViperAddr = "localhost:9300"
|
||||
DefaultHTTPViperPath = "/config"
|
||||
)
|
||||
|
||||
func NewServiceHTTPViper(l *zap.Logger, c *viper.Viper, name, addr, path string) *ServiceHTTP {
|
||||
func NewHTTPViper(l *zap.Logger, c *viper.Viper, name, addr, path string) *HTTP {
|
||||
handler := http.NewServeMux()
|
||||
handler.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
|
||||
type payload struct {
|
||||
@ -44,15 +43,15 @@ func NewServiceHTTPViper(l *zap.Logger, c *viper.Viper, name, addr, path string)
|
||||
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
|
||||
}
|
||||
})
|
||||
return NewServiceHTTP(l, name, addr, handler)
|
||||
return NewHTTP(l, name, addr, handler)
|
||||
}
|
||||
|
||||
func NewDefaultServiceHTTPViper() *ServiceHTTP {
|
||||
return NewServiceHTTPViper(
|
||||
log.Logger(),
|
||||
func NewDefaultHTTPViper(l *zap.Logger) *HTTP {
|
||||
return NewHTTPViper(
|
||||
l,
|
||||
config.Config(),
|
||||
DefaultServiceHTTPViperName,
|
||||
DefaultServiceHTTPViperAddr,
|
||||
DefaultServiceHTTPViperPath,
|
||||
DefaultHTTPViperName,
|
||||
DefaultHTTPViperAddr,
|
||||
DefaultHTTPViperPath,
|
||||
)
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
package keel
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
@ -12,12 +12,12 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultServiceHTTPZapName = "zap"
|
||||
DefaultServiceHTTPZapAddr = "localhost:9100"
|
||||
DefaultServiceHTTPZapPath = "/log"
|
||||
DefaultHTTPZapName = "zap"
|
||||
DefaultHTTPZapAddr = "localhost:9100"
|
||||
DefaultHTTPZapPath = "/log"
|
||||
)
|
||||
|
||||
func NewServiceHTTPZap(l *zap.Logger, name, addr, path string) *ServiceHTTP {
|
||||
func NewHTTPZap(l *zap.Logger, name, addr, path string) *HTTP {
|
||||
handler := http.NewServeMux()
|
||||
handler.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
|
||||
type errorResponse struct {
|
||||
@ -91,14 +91,14 @@ func NewServiceHTTPZap(l *zap.Logger, name, addr, path string) *ServiceHTTP {
|
||||
})
|
||||
}
|
||||
})
|
||||
return NewServiceHTTP(l, name, addr, handler)
|
||||
return NewHTTP(l, name, addr, handler)
|
||||
}
|
||||
|
||||
func NewDefaultServiceHTTPZap() *ServiceHTTP {
|
||||
return NewServiceHTTPZap(
|
||||
log.Logger(),
|
||||
DefaultServiceHTTPZapName,
|
||||
DefaultServiceHTTPZapAddr,
|
||||
DefaultServiceHTTPZapPath,
|
||||
func NewDefaultHTTPZap(l *zap.Logger) *HTTP {
|
||||
return NewHTTPZap(
|
||||
l,
|
||||
DefaultHTTPZapName,
|
||||
DefaultHTTPZapAddr,
|
||||
DefaultHTTPZapPath,
|
||||
)
|
||||
}
|
||||
@ -1,37 +0,0 @@
|
||||
package keel
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/foomo/keel/log"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultServiceHTTPPrometheusName = "prometheus"
|
||||
DefaultServiceHTTPPrometheusAddr = ":9200"
|
||||
DefaultServiceHTTPPrometheusPath = "/metrics"
|
||||
)
|
||||
|
||||
func NewServiceHTTPPrometheus(l *zap.Logger, name, addr, path string) *ServiceHTTP {
|
||||
handler := http.NewServeMux()
|
||||
handler.Handle(path, promhttp.HandlerFor(
|
||||
prometheus.DefaultGatherer,
|
||||
promhttp.HandlerOpts{
|
||||
EnableOpenMetrics: true,
|
||||
},
|
||||
))
|
||||
return NewServiceHTTP(l, name, addr, handler)
|
||||
}
|
||||
|
||||
func NewDefaultServiceHTTPPrometheus() *ServiceHTTP {
|
||||
return NewServiceHTTPPrometheus(
|
||||
log.Logger(),
|
||||
DefaultServiceHTTPPrometheusName,
|
||||
DefaultServiceHTTPPrometheusAddr,
|
||||
DefaultServiceHTTPPrometheusPath,
|
||||
)
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user