mirror of
https://github.com/foomo/keel.git
synced 2025-10-16 12:35:34 +00:00
436 lines
10 KiB
Go
436 lines
10 KiB
Go
//go:build docs
|
|
// +build docs
|
|
|
|
package keel
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"reflect"
|
|
"sort"
|
|
"time"
|
|
|
|
markdowntable "github.com/fbiville/markdown-table-formatter/pkg/markdown"
|
|
"github.com/foomo/keel/config"
|
|
"github.com/foomo/keel/healthz"
|
|
"github.com/foomo/keel/interfaces"
|
|
"github.com/foomo/keel/log"
|
|
"github.com/foomo/keel/service"
|
|
"github.com/foomo/keel/telemetry/nonrecording"
|
|
"github.com/pkg/errors"
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
"github.com/spf13/viper"
|
|
"go.opentelemetry.io/otel"
|
|
"go.opentelemetry.io/otel/metric"
|
|
otelglobal "go.opentelemetry.io/otel/metric/global"
|
|
"go.opentelemetry.io/otel/trace"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
// 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
|
|
closers []interface{}
|
|
probes map[healthz.Type][]interface{}
|
|
ctx context.Context
|
|
gCtx context.Context
|
|
l *zap.Logger
|
|
c *viper.Viper
|
|
}
|
|
|
|
func NewServer(opts ...Option) *Server {
|
|
inst := &Server{
|
|
probes: map[healthz.Type][]interface{}{},
|
|
meterProvider: nonrecording.NewNoopMeterProvider(),
|
|
traceProvider: trace.NewNoopTracerProvider(),
|
|
ctx: context.Background(),
|
|
c: config.Config(),
|
|
l: log.Logger(),
|
|
}
|
|
|
|
inst.meter = inst.meterProvider.Meter("")
|
|
otelglobal.SetMeterProvider(inst.meterProvider)
|
|
inst.tracer = inst.traceProvider.Tracer("")
|
|
otel.SetTracerProvider(inst.traceProvider)
|
|
|
|
// add probe
|
|
inst.AddAlwaysHealthzers(inst)
|
|
|
|
return inst
|
|
}
|
|
|
|
// Logger returns server logger
|
|
func (s *Server) Logger() *zap.Logger {
|
|
return s.l
|
|
}
|
|
|
|
// Meter returns the implementation meter
|
|
func (s *Server) Meter() metric.Meter {
|
|
return s.meter
|
|
}
|
|
|
|
// Tracer returns the implementation tracer
|
|
func (s *Server) Tracer() trace.Tracer {
|
|
return s.tracer
|
|
}
|
|
|
|
// Config returns server config
|
|
func (s *Server) Config() *viper.Viper {
|
|
return s.c
|
|
}
|
|
|
|
// Context returns server context
|
|
func (s *Server) Context() context.Context {
|
|
return s.ctx
|
|
}
|
|
|
|
// CancelContext returns server's cancel context
|
|
func (s *Server) CancelContext() context.Context {
|
|
return s.ctx
|
|
}
|
|
|
|
// AddService add a single service
|
|
func (s *Server) AddService(service Service) {
|
|
for _, value := range s.services {
|
|
if value == service {
|
|
return
|
|
}
|
|
}
|
|
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)
|
|
}
|
|
}
|
|
|
|
// AddCloser adds a closer to be called on shutdown
|
|
func (s *Server) AddCloser(closer interface{}) {
|
|
for _, value := range s.closers {
|
|
if value == closer {
|
|
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:
|
|
s.closers = append(s.closers, closer)
|
|
default:
|
|
s.l.Warn("unable to add closer", log.FValue(fmt.Sprintf("%T", closer)))
|
|
}
|
|
}
|
|
|
|
// AddClosers adds the given closers to be called on shutdown
|
|
func (s *Server) AddClosers(closers ...interface{}) {
|
|
for _, closer := range closers {
|
|
s.AddCloser(closer)
|
|
}
|
|
}
|
|
|
|
// 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:
|
|
s.probes[typ] = append(s.probes[typ], probe)
|
|
default:
|
|
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 healthz.Type, probes ...interface{}) {
|
|
for _, probe := range probes {
|
|
s.AddHealthzer(typ, probe)
|
|
}
|
|
}
|
|
|
|
// AddAlwaysHealthzers adds the probes to be called on any healthz checks
|
|
func (s *Server) AddAlwaysHealthzers(probes ...interface{}) {
|
|
s.AddHealthzers(healthz.TypeAlways, probes...)
|
|
}
|
|
|
|
// AddStartupHealthzers adds the startup probes to be called on healthz checks
|
|
func (s *Server) AddStartupHealthzers(probes ...interface{}) {
|
|
s.AddHealthzers(healthz.TypeStartup, probes...)
|
|
}
|
|
|
|
// AddLivenessHealthzers adds the liveness probes to be called on healthz checks
|
|
func (s *Server) AddLivenessHealthzers(probes ...interface{}) {
|
|
s.AddHealthzers(healthz.TypeLiveness, probes...)
|
|
}
|
|
|
|
// AddReadinessHealthzers adds the readiness probes to be called on healthz checks
|
|
func (s *Server) AddReadinessHealthzers(probes ...interface{}) {
|
|
s.AddHealthzers(healthz.TypeReadiness, probes...)
|
|
}
|
|
|
|
// IsCanceled returns true if the internal errgroup has been canceled
|
|
func (s *Server) IsCanceled() bool {
|
|
return errors.Is(s.gCtx.Err(), context.Canceled)
|
|
}
|
|
|
|
// Healthz returns true if the server is running
|
|
func (s *Server) Healthz() error {
|
|
return nil
|
|
}
|
|
|
|
// Run runs the server
|
|
func (s *Server) Run() {
|
|
// add init services to closers
|
|
for _, initService := range s.initServices {
|
|
s.AddClosers(initService)
|
|
}
|
|
|
|
md := &MD{}
|
|
|
|
{
|
|
var rows [][]string
|
|
for _, key := range s.Config().AllKeys() {
|
|
rows = append(rows, []string{
|
|
code(key),
|
|
code(s.Config().GetString(key)),
|
|
})
|
|
}
|
|
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", "Default"}, rows)
|
|
md.Println("")
|
|
}
|
|
}
|
|
|
|
{
|
|
var rows [][]string
|
|
for _, value := range s.initServices {
|
|
if v, ok := value.(*service.HTTP); ok {
|
|
t := reflect.TypeOf(v)
|
|
rows = append(rows, []string{
|
|
code(v.Name()),
|
|
code(t.String()),
|
|
stringer(v),
|
|
})
|
|
}
|
|
}
|
|
if len(rows) > 0 {
|
|
md.Println("## Init Services")
|
|
md.Println("")
|
|
md.Println("List of all registerd 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 {
|
|
if v, ok := value.(*service.HTTP); ok {
|
|
t := reflect.TypeOf(v)
|
|
rows = append(rows, []string{
|
|
code(v.Name()),
|
|
code(t.String()),
|
|
stringer(v),
|
|
})
|
|
}
|
|
}
|
|
if len(rows) > 0 {
|
|
md.Println("## Services")
|
|
md.Println("")
|
|
md.Println("List of all registered services that are being started.")
|
|
md.Println("")
|
|
md.Table([]string{"Name", "Type", "Description"}, rows)
|
|
md.Println("")
|
|
}
|
|
}
|
|
|
|
{
|
|
var rows [][]string
|
|
for k, probes := range s.probes {
|
|
for _, probe := range probes {
|
|
t := reflect.TypeOf(probe)
|
|
rows = append(rows, []string{
|
|
code(k.String()),
|
|
code(t.String()),
|
|
})
|
|
}
|
|
}
|
|
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", "Type"}, rows)
|
|
md.Println("")
|
|
}
|
|
}
|
|
|
|
{
|
|
var rows [][]string
|
|
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{
|
|
code(t.String()),
|
|
code(closer),
|
|
})
|
|
}
|
|
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"}, rows)
|
|
md.Println("")
|
|
}
|
|
}
|
|
|
|
{
|
|
var rows [][]string
|
|
s.meter.AsyncFloat64()
|
|
|
|
var names []string
|
|
values := map[string]nonrecording.Metric{}
|
|
for _, value := range nonrecording.Metrics() {
|
|
names = append(names, value.Name)
|
|
values[value.Name] = value
|
|
}
|
|
|
|
gatherer, _ := prometheus.DefaultRegisterer.(*prometheus.Registry).Gather()
|
|
for _, value := range gatherer {
|
|
names = append(names, value.GetName())
|
|
values[value.GetName()] = nonrecording.Metric{
|
|
Name: value.GetName(),
|
|
Type: value.GetType().String(),
|
|
Help: value.GetHelp(),
|
|
}
|
|
}
|
|
sort.Strings(names)
|
|
for _, name := range names {
|
|
value := values[name]
|
|
rows = append(rows, []string{
|
|
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("")
|
|
}
|
|
}
|
|
|
|
fmt.Print(md.String())
|
|
}
|
|
|
|
type MD struct {
|
|
value string
|
|
}
|
|
|
|
func (s *MD) Println(a ...any) {
|
|
s.value += fmt.Sprintln(a...)
|
|
}
|
|
|
|
func (s *MD) Print(a ...any) {
|
|
s.value += fmt.Sprint(a...)
|
|
}
|
|
|
|
func (s *MD) String() string {
|
|
return s.value
|
|
}
|
|
|
|
func (s *MD) Table(headers []string, rows [][]string) {
|
|
table, err := markdowntable.NewTableFormatterBuilder().
|
|
WithPrettyPrint().
|
|
Build(headers...).
|
|
Format(rows)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
s.Print(table)
|
|
}
|
|
|
|
func code(v string) string {
|
|
if v == "" {
|
|
return ""
|
|
}
|
|
return "`" + v + "`"
|
|
}
|
|
|
|
func stringer(v any) string {
|
|
if i, ok := v.(fmt.Stringer); ok {
|
|
return i.String()
|
|
}
|
|
return ""
|
|
}
|