mirror of
https://github.com/foomo/keel.git
synced 2025-10-16 12:35:34 +00:00
feat: add mongo and refactor readme
This commit is contained in:
parent
36a26d875d
commit
9983e267e2
29
closer.go
Normal file
29
closer.go
Normal file
@ -0,0 +1,29 @@
|
||||
package keel
|
||||
|
||||
import (
|
||||
"github.com/foomo/keel/interfaces"
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
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 variabled 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()
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -4,16 +4,6 @@ import (
|
||||
"context"
|
||||
)
|
||||
|
||||
type closer struct {
|
||||
handle func(context.Context) error
|
||||
}
|
||||
|
||||
func NewCloserFn(handle func(context.Context) error) closer {
|
||||
return closer{
|
||||
handle: handle,
|
||||
}
|
||||
}
|
||||
|
||||
// Closer interface
|
||||
type Closer interface {
|
||||
Close()
|
||||
|
||||
@ -1,6 +0,0 @@
|
||||
package interfaces
|
||||
|
||||
// Documenter interface
|
||||
type Documenter interface {
|
||||
Docs() string
|
||||
}
|
||||
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
|
||||
}
|
||||
6
interfaces/readmer.go
Normal file
6
interfaces/readmer.go
Normal file
@ -0,0 +1,6 @@
|
||||
package interfaces
|
||||
|
||||
// Readmer interface
|
||||
type Readmer interface {
|
||||
Readme() string
|
||||
}
|
||||
@ -13,6 +13,7 @@ type Markdown struct {
|
||||
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...))
|
||||
}
|
||||
|
||||
40
metrics/readme.go
Normal file
40
metrics/readme.go
Normal file
@ -0,0 +1,40 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"github.com/foomo/keel/markdown"
|
||||
"github.com/foomo/keel/telemetry/nonrecording"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
func Readme() string {
|
||||
md := markdown.Markdown{}
|
||||
values := nonrecording.Metrics()
|
||||
|
||||
gatherer, _ := prometheus.DefaultRegisterer.(*prometheus.Registry).Gather()
|
||||
for _, value := range gatherer {
|
||||
values = append(values, nonrecording.Metric{
|
||||
Name: value.GetName(),
|
||||
Type: value.GetType().String(),
|
||||
Help: value.GetHelp(),
|
||||
})
|
||||
}
|
||||
|
||||
rows := make([][]string, 0, len(values))
|
||||
for _, value := range values {
|
||||
rows = append(rows, []string{
|
||||
markdown.Code(value.Name),
|
||||
value.Type,
|
||||
value.Help,
|
||||
})
|
||||
}
|
||||
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()
|
||||
}
|
||||
@ -179,11 +179,11 @@ func WithHTTPHealthzService(enabled bool) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// WithHTTPDocsService option with default value
|
||||
func WithHTTPDocsService(enabled bool) Option {
|
||||
// WithHTTPReadmeService option with default value
|
||||
func WithHTTPReadmeService(enabled bool) Option {
|
||||
return func(inst *Server) {
|
||||
if config.GetBool(inst.Config(), "service.docs.enabled", enabled)() {
|
||||
svs := service.NewDefaultHTTPDocs(inst.Logger(), inst.documenter)
|
||||
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"
|
||||
@ -113,6 +114,11 @@ func CollectionWithIndexesCommitQuorumVotingMembers(v context.Context) Collectio
|
||||
// ~ Constructor
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
var (
|
||||
dbs = map[string][]string{}
|
||||
indices = map[string]map[string][]string{}
|
||||
)
|
||||
|
||||
func NewCollection(db *mongo.Database, name string, opts ...CollectionOption) (*Collection, error) {
|
||||
o := DefaultCollectionOptions()
|
||||
for _, opt := range opts {
|
||||
@ -120,11 +126,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.Name != nil {
|
||||
indices[db.Name()][name] = append(indices[db.Name()][name], *index.Options.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &Collection{
|
||||
|
||||
37
persistence/mongo/readme.go
Normal file
37
persistence/mongo/readme.go
Normal file
@ -0,0 +1,37 @@
|
||||
package keelmongo
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/foomo/keel/markdown"
|
||||
)
|
||||
|
||||
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()
|
||||
}
|
||||
349
server.go
349
server.go
@ -7,6 +7,7 @@ import (
|
||||
"os"
|
||||
"os/signal"
|
||||
"reflect"
|
||||
"slices"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
@ -15,11 +16,11 @@ import (
|
||||
"github.com/foomo/keel/healthz"
|
||||
"github.com/foomo/keel/interfaces"
|
||||
"github.com/foomo/keel/markdown"
|
||||
"github.com/foomo/keel/metrics"
|
||||
keelmongo "github.com/foomo/keel/persistence/mongo"
|
||||
"github.com/foomo/keel/service"
|
||||
"github.com/foomo/keel/telemetry/nonrecording"
|
||||
"github.com/go-logr/logr"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/spf13/viper"
|
||||
otelhost "go.opentelemetry.io/contrib/instrumentation/host"
|
||||
otelruntime "go.opentelemetry.io/contrib/instrumentation/runtime"
|
||||
@ -49,8 +50,8 @@ type Server struct {
|
||||
running atomic.Bool
|
||||
closers []interface{}
|
||||
closersLock sync.Mutex
|
||||
readmers []interfaces.Readmer
|
||||
probes map[healthz.Type][]interface{}
|
||||
documenter map[string]interfaces.Documenter
|
||||
ctx context.Context
|
||||
ctxCancel context.Context
|
||||
ctxCancelFn context.CancelFunc
|
||||
@ -64,8 +65,8 @@ func NewServer(opts ...Option) *Server {
|
||||
inst := &Server{
|
||||
shutdownTimeout: 30 * time.Second,
|
||||
shutdownSignals: []os.Signal{os.Interrupt, syscall.SIGTERM},
|
||||
readmers: []interfaces.Readmer{},
|
||||
probes: map[healthz.Type][]interface{}{},
|
||||
documenter: map[string]interfaces.Documenter{},
|
||||
ctx: context.Background(),
|
||||
c: config.Config(),
|
||||
l: log.Logger(),
|
||||
@ -178,7 +179,7 @@ func NewServer(opts ...Option) *Server {
|
||||
|
||||
// add probe
|
||||
inst.AddAlwaysHealthzers(inst)
|
||||
inst.AddDocumenter("Keel Server", inst)
|
||||
inst.AddReadmer(inst)
|
||||
|
||||
// start init services
|
||||
inst.startService(inst.initServices...)
|
||||
@ -218,20 +219,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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -244,25 +242,9 @@ func (s *Server) AddCloser(closer interface{}) {
|
||||
return
|
||||
}
|
||||
}
|
||||
switch closer.(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:
|
||||
if IsCloser(closer) {
|
||||
s.closers = append(s.closers, closer)
|
||||
default:
|
||||
} else {
|
||||
s.l.Warn("unable to add closer", log.FValue(fmt.Sprintf("%T", closer)))
|
||||
}
|
||||
}
|
||||
@ -274,22 +256,25 @@ func (s *Server) AddClosers(closers ...interface{}) {
|
||||
}
|
||||
}
|
||||
|
||||
// AddDocumenter adds a dcoumenter to beadded to the exposed docs
|
||||
func (s *Server) AddDocumenter(name string, documenter interfaces.Documenter) {
|
||||
s.documenter[name] = documenter
|
||||
// AddReadmer adds a readmer to be added to the exposed readme
|
||||
func (s *Server) AddReadmer(readmer interfaces.Readmer) {
|
||||
if !slices.Contains(s.readmers, readmer) {
|
||||
s.readmers = append(s.readmers, readmer)
|
||||
}
|
||||
}
|
||||
|
||||
// AddReadmers adds readmers to be added to the exposed readme
|
||||
func (s *Server) AddReadmers(readmers ...interfaces.Readmer) {
|
||||
for _, readmer := range readmers {
|
||||
s.AddCloser(readmer)
|
||||
}
|
||||
}
|
||||
|
||||
// AddHealthzer adds a probe to be called on healthz checks
|
||||
func (s *Server) AddHealthzer(typ healthz.Type, probe interface{}) {
|
||||
switch probe.(type) {
|
||||
case healthz.BoolHealthzer,
|
||||
healthz.BoolHealthzerWithContext,
|
||||
healthz.ErrorHealthzer,
|
||||
healthz.ErrorHealthzWithContext,
|
||||
interfaces.ErrorPinger,
|
||||
interfaces.ErrorPingerWithContext:
|
||||
if IsHealthz(probe) {
|
||||
s.probes[typ] = append(s.probes[typ], probe)
|
||||
default:
|
||||
} else {
|
||||
s.l.Debug("not a healthz probe", log.FValue(fmt.Sprintf("%T", probe)))
|
||||
}
|
||||
}
|
||||
@ -366,43 +351,131 @@ func (s *Server) Run() {
|
||||
s.l.Info("keel server stopped")
|
||||
}
|
||||
|
||||
// Docs returns the self-documenting string
|
||||
func (s *Server) Docs() string {
|
||||
// Readme returns the self-documenting string
|
||||
func (s *Server) Readme() string {
|
||||
md := &markdown.Markdown{}
|
||||
|
||||
{
|
||||
var rows [][]string
|
||||
keys := s.Config().AllKeys()
|
||||
defaults := config.Defaults()
|
||||
for _, key := range keys {
|
||||
var fallback interface{}
|
||||
if v, ok := defaults[key]; ok {
|
||||
fallback = v
|
||||
md.Print(env.Readme())
|
||||
md.Print(config.Readme())
|
||||
md.Println(s.readmeServices())
|
||||
md.Println(s.readmeHealthz())
|
||||
md.Print(s.readmeCloser())
|
||||
md.Print(keelmongo.Readme())
|
||||
md.Print(metrics.Readme())
|
||||
|
||||
return md.String()
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// ~ Private methods
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
// startService starts the given services
|
||||
func (s *Server) startService(services ...Service) {
|
||||
for _, value := range services {
|
||||
value := value
|
||||
s.g.Go(func() error {
|
||||
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")
|
||||
return err
|
||||
}
|
||||
rows = append(rows, []string{
|
||||
markdown.Code(key),
|
||||
markdown.Code(config.TypeOf(key)),
|
||||
"",
|
||||
markdown.Code(fmt.Sprintf("%v", fallback)),
|
||||
})
|
||||
return nil
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) readmeCloser() string {
|
||||
md := &markdown.Markdown{}
|
||||
rows := make([][]string, 0, len(s.closers))
|
||||
s.closersLock.Lock()
|
||||
defer s.closersLock.Unlock()
|
||||
for _, value := range s.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"
|
||||
}
|
||||
for _, key := range config.RequiredKeys() {
|
||||
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(key),
|
||||
markdown.Code(config.TypeOf(key)),
|
||||
markdown.Code("true"),
|
||||
"",
|
||||
markdown.Code(markdown.Name(probe)),
|
||||
markdown.Code(k.String()),
|
||||
markdown.Code(t.String()),
|
||||
markdown.String(probe),
|
||||
})
|
||||
}
|
||||
if len(rows) > 0 {
|
||||
md.Println("### Config")
|
||||
md.Println("")
|
||||
md.Println("List of all registered config variabled with their defaults.")
|
||||
md.Println("")
|
||||
md.Table([]string{"Key", "Type", "Required", "Default"}, rows)
|
||||
md.Println("")
|
||||
}
|
||||
}
|
||||
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 runntime.")
|
||||
md.Println("")
|
||||
md.Table([]string{"Name", "Probe", "Type", "Description"}, rows)
|
||||
md.Println("")
|
||||
}
|
||||
|
||||
return md.String()
|
||||
}
|
||||
|
||||
func (s *Server) readmeServices() string {
|
||||
md := &markdown.Markdown{}
|
||||
|
||||
{
|
||||
var rows [][]string
|
||||
@ -446,137 +519,5 @@ func (s *Server) Docs() string {
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
var rows [][]string
|
||||
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 runntime.")
|
||||
md.Println("")
|
||||
md.Table([]string{"Name", "Probe", "Type", "Description"}, rows)
|
||||
md.Println("")
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
var rows [][]string
|
||||
s.closersLock.Lock()
|
||||
defer s.closersLock.Unlock()
|
||||
for _, value := range s.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("")
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
var rows [][]string
|
||||
s.meter.AsyncFloat64()
|
||||
|
||||
values := nonrecording.Metrics()
|
||||
|
||||
gatherer, _ := prometheus.DefaultRegisterer.(*prometheus.Registry).Gather()
|
||||
for _, value := range gatherer {
|
||||
values = append(values, nonrecording.Metric{
|
||||
Name: value.GetName(),
|
||||
Type: value.GetType().String(),
|
||||
Help: value.GetHelp(),
|
||||
})
|
||||
}
|
||||
for _, value := range values {
|
||||
rows = append(rows, []string{
|
||||
markdown.Code(value.Name),
|
||||
value.Type,
|
||||
value.Help,
|
||||
})
|
||||
}
|
||||
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()
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// ~ Private methods
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
// startService starts the given services
|
||||
func (s *Server) startService(services ...Service) {
|
||||
for _, value := range services {
|
||||
value := value
|
||||
s.g.Go(func() error {
|
||||
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")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,23 +9,21 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultHTTPDocsName = "docs"
|
||||
DefaultHTTPDocsAddr = "localhost:9001"
|
||||
DefaultHTTPDocsPath = "/docs"
|
||||
DefaultHTTPReadmeName = "readme"
|
||||
DefaultHTTPReadmeAddr = "localhost:9001"
|
||||
DefaultHTTPReadmePath = "/readme"
|
||||
)
|
||||
|
||||
func NewHTTPDocs(l *zap.Logger, name, addr, path string, documenters map[string]interfaces.Documenter) *HTTP {
|
||||
func NewHTTPReadme(l *zap.Logger, name, addr, path string, readmers *[]interfaces.Readmer) *HTTP {
|
||||
handler := http.NewServeMux()
|
||||
handler.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Header().Add("Content-Type", "text/markdown")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
md := &markdown.Markdown{}
|
||||
for name, documenter := range documenters {
|
||||
md.Printf("## %s", name)
|
||||
md.Println("")
|
||||
md.Print(documenter.Docs())
|
||||
for _, readmer := range *readmers {
|
||||
md.Print(readmer.Readme())
|
||||
}
|
||||
_, _ = w.Write([]byte(md.String()))
|
||||
default:
|
||||
@ -35,12 +33,12 @@ func NewHTTPDocs(l *zap.Logger, name, addr, path string, documenters map[string]
|
||||
return NewHTTP(l, name, addr, handler)
|
||||
}
|
||||
|
||||
func NewDefaultHTTPDocs(l *zap.Logger, documenter map[string]interfaces.Documenter) *HTTP {
|
||||
return NewHTTPDocs(
|
||||
func NewDefaultHTTPReadme(l *zap.Logger, readmers *[]interfaces.Readmer) *HTTP {
|
||||
return NewHTTPReadme(
|
||||
l,
|
||||
DefaultHTTPDocsName,
|
||||
DefaultHTTPDocsAddr,
|
||||
DefaultHTTPDocsPath,
|
||||
documenter,
|
||||
DefaultHTTPReadmeName,
|
||||
DefaultHTTPReadmeAddr,
|
||||
DefaultHTTPReadmePath,
|
||||
readmers,
|
||||
)
|
||||
}
|
||||
@ -6,23 +6,34 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/foomo/keel"
|
||||
"github.com/foomo/keel/config"
|
||||
"github.com/foomo/keel/env"
|
||||
"github.com/foomo/keel/examples/persistence/mongo/store"
|
||||
"github.com/foomo/keel/log"
|
||||
keelmongo "github.com/foomo/keel/persistence/mongo"
|
||||
"github.com/foomo/keel/service"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func ExampleNewHTTPDocs() {
|
||||
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.WithHTTPDocsService(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()
|
||||
@ -33,18 +44,40 @@ func ExampleNewHTTPDocs() {
|
||||
_ = config.MustGetBool(c, "example.required.bool")
|
||||
_ = config.MustGetString(c, "example.required.string")
|
||||
|
||||
// create persistor
|
||||
persistor, err := keelmongo.New(svr.Context(), "mongodb://localhost:27017/dummy")
|
||||
log.Must(l, err, "failed to create persistor")
|
||||
|
||||
// ensure to add the persistor to the closers
|
||||
svr.AddClosers(persistor)
|
||||
|
||||
// create repositories
|
||||
_, err = persistor.Collection(
|
||||
"dummy",
|
||||
// define indexes but beware of changes on large dbs
|
||||
keelmongo.CollectionWithIndexes(
|
||||
store.EntityIndex,
|
||||
store.EntityWithVersionsIndex,
|
||||
),
|
||||
// define max time for index creation
|
||||
keelmongo.CollectionWithIndexesMaxTime(time.Minute),
|
||||
)
|
||||
log.Must(l, err, "failed to create collection")
|
||||
|
||||
// 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() {
|
||||
resp, _ := http.Get("http://localhost:9001/docs") //nolint:noctx
|
||||
defer resp.Body.Close() //nolint:govet
|
||||
resp, _ := http.Get("http://localhost:9001/readme") //nolint:noctx
|
||||
defer resp.Body.Close() //nolint:govet
|
||||
b, _ := io.ReadAll(resp.Body)
|
||||
fmt.Print(string(b))
|
||||
shutdown()
|
||||
@ -53,7 +86,26 @@ func ExampleNewHTTPDocs() {
|
||||
svr.Run()
|
||||
|
||||
// Output:
|
||||
// ## Keel Server
|
||||
// ### 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_MONGO_COMMAND_ATTRIBUTE_DISABLED` | `bool` | | |
|
||||
// | `OTEL_MONGO_ENABLED` | `bool` | | |
|
||||
//
|
||||
// ### Config
|
||||
//
|
||||
@ -65,15 +117,15 @@ func ExampleNewHTTPDocs() {
|
||||
// | `example.required.bool` | `bool` | `true` | |
|
||||
// | `example.required.string` | `string` | `true` | |
|
||||
// | `example.string` | `string` | | `fallback` |
|
||||
// | `service.docs.enabled` | `bool` | | `true` |
|
||||
// | `service.readme.enabled` | `bool` | | `true` |
|
||||
//
|
||||
// ### Init Services
|
||||
//
|
||||
// List of all registered init services that are being immediately started.
|
||||
//
|
||||
// | Name | Type | Address |
|
||||
// | ------ | --------------- | ------------------------------------ |
|
||||
// | `docs` | `*service.HTTP` | `*http.ServeMux` on `localhost:9001` |
|
||||
// | Name | Type | Address |
|
||||
// | -------- | --------------- | ------------------------------------ |
|
||||
// | `readme` | `*service.HTTP` | `*http.ServeMux` on `localhost:9001` |
|
||||
//
|
||||
// ### Services
|
||||
//
|
||||
@ -84,6 +136,7 @@ func ExampleNewHTTPDocs() {
|
||||
// | `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 runntime.
|
||||
@ -93,17 +146,27 @@ func ExampleNewHTTPDocs() {
|
||||
// | | `always` | `*keel.Server` | |
|
||||
// | `demo-goroutine` | `always` | `*service.GoRoutine` | parallel: `1` |
|
||||
// | `demp-http` | `always` | `*service.HTTP` | `http.HandlerFunc` on `localhost:8080` |
|
||||
// | `docs` | `always` | `*service.HTTP` | `*http.ServeMux` on `localhost:9001` |
|
||||
// | `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` |
|
||||
// | `docs` | `*service.HTTP` | `ErrorCloserWithContext` | `*http.ServeMux` on `localhost:9001` |
|
||||
// | Name | Type | Closer | Description |
|
||||
// | ---------------- | ---------------------- | ------------------------ | -------------------------------------- |
|
||||
// | | `*keelmongo.Persistor` | `ErrorCloserWithContext` | |
|
||||
// | `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` |
|
||||
//
|
||||
// ### Mongo
|
||||
//
|
||||
// List of all used mongo collections.
|
||||
//
|
||||
// | Database | Collection | Indices |
|
||||
// | -------- | ---------- | ------------------------ |
|
||||
// | `dummy` | `dummy` | `id_1`, `id_1_version_1` |
|
||||
//
|
||||
// ### Metrics
|
||||
//
|
||||
@ -137,7 +200,7 @@ func ExampleNewHTTPDocs() {
|
||||
// | `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. |
|
||||
// | `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. |
|
||||
Loading…
Reference in New Issue
Block a user