feat: add GoRoutine service

moves all services into the service package
This commit is contained in:
Kevin Franklin Kim 2023-09-08 12:14:56 +02:00
parent dfef906811
commit 07f0c394d5
No known key found for this signature in database
61 changed files with 1186 additions and 256 deletions

View File

@ -25,6 +25,7 @@ import (
"net/http" "net/http"
"github.com/foomo/keel" "github.com/foomo/keel"
"github.com/foomo/keel/service"
) )
func main() { func main() {
@ -39,7 +40,7 @@ func main() {
svs := newService() svs := newService()
svr.AddService( svr.AddService(
keel.NewServiceHTTP(l, "demo", ":8080", svs), service.NewHTTP(l, "demo", "localhost:8080", svs),
) )
svr.Run() svr.Run()

View File

@ -5,6 +5,5 @@ import (
) )
var ( var (
ErrServerNotRunning = errors.New("server not running") ErrServerNotRunning = errors.New("server not running")
ErrServiceNotRunning = errors.New("service not running")
) )

View File

@ -7,6 +7,7 @@ import (
"time" "time"
"github.com/davecgh/go-spew/spew" "github.com/davecgh/go-spew/spew"
"github.com/foomo/keel/service"
"github.com/foomo/keel" "github.com/foomo/keel"
"github.com/foomo/keel/config" "github.com/foomo/keel/config"
@ -85,7 +86,7 @@ func main() {
}) })
svr.AddService( svr.AddService(
keel.NewServiceHTTP(l, "demo", "localhost:8081", svs), service.NewHTTP(l, "demo", "localhost:8081", svs),
) )
svr.Run() svr.Run()

View File

@ -6,6 +6,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/foomo/keel/service"
"go.uber.org/zap" "go.uber.org/zap"
"github.com/foomo/keel" "github.com/foomo/keel"
@ -32,7 +33,7 @@ func main() {
}) })
svr.AddService( svr.AddService(
keel.NewServiceHTTP(l, "demo", "localhost:8080", svs), service.NewHTTP(l, "demo", "localhost:8080", svs),
) )
svr.Run() svr.Run()

View File

@ -8,6 +8,8 @@ import (
"github.com/foomo/keel" "github.com/foomo/keel"
"github.com/foomo/keel/examples/healthz/handler" "github.com/foomo/keel/examples/healthz/handler"
"github.com/foomo/keel/healthz"
"github.com/foomo/keel/service"
) )
// See k8s for probe documentation // See k8s for probe documentation
@ -46,7 +48,7 @@ func main() {
svr.AddReadinessHealthzers(rh) svr.AddReadinessHealthzers(rh)
// add inline probe e.g. in case you start go routines // 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") l.Info("healther fn")
return nil return nil
})) }))
@ -69,7 +71,7 @@ func main() {
// add services // add services
svr.AddService( svr.AddService(
keel.NewServiceHTTP(l, "demo", "localhost:8080", svs), service.NewHTTP(l, "demo", "localhost:8080", svs),
) )
// start serer // start serer

View File

@ -7,6 +7,7 @@ import (
"github.com/foomo/keel" "github.com/foomo/keel"
"github.com/foomo/keel/log" "github.com/foomo/keel/log"
"github.com/foomo/keel/service"
) )
type CustomError struct { type CustomError struct {
@ -46,7 +47,7 @@ func main() {
}) })
svr.AddService( svr.AddService(
keel.NewServiceHTTP(l, "demo", "localhost:8080", svs), service.NewHTTP(l, "demo", "localhost:8080", svs),
) )
svr.Run() svr.Run()

View File

@ -6,6 +6,7 @@ import (
"github.com/foomo/keel" "github.com/foomo/keel"
"github.com/foomo/keel/log" "github.com/foomo/keel/log"
"github.com/foomo/keel/net/http/middleware" "github.com/foomo/keel/net/http/middleware"
"github.com/foomo/keel/service"
httputils "github.com/foomo/keel/utils/net/http" httputils "github.com/foomo/keel/utils/net/http"
) )
@ -29,7 +30,7 @@ func main() {
log.Must(l, err, "failed to hash password") log.Must(l, err, "failed to hash password")
svr.AddService( svr.AddService(
keel.NewServiceHTTP(l, "demo", "localhost:8080", svs, service.NewHTTP(l, "demo", "localhost:8080", svs,
middleware.BasicAuth( middleware.BasicAuth(
username, username,
passwordHash, passwordHash,

View File

@ -6,6 +6,7 @@ import (
"github.com/foomo/keel" "github.com/foomo/keel"
keelhttp "github.com/foomo/keel/net/http" keelhttp "github.com/foomo/keel/net/http"
"github.com/foomo/keel/net/http/middleware" "github.com/foomo/keel/net/http/middleware"
"github.com/foomo/keel/service"
) )
func main() { func main() {
@ -22,7 +23,7 @@ func main() {
}) })
svr.AddService( svr.AddService(
keel.NewServiceHTTP(l, "demo", "localhost:8080", svs, service.NewHTTP(l, "demo", "localhost:8080", svs,
middleware.CORS( middleware.CORS(
middleware.CORSWithAllowOrigins("example.com"), middleware.CORSWithAllowOrigins("example.com"),
middleware.CORSWithAllowMethods(http.MethodGet, http.MethodPost), middleware.CORSWithAllowMethods(http.MethodGet, http.MethodPost),

View File

@ -6,6 +6,7 @@ import (
"net/http" "net/http"
"strings" "strings"
"github.com/foomo/keel/service"
jwt2 "github.com/golang-jwt/jwt" jwt2 "github.com/golang-jwt/jwt"
"go.uber.org/zap" "go.uber.org/zap"
@ -75,7 +76,7 @@ func main() {
}) })
svr.AddService( svr.AddService(
keel.NewServiceHTTP(l, "demo", "localhost:8080", svs, service.NewHTTP(l, "demo", "localhost:8080", svs,
middleware.Skip( middleware.Skip(
middleware.JWT( middleware.JWT(
jwtInst, jwtInst,

View File

@ -5,6 +5,7 @@ import (
"crypto/rsa" "crypto/rsa"
"net/http" "net/http"
"github.com/foomo/keel/service"
jwt2 "github.com/golang-jwt/jwt" jwt2 "github.com/golang-jwt/jwt"
"github.com/foomo/keel" "github.com/foomo/keel"
@ -66,7 +67,7 @@ func main() {
}) })
svr.AddService( svr.AddService(
keel.NewServiceHTTP(l, "demo", "localhost:8080", svs, service.NewHTTP(l, "demo", "localhost:8080", svs,
middleware.Skip( middleware.Skip(
middleware.JWT( middleware.JWT(
jwtInst, jwtInst,

View File

@ -6,6 +6,7 @@ import (
"github.com/foomo/keel" "github.com/foomo/keel"
keelhttp "github.com/foomo/keel/net/http" keelhttp "github.com/foomo/keel/net/http"
"github.com/foomo/keel/net/http/middleware" "github.com/foomo/keel/net/http/middleware"
"github.com/foomo/keel/service"
) )
func main() { func main() {
@ -22,7 +23,7 @@ func main() {
}) })
svr.AddService( svr.AddService(
keel.NewServiceHTTP(l, "demo", "localhost:8080", svs, service.NewHTTP(l, "demo", "localhost:8080", svs,
middleware.Logger(), middleware.Logger(),
), ),
) )

View File

@ -5,6 +5,7 @@ import (
"github.com/foomo/keel" "github.com/foomo/keel"
"github.com/foomo/keel/net/http/middleware" "github.com/foomo/keel/net/http/middleware"
"github.com/foomo/keel/service"
) )
func main() { func main() {
@ -23,7 +24,7 @@ func main() {
}) })
svr.AddService( svr.AddService(
keel.NewServiceHTTP(l, "demo", "localhost:8080", svs, service.NewHTTP(l, "demo", "localhost:8080", svs,
middleware.Recover( middleware.Recover(
middleware.RecoverWithDisablePrintStack(true), middleware.RecoverWithDisablePrintStack(true),
), ),

View File

@ -6,6 +6,7 @@ import (
"github.com/foomo/keel" "github.com/foomo/keel"
keelhttp "github.com/foomo/keel/net/http" keelhttp "github.com/foomo/keel/net/http"
"github.com/foomo/keel/net/http/middleware" "github.com/foomo/keel/net/http/middleware"
"github.com/foomo/keel/service"
) )
func main() { func main() {
@ -27,7 +28,7 @@ func main() {
}) })
svr.AddService( svr.AddService(
keel.NewServiceHTTP(l, "demo", "localhost:8080", svs, service.NewHTTP(l, "demo", "localhost:8080", svs,
middleware.RequestID( middleware.RequestID(
middleware.RequestIDWithSetResponseHeader(true), middleware.RequestIDWithSetResponseHeader(true),
middleware.RequestIDWithGenerator(requestIDGenerator), middleware.RequestIDWithGenerator(requestIDGenerator),

View File

@ -6,6 +6,7 @@ import (
"github.com/foomo/keel" "github.com/foomo/keel"
"github.com/foomo/keel/net/http/middleware" "github.com/foomo/keel/net/http/middleware"
"github.com/foomo/keel/service"
) )
func main() { func main() {
@ -27,7 +28,7 @@ func main() {
}) })
svr.AddService( svr.AddService(
keel.NewServiceHTTP(l, "demo", "localhost:8080", svs, service.NewHTTP(l, "demo", "localhost:8080", svs,
middleware.ResponseTime( middleware.ResponseTime(
// automatically set cookie if not exists // automatically set cookie if not exists
middleware.ResponseTimeWithMaxDuration(time.Millisecond*500), middleware.ResponseTimeWithMaxDuration(time.Millisecond*500),

View File

@ -8,6 +8,7 @@ import (
keelhttp "github.com/foomo/keel/net/http" keelhttp "github.com/foomo/keel/net/http"
"github.com/foomo/keel/net/http/cookie" "github.com/foomo/keel/net/http/cookie"
"github.com/foomo/keel/net/http/middleware" "github.com/foomo/keel/net/http/middleware"
"github.com/foomo/keel/service"
) )
func main() { func main() {
@ -44,7 +45,7 @@ func main() {
}) })
svr.AddService( svr.AddService(
keel.NewServiceHTTP(l, "demo", "localhost:8080", svs, service.NewHTTP(l, "demo", "localhost:8080", svs,
middleware.SessionID( middleware.SessionID(
// automatically set cookie if not exists // automatically set cookie if not exists
middleware.SessionIDWithSetCookie(true), middleware.SessionIDWithSetCookie(true),

View File

@ -3,6 +3,7 @@ package main
import ( import (
"net/http" "net/http"
"github.com/foomo/keel/service"
"go.uber.org/zap" "go.uber.org/zap"
"github.com/foomo/keel" "github.com/foomo/keel"
@ -28,7 +29,7 @@ func main() {
svr.AddServices( svr.AddServices(
// with URI blacklist // with URI blacklist
keel.NewServiceHTTP(l, "demo", "localhost:8080", svs, service.NewHTTP(l, "demo", "localhost:8080", svs,
middleware.Skip( middleware.Skip(
func(l *zap.Logger, name string, next http.Handler) http.Handler { func(l *zap.Logger, name string, next http.Handler) http.Handler {
return http.NotFoundHandler() return http.NotFoundHandler()
@ -38,7 +39,7 @@ func main() {
), ),
// with URI whitelist // with URI whitelist
keel.NewServiceHTTP(l, "demo", ":8081", svs, service.NewHTTP(l, "demo", "localhost:8081", svs,
middleware.Skip( middleware.Skip(
func(l *zap.Logger, name string, next http.Handler) http.Handler { func(l *zap.Logger, name string, next http.Handler) http.Handler {
return http.NotFoundHandler() return http.NotFoundHandler()

View File

@ -5,6 +5,7 @@ import (
"github.com/foomo/keel" "github.com/foomo/keel"
"github.com/foomo/keel/net/http/middleware" "github.com/foomo/keel/net/http/middleware"
"github.com/foomo/keel/service"
) )
func main() { func main() {
@ -23,7 +24,7 @@ func main() {
}) })
svr.AddService( svr.AddService(
keel.NewServiceHTTP(l, "demo", "localhost:8080", svs, service.NewHTTP(l, "demo", "localhost:8080", svs,
middleware.Telemetry( middleware.Telemetry(
middleware.TelemetryWithInjectPropagationHeader(true), middleware.TelemetryWithInjectPropagationHeader(true),
), ),

View File

@ -5,6 +5,7 @@ import (
"github.com/foomo/keel" "github.com/foomo/keel"
"github.com/foomo/keel/net/http/middleware" "github.com/foomo/keel/net/http/middleware"
"github.com/foomo/keel/service"
) )
func main() { func main() {
@ -26,7 +27,7 @@ func main() {
tokenProvider := middleware.CookieTokenProvider("keel-token") tokenProvider := middleware.CookieTokenProvider("keel-token")
svr.AddService( svr.AddService(
keel.NewServiceHTTP(l, "demo", "localhost:8080", svs, service.NewHTTP(l, "demo", "localhost:8080", svs,
middleware.TokenAuth( middleware.TokenAuth(
token, token,
middleware.TokenAuthWithTokenProvider(tokenProvider), middleware.TokenAuthWithTokenProvider(tokenProvider),

View File

@ -5,6 +5,7 @@ import (
"github.com/foomo/keel" "github.com/foomo/keel"
"github.com/foomo/keel/net/http/middleware" "github.com/foomo/keel/net/http/middleware"
"github.com/foomo/keel/service"
) )
func main() { func main() {
@ -29,7 +30,7 @@ func main() {
) )
svr.AddService( svr.AddService(
keel.NewServiceHTTP(l, "demo", "localhost:8080", svs, service.NewHTTP(l, "demo", "localhost:8080", svs,
middleware.TokenAuth( middleware.TokenAuth(
token, token,
middleware.TokenAuthWithTokenProvider(tokenProvider), middleware.TokenAuthWithTokenProvider(tokenProvider),

View File

@ -6,6 +6,7 @@ import (
"github.com/foomo/keel" "github.com/foomo/keel"
"github.com/foomo/keel/config" "github.com/foomo/keel/config"
"github.com/foomo/keel/service"
) )
func main() { func main() {
@ -42,7 +43,7 @@ func main() {
// curl localhost:8080 // curl localhost:8080
svr.AddService( 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) { func(w http.ResponseWriter, r *http.Request) {
fmt.Println("current foo:", fooFn()) //nolint:forbidigo fmt.Println("current foo:", fooFn()) //nolint:forbidigo
}), }),

View File

@ -4,6 +4,7 @@ import (
"net/http" "net/http"
"github.com/foomo/keel" "github.com/foomo/keel"
"github.com/foomo/keel/service"
) )
func server() { func server() {
@ -26,7 +27,7 @@ func server() {
}) })
svr.AddService( svr.AddService(
keel.NewServiceHTTP(l, "demo", "localhost:8080", svs), service.NewHTTP(l, "demo", "localhost:8080", svs),
) )
svr.Run() svr.Run()

View File

@ -8,6 +8,7 @@ import (
keelhttp "github.com/foomo/keel/net/http" keelhttp "github.com/foomo/keel/net/http"
"github.com/foomo/keel/net/http/middleware" "github.com/foomo/keel/net/http/middleware"
"github.com/foomo/keel/net/http/roundtripware" "github.com/foomo/keel/net/http/roundtripware"
"github.com/foomo/keel/service"
httputils "github.com/foomo/keel/utils/net/http" httputils "github.com/foomo/keel/utils/net/http"
) )
@ -52,7 +53,7 @@ func main() {
}) })
svr.AddService( svr.AddService(
keel.NewServiceHTTP(l, "demo", "localhost:8080", svs, service.NewHTTP(l, "demo", "localhost:8080", svs,
// add middleware // add middleware
middleware.RequestID(), middleware.RequestID(),
// add middleware // add middleware

View File

@ -4,6 +4,7 @@ import (
"net/http" "net/http"
"github.com/foomo/keel" "github.com/foomo/keel"
"github.com/foomo/keel/service"
) )
func server() { func server() {
@ -27,7 +28,7 @@ func server() {
}) })
svr.AddService( svr.AddService(
keel.NewServiceHTTP(l, "demo", "localhost:8080", svs), service.NewHTTP(l, "demo", "localhost:8080", svs),
) )
svr.Run() svr.Run()

View File

@ -5,6 +5,7 @@ import (
"github.com/foomo/keel" "github.com/foomo/keel"
"github.com/foomo/keel/config" "github.com/foomo/keel/config"
"github.com/foomo/keel/service"
) )
func main() { func main() {
@ -23,7 +24,7 @@ func main() {
}) })
svr.AddServices( svr.AddServices(
keel.NewServiceHTTP(l, "demo", "localhost:8080", service.NewHTTP(l, "demo", "localhost:8080",
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
c.Set("service.enabled", !enabled()) c.Set("service.enabled", !enabled())
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
@ -32,7 +33,7 @@ func main() {
), ),
keel.NewServiceEnabler(l, "service-enabler", keel.NewServiceEnabler(l, "service-enabler",
func() keel.Service { func() keel.Service {
return keel.NewServiceHTTP(l, "service", "localhost:8081", svs) return service.NewHTTP(l, "service", "localhost:8081", svs)
}, },
enabled, enabled,
), ),

View File

@ -1,7 +1,6 @@
package main package main
import ( import (
"net/http"
"os" "os"
"github.com/foomo/keel" "github.com/foomo/keel"
@ -29,24 +28,11 @@ func main() {
keel.WithHTTPPProfService(false), keel.WithHTTPPProfService(false),
) )
l := svr.Logger()
// alternatively you can add them manually // alternatively you can add them manually
// svr.AddServices(keel.NewDefaultServiceHTTPZap()) // svr.AddServices(keel.NewDefaultServiceHTTPZap())
// svr.AddServices(keel.NewDefaultServiceHTTPViper()) // svr.AddServices(keel.NewDefaultServiceHTTPViper())
// svr.AddServices(keel.NewDefaultServiceHTTPPProf()) // svr.AddServices(keel.NewDefaultServiceHTTPPProf())
// svr.AddServices(keel.NewDefaultServiceHTTPPrometheus()) // 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() svr.Run()
} }

View File

@ -5,6 +5,7 @@ import (
"net/http" "net/http"
"time" "time"
"github.com/foomo/keel/service"
"github.com/nats-io/nats.go" "github.com/nats-io/nats.go"
"github.com/pkg/errors" "github.com/pkg/errors"
"go.uber.org/zap" "go.uber.org/zap"
@ -89,7 +90,7 @@ func main() {
svr.AddClosers(subscription, stream) svr.AddClosers(subscription, stream)
svr.AddService( svr.AddService(
keel.NewServiceHTTP(l, "demo", "localhost:8080", svs), service.NewHTTP(l, "demo", "localhost:8080", svs),
) )
svr.Run() svr.Run()

View File

@ -4,6 +4,7 @@ import (
"net/http" "net/http"
"time" "time"
"github.com/foomo/keel/service"
"github.com/nats-io/nats.go" "github.com/nats-io/nats.go"
"github.com/foomo/keel" "github.com/foomo/keel"
@ -73,7 +74,7 @@ func main() {
svr.AddClosers(subscription, stream.Conn()) svr.AddClosers(subscription, stream.Conn())
svr.AddService( svr.AddService(
keel.NewServiceHTTP(l, "demo", "localhost:8080", svs), service.NewHTTP(l, "demo", "localhost:8080", svs),
) )
svr.Run() svr.Run()

View File

@ -4,6 +4,9 @@ import (
"math/rand" "math/rand"
"net/http" "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/attribute"
"go.opentelemetry.io/otel/metric/instrument" "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 { // up down
upDown, err := meter.SyncInt64().UpDownCounter( upDown, err := meter.SyncInt64().UpDownCounter(
"a.updown", "a.updown",
@ -92,7 +103,7 @@ func main() {
} }
svr.AddService( svr.AddService(
keel.NewServiceHTTP(l, "demo", "localhost:8080", svs, service.NewHTTP(l, "demo", "localhost:8080", svs,
middleware.Telemetry(), middleware.Telemetry(),
middleware.Recover(), middleware.Recover(),
), ),

1
go.mod
View File

@ -5,6 +5,7 @@ go 1.20
require ( require (
github.com/avast/retry-go v3.0.0+incompatible github.com/avast/retry-go v3.0.0+incompatible
github.com/davecgh/go-spew v1.1.1 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/foomo/gotsrpc/v2 v2.7.2
github.com/go-logr/logr v1.2.4 github.com/go-logr/logr v1.2.4
github.com/golang-jwt/jwt v3.2.2+incompatible github.com/golang-jwt/jwt v3.2.2+incompatible

2
go.sum
View File

@ -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.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 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= 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 h1:+nS9g82KMXccJ/wp0zyRW9ZBHFETmMGtkk+2CTTrW4o=
github.com/felixge/httpsnoop v1.0.2/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/felixge/httpsnoop v1.0.2/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/foomo/gotsrpc/v2 v2.7.2 h1:a94V/a8LSssq+aRN3Fv1lJPjWoyMilOvRq+yEaDTHVM= github.com/foomo/gotsrpc/v2 v2.7.2 h1:a94V/a8LSssq+aRN3Fv1lJPjWoyMilOvRq+yEaDTHVM=

1
healthz/docs.go Normal file
View File

@ -0,0 +1 @@
package healthz

View File

@ -1,4 +1,4 @@
package keel package healthz
import "context" import "context"
@ -16,6 +16,10 @@ func (h healther) Healthz(ctx context.Context) error {
return h.handle(ctx) return h.handle(ctx)
} }
func (h healther) Close(ctx context.Context) error {
return h.handle(ctx)
}
// BoolHealthzer interface // BoolHealthzer interface
type BoolHealthzer interface { type BoolHealthzer interface {
Healthz() bool Healthz() bool

View File

@ -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/ // https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/
type HealthzType string type Type string
const ( const (
// HealthzTypeAlways will run on any checks // TypeAlways will run on any checks
HealthzTypeAlways HealthzType = "always" TypeAlways Type = "always"
// HealthzTypeStartup will run on /healthz/startup checks // 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, // > 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 // > 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 // > 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. // > killed by the kubelet before they are up and running.
HealthzTypeStartup HealthzType = "startup" TypeStartup Type = "startup"
// HealthzTypeReadiness will run on /healthz/readiness checks // TypeReadiness will run on /healthz/readiness checks
// > The kubelet uses readiness probes to know when a container is ready to start accepting traffic. // > 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 // > 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. // > which Pods are used as backends for Services. When a Pod is not ready, it is removed from Service load balancers.
HealthzTypeReadiness HealthzType = "readiness" TypeReadiness Type = "readiness"
// HealthzTypeLiveness will run on /healthz/liveness checks // 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 // > 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 // > 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. // > can help to make the application more available despite bugs.
HealthzTypeLiveness HealthzType = "liveness" TypeLiveness Type = "liveness"
) )
// String interface // String interface
func (t HealthzType) String() string { func (t Type) String() string {
return string(t) return string(t)
} }

View File

@ -1,6 +1,8 @@
package keel package interfaces
import "context" import (
"context"
)
type closer struct { type closer struct {
handle func(context.Context) error handle func(context.Context) error
@ -12,10 +14,6 @@ func NewCloserFn(handle func(context.Context) error) closer {
} }
} }
func (h healther) Close(ctx context.Context) error {
return h.handle(ctx)
}
// Closer interface // Closer interface
type Closer interface { type Closer interface {
Close() Close()

1
interfaces/doc.go Normal file
View File

@ -0,0 +1 @@
package interfaces

View File

@ -1,4 +1,4 @@
package keel package interfaces
import "context" import "context"

View File

@ -1,4 +1,4 @@
package keel package interfaces
import "context" import "context"

View File

@ -1,4 +1,4 @@
package keel package interfaces
import "context" import "context"

View File

@ -1,4 +1,4 @@
package keel package interfaces
import "context" import "context"

11
log/fields_keel.go Normal file
View 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")
)

View File

@ -15,6 +15,8 @@ func FPeerService(name string) zap.Field {
} }
const ( const (
ServiceTypeKey = "service_type"
// ServiceNameKey represents the NameKey of the service. // ServiceNameKey represents the NameKey of the service.
ServiceNameKey = "service_name" ServiceNameKey = "service_name"
@ -35,6 +37,10 @@ const (
ServiceVersionKey = "service_version" ServiceVersionKey = "service_version"
) )
func FServiceType(name string) zap.Field {
return zap.String(ServiceTypeKey, name)
}
func FServiceName(name string) zap.Field { func FServiceName(name string) zap.Field {
return zap.String(ServiceNameKey, name) return zap.String(ServiceNameKey, name)
} }

View File

@ -7,6 +7,7 @@ import (
"net/http" "net/http"
"strings" "strings"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace"
"go.uber.org/zap" "go.uber.org/zap"
@ -20,6 +21,17 @@ func With(l *zap.Logger, fields ...zap.Field) *zap.Logger {
return l.With(fields...) 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 { func WithError(l *zap.Logger, err error) *zap.Logger {
return With(l, FErrorType(err), FError(err)) return With(l, FErrorType(err), FError(err))
} }

View File

@ -5,6 +5,7 @@ import (
"os" "os"
"time" "time"
"github.com/foomo/keel/service"
"github.com/spf13/viper" "github.com/spf13/viper"
"go.uber.org/zap" "go.uber.org/zap"
@ -72,9 +73,9 @@ func WithShutdownTimeout(shutdownTimeout time.Duration) Option {
func WithHTTPZapService(enabled bool) Option { func WithHTTPZapService(enabled bool) Option {
return func(inst *Server) { return func(inst *Server) {
if config.GetBool(inst.Config(), "service.zap.enabled", enabled)() { if config.GetBool(inst.Config(), "service.zap.enabled", enabled)() {
service := NewDefaultServiceHTTPZap() svs := service.NewDefaultHTTPZap()
inst.initServices = append(inst.initServices, service) inst.initServices = append(inst.initServices, svs)
inst.AddAlwaysHealthzers(service) inst.AddAlwaysHealthzers(svs)
} }
} }
} }
@ -83,9 +84,9 @@ func WithHTTPZapService(enabled bool) Option {
func WithHTTPViperService(enabled bool) Option { func WithHTTPViperService(enabled bool) Option {
return func(inst *Server) { return func(inst *Server) {
if config.GetBool(inst.Config(), "service.viper.enabled", enabled)() { if config.GetBool(inst.Config(), "service.viper.enabled", enabled)() {
service := NewDefaultServiceHTTPViper() svs := service.NewDefaultHTTPViper()
inst.initServices = append(inst.initServices, service) inst.initServices = append(inst.initServices, svs)
inst.AddAlwaysHealthzers(service) inst.AddAlwaysHealthzers(svs)
} }
} }
} }
@ -149,9 +150,9 @@ func WithPrometheusMeter(enabled bool) Option {
func WithHTTPPrometheusService(enabled bool) Option { func WithHTTPPrometheusService(enabled bool) Option {
return func(inst *Server) { return func(inst *Server) {
if config.GetBool(inst.Config(), "service.prometheus.enabled", enabled)() { if config.GetBool(inst.Config(), "service.prometheus.enabled", enabled)() {
service := NewDefaultServiceHTTPPrometheus() svs := service.NewDefaultHTTPPrometheus()
inst.initServices = append(inst.initServices, service) inst.initServices = append(inst.initServices, svs)
inst.AddAlwaysHealthzers(service) inst.AddAlwaysHealthzers(svs)
} }
} }
} }
@ -160,9 +161,9 @@ func WithHTTPPrometheusService(enabled bool) Option {
func WithHTTPPProfService(enabled bool) Option { func WithHTTPPProfService(enabled bool) Option {
return func(inst *Server) { return func(inst *Server) {
if config.GetBool(inst.Config(), "service.pprof.enabled", enabled)() { if config.GetBool(inst.Config(), "service.pprof.enabled", enabled)() {
service := NewDefaultServiceHTTPPProf() svs := service.NewDefaultHTTPPProf()
inst.initServices = append(inst.initServices, service) inst.initServices = append(inst.initServices, svs)
inst.AddAlwaysHealthzers(service) inst.AddAlwaysHealthzers(svs)
} }
} }
} }
@ -171,9 +172,9 @@ func WithHTTPPProfService(enabled bool) Option {
func WithHTTPHealthzService(enabled bool) Option { func WithHTTPHealthzService(enabled bool) Option {
return func(inst *Server) { return func(inst *Server) {
if config.GetBool(inst.Config(), "service.healthz.enabled", enabled)() { if config.GetBool(inst.Config(), "service.healthz.enabled", enabled)() {
service := NewDefaultServiceHTTPProbes(inst.probes) svs := service.NewDefaultHTTPProbes(inst.probes)
inst.initServices = append(inst.initServices, service) inst.initServices = append(inst.initServices, svs)
inst.AddAlwaysHealthzers(service) inst.AddAlwaysHealthzers(svs)
} }
} }
} }

View File

@ -1,3 +1,6 @@
//go:build !docs
// +build !docs
package keel package keel
import ( import (
@ -10,6 +13,8 @@ import (
"syscall" "syscall"
"time" "time"
"github.com/foomo/keel/healthz"
"github.com/foomo/keel/interfaces"
"github.com/go-logr/logr" "github.com/go-logr/logr"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/viper" "github.com/spf13/viper"
@ -40,7 +45,7 @@ type Server struct {
shutdownTimeout time.Duration shutdownTimeout time.Duration
running atomic.Bool running atomic.Bool
closers []interface{} closers []interface{}
probes map[HealthzType][]interface{} probes map[healthz.Type][]interface{}
ctx context.Context ctx context.Context
ctxCancel context.Context ctxCancel context.Context
ctxCancelFn context.CancelFunc ctxCancelFn context.CancelFunc
@ -54,7 +59,7 @@ func NewServer(opts ...Option) *Server {
inst := &Server{ inst := &Server{
shutdownTimeout: 30 * time.Second, shutdownTimeout: 30 * time.Second,
shutdownSignals: []os.Signal{os.Interrupt, syscall.SIGTERM}, shutdownSignals: []os.Signal{os.Interrupt, syscall.SIGTERM},
probes: map[HealthzType][]interface{}{}, probes: map[healthz.Type][]interface{}{},
ctx: context.Background(), ctx: context.Background(),
c: config.Config(), c: config.Config(),
l: log.Logger(), l: log.Logger(),
@ -83,51 +88,51 @@ func NewServer(opts ...Option) *Server {
for _, closer := range closers { for _, closer := range closers {
l := inst.l.With(log.FName(fmt.Sprintf("%T", closer))) l := inst.l.With(log.FName(fmt.Sprintf("%T", closer)))
switch c := closer.(type) { switch c := closer.(type) {
case Closer: case interfaces.Closer:
c.Close() c.Close()
case ErrorCloser: case interfaces.ErrorCloser:
if err := c.Close(); err != nil { if err := c.Close(); err != nil {
log.WithError(l, err).Error("failed to gracefully stop ErrorCloser") log.WithError(l, err).Error("failed to gracefully stop ErrorCloser")
} }
case CloserWithContext: case interfaces.CloserWithContext:
c.Close(timeoutCtx) c.Close(timeoutCtx)
case ErrorCloserWithContext: case interfaces.ErrorCloserWithContext:
if err := c.Close(timeoutCtx); err != nil { if err := c.Close(timeoutCtx); err != nil {
log.WithError(l, err).Error("failed to gracefully stop ErrorCloserWithContext") log.WithError(l, err).Error("failed to gracefully stop ErrorCloserWithContext")
} }
case Shutdowner: case interfaces.Shutdowner:
c.Shutdown() c.Shutdown()
case ErrorShutdowner: case interfaces.ErrorShutdowner:
if err := c.Shutdown(); err != nil { if err := c.Shutdown(); err != nil {
log.WithError(l, err).Error("failed to gracefully stop ErrorShutdowner") log.WithError(l, err).Error("failed to gracefully stop ErrorShutdowner")
} }
case ShutdownerWithContext: case interfaces.ShutdownerWithContext:
c.Shutdown(timeoutCtx) c.Shutdown(timeoutCtx)
case ErrorShutdownerWithContext: case interfaces.ErrorShutdownerWithContext:
if err := c.Shutdown(timeoutCtx); err != nil { if err := c.Shutdown(timeoutCtx); err != nil {
log.WithError(l, err).Error("failed to gracefully stop ErrorShutdownerWithContext") log.WithError(l, err).Error("failed to gracefully stop ErrorShutdownerWithContext")
} }
case Stopper: case interfaces.Stopper:
c.Stop() c.Stop()
case ErrorStopper: case interfaces.ErrorStopper:
if err := c.Stop(); err != nil { if err := c.Stop(); err != nil {
log.WithError(l, err).Error("failed to gracefully stop ErrorStopper") log.WithError(l, err).Error("failed to gracefully stop ErrorStopper")
} }
case StopperWithContext: case interfaces.StopperWithContext:
c.Stop(timeoutCtx) c.Stop(timeoutCtx)
case ErrorStopperWithContext: case interfaces.ErrorStopperWithContext:
if err := c.Stop(timeoutCtx); err != nil { if err := c.Stop(timeoutCtx); err != nil {
log.WithError(l, err).Error("failed to gracefully stop ErrorStopperWithContext") log.WithError(l, err).Error("failed to gracefully stop ErrorStopperWithContext")
} }
case Unsubscriber: case interfaces.Unsubscriber:
c.Unsubscribe() c.Unsubscribe()
case ErrorUnsubscriber: case interfaces.ErrorUnsubscriber:
if err := c.Unsubscribe(); err != nil { if err := c.Unsubscribe(); err != nil {
log.WithError(l, err).Error("failed to gracefully stop ErrorUnsubscriber") log.WithError(l, err).Error("failed to gracefully stop ErrorUnsubscriber")
} }
case UnsubscriberWithContext: case interfaces.UnsubscriberWithContext:
c.Unsubscribe(timeoutCtx) c.Unsubscribe(timeoutCtx)
case ErrorUnsubscriberWithContext: case interfaces.ErrorUnsubscriberWithContext:
if err := c.Unsubscribe(timeoutCtx); err != nil { if err := c.Unsubscribe(timeoutCtx); err != nil {
log.WithError(l, err).Error("failed to gracefully stop ErrorUnsubscriberWithContext") log.WithError(l, err).Error("failed to gracefully stop ErrorUnsubscriberWithContext")
} }
@ -229,22 +234,22 @@ func (s *Server) AddCloser(closer interface{}) {
} }
} }
switch closer.(type) { switch closer.(type) {
case Closer, case interfaces.Closer,
ErrorCloser, interfaces.ErrorCloser,
CloserWithContext, interfaces.CloserWithContext,
ErrorCloserWithContext, interfaces.ErrorCloserWithContext,
Shutdowner, interfaces.Shutdowner,
ErrorShutdowner, interfaces.ErrorShutdowner,
ShutdownerWithContext, interfaces.ShutdownerWithContext,
ErrorShutdownerWithContext, interfaces.ErrorShutdownerWithContext,
Stopper, interfaces.Stopper,
ErrorStopper, interfaces.ErrorStopper,
StopperWithContext, interfaces.StopperWithContext,
ErrorStopperWithContext, interfaces.ErrorStopperWithContext,
Unsubscriber, interfaces.Unsubscriber,
ErrorUnsubscriber, interfaces.ErrorUnsubscriber,
UnsubscriberWithContext, interfaces.UnsubscriberWithContext,
ErrorUnsubscriberWithContext: interfaces.ErrorUnsubscriberWithContext:
s.closers = append(s.closers, closer) s.closers = append(s.closers, closer)
default: default:
s.l.Warn("unable to add closer", log.FValue(fmt.Sprintf("%T", closer))) s.l.Warn("unable to add closer", log.FValue(fmt.Sprintf("%T", closer)))
@ -259,14 +264,14 @@ func (s *Server) AddClosers(closers ...interface{}) {
} }
// AddHealthzer adds a probe to be called on healthz checks // AddHealthzer adds a probe to be called on healthz checks
func (s *Server) AddHealthzer(typ HealthzType, probe interface{}) { func (s *Server) AddHealthzer(typ healthz.Type, probe interface{}) {
switch probe.(type) { switch probe.(type) {
case BoolHealthzer, case healthz.BoolHealthzer,
BoolHealthzerWithContext, healthz.BoolHealthzerWithContext,
ErrorHealthzer, healthz.ErrorHealthzer,
ErrorHealthzWithContext, healthz.ErrorHealthzWithContext,
ErrorPinger, interfaces.ErrorPinger,
ErrorPingerWithContext: interfaces.ErrorPingerWithContext:
s.probes[typ] = append(s.probes[typ], probe) s.probes[typ] = append(s.probes[typ], probe)
default: default:
s.l.Debug("not a healthz probe", log.FValue(fmt.Sprintf("%T", probe))) s.l.Debug("not a healthz probe", log.FValue(fmt.Sprintf("%T", probe)))
@ -274,7 +279,7 @@ func (s *Server) AddHealthzer(typ HealthzType, probe interface{}) {
} }
// AddHealthzers adds the given probes to be called on healthz checks // 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 { for _, probe := range probes {
s.AddHealthzer(typ, probe) s.AddHealthzer(typ, probe)
} }
@ -282,22 +287,22 @@ func (s *Server) AddHealthzers(typ HealthzType, probes ...interface{}) {
// AddAlwaysHealthzers adds the probes to be called on any healthz checks // AddAlwaysHealthzers adds the probes to be called on any healthz checks
func (s *Server) AddAlwaysHealthzers(probes ...interface{}) { 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 // AddStartupHealthzers adds the startup probes to be called on healthz checks
func (s *Server) AddStartupHealthzers(probes ...interface{}) { 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 // AddLivenessHealthzers adds the liveness probes to be called on healthz checks
func (s *Server) AddLivenessHealthzers(probes ...interface{}) { 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 // AddReadinessHealthzers adds the readiness probes to be called on healthz checks
func (s *Server) AddReadinessHealthzers(probes ...interface{}) { 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 // IsCanceled returns true if the internal errgroup has been canceled

435
server_docs.go Normal file
View File

@ -0,0 +1,435 @@
//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 ""
}

View File

@ -10,6 +10,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/foomo/keel/service"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
"go.uber.org/zap" "go.uber.org/zap"
@ -74,7 +75,7 @@ func (s *KeelTestSuite) TearDownSuite() {}
func (s *KeelTestSuite) TestServiceHTTP() { func (s *KeelTestSuite) TestServiceHTTP() {
s.svr.AddServices( s.svr.AddServices(
keel.NewServiceHTTP(s.l, "test", ":55000", s.mux), service.NewHTTP(s.l, "test", ":55000", s.mux),
) )
s.runServer() s.runServer()
@ -86,8 +87,8 @@ func (s *KeelTestSuite) TestServiceHTTP() {
func (s *KeelTestSuite) TestServiceHTTPZap() { func (s *KeelTestSuite) TestServiceHTTPZap() {
s.svr.AddServices( s.svr.AddServices(
keel.NewServiceHTTPZap(s.l, "zap", ":9100", "/log"), service.NewHTTPZap(s.l, "zap", ":9100", "/log"),
keel.NewServiceHTTP(s.l, "test", ":55000", s.mux), service.NewHTTP(s.l, "test", ":55000", s.mux),
) )
s.runServer() s.runServer()
@ -141,7 +142,7 @@ func (s *KeelTestSuite) TestServiceHTTPZap() {
func (s *KeelTestSuite) TestGraceful() { func (s *KeelTestSuite) TestGraceful() {
s.svr.AddServices( s.svr.AddServices(
keel.NewServiceHTTP(s.l, "test", ":55000", s.mux), service.NewHTTP(s.l, "test", ":55000", s.mux),
) )
s.runServer() s.runServer()

10
service/errors.go Normal file
View File

@ -0,0 +1,10 @@
package service
import (
"github.com/pkg/errors"
)
var (
ErrServiceNotRunning = errors.New("service not running")
ErrServiceShutdown = errors.New("service shutdown")
)

94
service/goroutine.go Normal file
View File

@ -0,0 +1,94 @@
package service
import (
"context"
"fmt"
"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
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) *GoRoutine {
if l == nil {
l = log.Logger()
}
// enrich the log
l = log.WithAttributes(l,
log.KeelServiceTypeKey.String("goroutine"),
log.KeelServiceNameKey.String(name),
)
return &GoRoutine{
handler: handler,
name: name,
parallel: 1,
l: l,
}
}
// ------------------------------------------------------------------------------------------------
// ~ 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.cancel = cancel
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)
})
}
return s.wg.Wait()
}
func (s *GoRoutine) Close(ctx context.Context) error {
s.l.Info("stopping keel service")
s.cancel(ErrServiceShutdown)
return s.wg.Wait()
}

47
service/goroutine_test.go Normal file
View File

@ -0,0 +1,47 @@
package service_test
import (
"context"
"time"
"github.com/foomo/keel"
"github.com/foomo/keel/service"
"github.com/pkg/errors"
"go.uber.org/zap"
)
func ExampleNewGoRoutine() {
shutdown(3 * time.Second)
svr := keel.NewServer(
keel.WithLogger(zap.NewExample()),
)
svr.AddService(
service.NewGoRoutine(svr.Logger(), "demo", func(ctx context.Context, l *zap.Logger) error {
for {
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)
}
}),
)
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":"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"}
}

14
service/helper_test.go Normal file
View File

@ -0,0 +1,14 @@
package service_test
import (
"syscall"
"time"
)
// shutdown example after the given time
func shutdown(duration time.Duration) {
go func() {
time.Sleep(duration)
_ = syscall.Kill(syscall.Getpid(), syscall.SIGINT)
}()
}

View File

@ -1,7 +1,8 @@
package keel package service
import ( import (
"context" "context"
"fmt"
"net" "net"
"net/http" "net/http"
"strings" "strings"
@ -14,22 +15,25 @@ import (
"github.com/foomo/keel/net/http/middleware" "github.com/foomo/keel/net/http/middleware"
) )
// ServiceHTTP struct // HTTP struct
type ServiceHTTP struct { type HTTP struct {
running atomic.Bool running atomic.Bool
server *http.Server server *http.Server
name string name string
l *zap.Logger 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 { if l == nil {
l = log.Logger() l = log.Logger()
} }
// enrich the log // 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{ server: &http.Server{
Addr: addr, Addr: addr,
ErrorLog: zap.NewStdLog(l), 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 return s.name
} }
func (s *ServiceHTTP) Healthz() error { func (s *HTTP) Healthz() error {
if !s.running.Load() { if !s.running.Load() {
return ErrServiceNotRunning return ErrServiceNotRunning
} }
return nil return nil
} }
func (s *ServiceHTTP) Start(ctx context.Context) error { func (s *HTTP) String() string {
return fmt.Sprintf("address: `%s`", s.server.Addr)
}
func (s *HTTP) Start(ctx context.Context) error {
var fields []zap.Field var fields []zap.Field
if value := strings.Split(s.server.Addr, ":"); len(value) == 2 { if value := strings.Split(s.server.Addr, ":"); len(value) == 2 {
ip, port := value[0], value[1] 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)) 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.BaseContext = func(_ net.Listener) context.Context { return ctx }
s.server.RegisterOnShutdown(func() { s.server.RegisterOnShutdown(func() {
s.running.Store(false) s.running.Store(false)
}) })
s.running.Store(true) s.running.Store(true)
if err := s.server.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) { if err := s.server.ListenAndServe(); errors.Is(err, http.ErrServerClosed) {
log.WithError(s.l, err).Error("service error") return nil
return err } else if err != nil {
return errors.Wrap(err, "failed to start service")
} }
return nil return nil
} }
func (s *ServiceHTTP) Close(ctx context.Context) error { func (s *HTTP) Close(ctx context.Context) error {
s.l.Info("stopping http service") s.l.Info("stopping keel service")
return s.server.Shutdown(ctx) if err := s.server.Shutdown(ctx); err != nil {
return errors.Wrap(err, "failed to stop service")
}
return nil
} }

39
service/http_test.go Normal file
View File

@ -0,0 +1,39 @@
package service_test
import (
"net/http"
"time"
"github.com/foomo/keel"
"github.com/foomo/keel/service"
"go.uber.org/zap"
)
func ExampleNewHTTP() {
shutdown(3 * time.Second)
svr := keel.NewServer(
keel.WithLogger(zap.NewExample()),
)
l := svr.Logger()
// 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(
service.NewHTTP(l, "demo", "localhost:8080", svs),
)
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":"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"}
}

View File

@ -1,19 +1,21 @@
package keel package service
import ( import (
"context" "context"
"errors" "errors"
"net/http" "net/http"
"github.com/foomo/keel/healthz"
"github.com/foomo/keel/interfaces"
"go.uber.org/zap" "go.uber.org/zap"
"github.com/foomo/keel/log" "github.com/foomo/keel/log"
) )
const ( const (
DefaultServiceHTTPHealthzName = "healthz" DefaultHTTPHealthzName = "healthz"
DefaultServiceHTTPHealthzAddr = ":9400" DefaultHTTPHealthzAddr = ":9400"
DefaultServiceHTTPHealthzPath = "/healthz" DefaultHTTPHealthzPath = "/healthz"
) )
var ( var (
@ -24,7 +26,7 @@ var (
ErrStartupProbeFailed = errors.New("startup probe failed") 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() handler := http.NewServeMux()
unavailable := func(l *zap.Logger, w http.ResponseWriter, r *http.Request, err error) { 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) { call := func(ctx context.Context, probe interface{}) (bool, error) {
switch h := probe.(type) { switch h := probe.(type) {
case BoolHealthzer: case healthz.BoolHealthzer:
return h.Healthz(), nil return h.Healthz(), nil
case BoolHealthzerWithContext: case healthz.BoolHealthzerWithContext:
return h.Healthz(ctx), nil return h.Healthz(ctx), nil
case ErrorHealthzer: case healthz.ErrorHealthzer:
return true, h.Healthz() return true, h.Healthz()
case ErrorHealthzWithContext: case healthz.ErrorHealthzWithContext:
return true, h.Healthz(ctx) return true, h.Healthz(ctx)
case ErrorPinger: case interfaces.ErrorPinger:
return true, h.Ping() return true, h.Ping()
case ErrorPingerWithContext: case interfaces.ErrorPingerWithContext:
return true, h.Ping(ctx) return true, h.Ping(ctx)
default: default:
return false, ErrUnhandledHealthzProbe 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) { handler.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
for typ, values := range probes { for typ, values := range probes {
if typ == HealthzTypeStartup { if typ == healthz.TypeStartup {
continue continue
} }
for _, p := range values { for _, p := range values {
@ -72,12 +74,12 @@ func NewServiceHTTPHealthz(l *zap.Logger, name, addr, path string, probes map[He
_, _ = w.Write([]byte("OK")) _, _ = 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{} var ps []interface{}
if p, ok := probes[HealthzTypeAlways]; ok { if p, ok := probes[healthz.TypeAlways]; ok {
ps = append(ps, p...) ps = append(ps, p...)
} }
if p, ok := probes[HealthzTypeLiveness]; ok { if p, ok := probes[healthz.TypeLiveness]; ok {
ps = append(ps, p...) ps = append(ps, p...)
} }
for _, p := range ps { for _, p := range ps {
@ -93,12 +95,12 @@ func NewServiceHTTPHealthz(l *zap.Logger, name, addr, path string, probes map[He
_, _ = w.Write([]byte("OK")) _, _ = 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{} var ps []interface{}
if p, ok := probes[HealthzTypeAlways]; ok { if p, ok := probes[healthz.TypeAlways]; ok {
ps = append(ps, p...) ps = append(ps, p...)
} }
if p, ok := probes[HealthzTypeReadiness]; ok { if p, ok := probes[healthz.TypeReadiness]; ok {
ps = append(ps, p...) ps = append(ps, p...)
} }
for _, p := range ps { for _, p := range ps {
@ -114,12 +116,12 @@ func NewServiceHTTPHealthz(l *zap.Logger, name, addr, path string, probes map[He
_, _ = w.Write([]byte("OK")) _, _ = 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{} var ps []interface{}
if p, ok := probes[HealthzTypeAlways]; ok { if p, ok := probes[healthz.TypeAlways]; ok {
ps = append(ps, p...) ps = append(ps, p...)
} }
if p, ok := probes[HealthzTypeStartup]; ok { if p, ok := probes[healthz.TypeStartup]; ok {
ps = append(ps, p...) ps = append(ps, p...)
} }
for _, p := range ps { 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.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("OK")) _, _ = w.Write([]byte("OK"))
}) })
return NewServiceHTTP(l, name, addr, handler) return NewHTTP(l, name, addr, handler)
} }
func NewDefaultServiceHTTPProbes(probes map[HealthzType][]interface{}) *ServiceHTTP { func NewDefaultHTTPProbes(probes map[healthz.Type][]interface{}) *HTTP {
return NewServiceHTTPHealthz( return NewHealthz(
log.Logger(), log.Logger(),
DefaultServiceHTTPHealthzName, DefaultHTTPHealthzName,
DefaultServiceHTTPHealthzAddr, DefaultHTTPHealthzAddr,
DefaultServiceHTTPHealthzPath, DefaultHTTPHealthzPath,
probes, probes,
) )
} }

View File

@ -1,6 +1,6 @@
//go:build !pprof //go:build !pprof
package keel package service
import ( import (
"net/http" "net/http"
@ -10,12 +10,12 @@ import (
) )
const ( const (
DefaultServiceHTTPPProfName = "pprof" DefaultHTTPPProfName = "pprof"
DefaultServiceHTTPPProfAddr = "localhost:6060" DefaultHTTPPProfAddr = "localhost:6060"
DefaultServiceHTTPPProfPath = "/debug/pprof" 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) { route := func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotImplemented) w.WriteHeader(http.StatusNotImplemented)
_, _ = w.Write([]byte("To enable pprof, you need to build your binary with the `-tags=pprof` flag")) _, _ = w.Write([]byte("To enable pprof, you need to build your binary with the `-tags=pprof` flag"))
@ -26,14 +26,14 @@ func NewServiceHTTPPProf(l *zap.Logger, name, addr, path string) *ServiceHTTP {
handler.HandleFunc(path+"/profile", route) handler.HandleFunc(path+"/profile", route)
handler.HandleFunc(path+"/symbol", route) handler.HandleFunc(path+"/symbol", route)
handler.HandleFunc(path+"/trace", route) handler.HandleFunc(path+"/trace", route)
return NewServiceHTTP(l, name, addr, handler) return NewHTTP(l, name, addr, handler)
} }
func NewDefaultServiceHTTPPProf() *ServiceHTTP { func NewDefaultHTTPPProf() *HTTP {
return NewServiceHTTPPProf( return NewHTTPPProf(
log.Logger(), log.Logger(),
DefaultServiceHTTPPProfName, DefaultHTTPPProfName,
DefaultServiceHTTPPProfAddr, DefaultHTTPPProfAddr,
DefaultServiceHTTPPProfPath, DefaultHTTPPProfPath,
) )
} }

View File

@ -1,7 +1,7 @@
//go:build pprof //go:build pprof
// +build pprof // +build pprof
package keel package service
import ( import (
"net/http" "net/http"
@ -12,12 +12,12 @@ import (
) )
const ( const (
DefaultServiceHTTPPProfName = "pprof" DefaultHTTPPProfName = "pprof"
DefaultServiceHTTPPProfAddr = "localhost:6060" DefaultHTTPPProfAddr = "localhost:6060"
DefaultServiceHTTPPProfPath = "/debug/pprof" 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 := http.NewServeMux()
handler.HandleFunc(path+"/", pprof.Index) handler.HandleFunc(path+"/", pprof.Index)
handler.HandleFunc(path+"/cmdline", pprof.Cmdline) 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) return NewServiceHTTP(l, name, addr, handler)
} }
func NewDefaultServiceHTTPPProf() *ServiceHTTP { func NewDefaultHTTPPProf() *ServiceHTTP {
return NewServiceHTTPPProf( return NewHTTPPProf(
log.Logger(), log.Logger(),
DefaultServiceHTTPPProfName, DefaultHTTPPProfName,
DefaultServiceHTTPPProfAddr, DefaultHTTPPProfAddr,
DefaultServiceHTTPPProfPath, DefaultHTTPPProfPath,
) )
} }

37
service/httpprometheus.go Normal file
View File

@ -0,0 +1,37 @@
package service
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 (
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() *HTTP {
return NewHTTPPrometheus(
log.Logger(),
DefaultHTTPPrometheusName,
DefaultHTTPPrometheusAddr,
DefaultHTTPPrometheusPath,
)
}

View File

@ -1,4 +1,4 @@
package keel package service
import ( import (
"encoding/json" "encoding/json"
@ -12,12 +12,12 @@ import (
) )
const ( const (
DefaultServiceHTTPViperName = "viper" DefaultHTTPViperName = "viper"
DefaultServiceHTTPViperAddr = "localhost:9300" DefaultHTTPViperAddr = "localhost:9300"
DefaultServiceHTTPViperPath = "/config" 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 := http.NewServeMux()
handler.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) { handler.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
type payload struct { type payload struct {
@ -44,15 +44,15 @@ func NewServiceHTTPViper(l *zap.Logger, c *viper.Viper, name, addr, path string)
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
} }
}) })
return NewServiceHTTP(l, name, addr, handler) return NewHTTP(l, name, addr, handler)
} }
func NewDefaultServiceHTTPViper() *ServiceHTTP { func NewDefaultHTTPViper() *HTTP {
return NewServiceHTTPViper( return NewHTTPViper(
log.Logger(), log.Logger(),
config.Config(), config.Config(),
DefaultServiceHTTPViperName, DefaultHTTPViperName,
DefaultServiceHTTPViperAddr, DefaultHTTPViperAddr,
DefaultServiceHTTPViperPath, DefaultHTTPViperPath,
) )
} }

View File

@ -1,4 +1,4 @@
package keel package service
import ( import (
"encoding/json" "encoding/json"
@ -12,12 +12,12 @@ import (
) )
const ( const (
DefaultServiceHTTPZapName = "zap" DefaultHTTPZapName = "zap"
DefaultServiceHTTPZapAddr = "localhost:9100" DefaultHTTPZapAddr = "localhost:9100"
DefaultServiceHTTPZapPath = "/log" 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 := http.NewServeMux()
handler.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) { handler.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
type errorResponse struct { 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 { func NewDefaultHTTPZap() *HTTP {
return NewServiceHTTPZap( return NewHTTPZap(
log.Logger(), log.Logger(),
DefaultServiceHTTPZapName, DefaultHTTPZapName,
DefaultServiceHTTPZapAddr, DefaultHTTPZapAddr,
DefaultServiceHTTPZapPath, DefaultHTTPZapPath,
) )
} }

View File

@ -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,
)
}

View File

@ -0,0 +1,162 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package nonrecording
import (
"context"
"strings"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/metric/instrument"
"go.opentelemetry.io/otel/metric/instrument/asyncfloat64"
"go.opentelemetry.io/otel/metric/instrument/asyncint64"
"go.opentelemetry.io/otel/metric/instrument/syncfloat64"
"go.opentelemetry.io/otel/metric/instrument/syncint64"
)
type Metric struct {
Name string
Type string
Help string
}
var metrics []Metric
func Metrics() []Metric {
return metrics
}
type nonrecordingAsyncFloat64Instrument struct {
instrument.Asynchronous
}
var (
_ asyncfloat64.InstrumentProvider = nonrecordingAsyncFloat64Instrument{}
_ asyncfloat64.Counter = nonrecordingAsyncFloat64Instrument{}
_ asyncfloat64.UpDownCounter = nonrecordingAsyncFloat64Instrument{}
_ asyncfloat64.Gauge = nonrecordingAsyncFloat64Instrument{}
)
func (n nonrecordingAsyncFloat64Instrument) Counter(name string, opts ...instrument.Option) (asyncfloat64.Counter, error) {
metrics = append(metrics, Metric{Name: strings.ReplaceAll(name, ".", "_"), Type: "COUNTER", Help: instrument.NewConfig(opts...).Description()})
return n, nil
}
func (n nonrecordingAsyncFloat64Instrument) UpDownCounter(name string, opts ...instrument.Option) (asyncfloat64.UpDownCounter, error) {
metrics = append(metrics, Metric{Name: strings.ReplaceAll(name, ".", "_"), Type: "UPDOWNCOUNTER", Help: instrument.NewConfig(opts...).Description()})
return n, nil
}
func (n nonrecordingAsyncFloat64Instrument) Gauge(name string, opts ...instrument.Option) (asyncfloat64.Gauge, error) {
metrics = append(metrics, Metric{Name: strings.ReplaceAll(name, ".", "_"), Type: "GAUGE", Help: instrument.NewConfig(opts...).Description()})
return n, nil
}
func (nonrecordingAsyncFloat64Instrument) Observe(context.Context, float64, ...attribute.KeyValue) {
}
type nonrecordingAsyncInt64Instrument struct {
instrument.Asynchronous
}
var (
_ asyncint64.InstrumentProvider = nonrecordingAsyncInt64Instrument{}
_ asyncint64.Counter = nonrecordingAsyncInt64Instrument{}
_ asyncint64.UpDownCounter = nonrecordingAsyncInt64Instrument{}
_ asyncint64.Gauge = nonrecordingAsyncInt64Instrument{}
)
func (n nonrecordingAsyncInt64Instrument) Counter(name string, opts ...instrument.Option) (asyncint64.Counter, error) {
metrics = append(metrics, Metric{Name: strings.ReplaceAll(name, ".", "_"), Type: "COUNTER", Help: instrument.NewConfig(opts...).Description()})
return n, nil
}
func (n nonrecordingAsyncInt64Instrument) UpDownCounter(name string, opts ...instrument.Option) (asyncint64.UpDownCounter, error) {
metrics = append(metrics, Metric{Name: strings.ReplaceAll(name, ".", "_"), Type: "UPDOWNCOUNTER", Help: instrument.NewConfig(opts...).Description()})
return n, nil
}
func (n nonrecordingAsyncInt64Instrument) Gauge(name string, opts ...instrument.Option) (asyncint64.Gauge, error) {
metrics = append(metrics, Metric{Name: strings.ReplaceAll(name, ".", "_"), Type: "GAUGE", Help: instrument.NewConfig(opts...).Description()})
return n, nil
}
func (nonrecordingAsyncInt64Instrument) Observe(context.Context, int64, ...attribute.KeyValue) {
}
type nonrecordingSyncFloat64Instrument struct {
instrument.Synchronous
}
var (
_ syncfloat64.InstrumentProvider = nonrecordingSyncFloat64Instrument{}
_ syncfloat64.Counter = nonrecordingSyncFloat64Instrument{}
_ syncfloat64.UpDownCounter = nonrecordingSyncFloat64Instrument{}
_ syncfloat64.Histogram = nonrecordingSyncFloat64Instrument{}
)
func (n nonrecordingSyncFloat64Instrument) Counter(name string, opts ...instrument.Option) (syncfloat64.Counter, error) {
metrics = append(metrics, Metric{Name: strings.ReplaceAll(name, ".", "_"), Type: "COUNTER", Help: instrument.NewConfig(opts...).Description()})
return n, nil
}
func (n nonrecordingSyncFloat64Instrument) UpDownCounter(name string, opts ...instrument.Option) (syncfloat64.UpDownCounter, error) {
metrics = append(metrics, Metric{Name: strings.ReplaceAll(name, ".", "_"), Type: "UPDOWNCOUNTER", Help: instrument.NewConfig(opts...).Description()})
return n, nil
}
func (n nonrecordingSyncFloat64Instrument) Histogram(name string, opts ...instrument.Option) (syncfloat64.Histogram, error) {
metrics = append(metrics, Metric{Name: strings.ReplaceAll(name, ".", "_"), Type: "HISTOGRAM", Help: instrument.NewConfig(opts...).Description()})
return n, nil
}
func (nonrecordingSyncFloat64Instrument) Add(context.Context, float64, ...attribute.KeyValue) {
}
func (nonrecordingSyncFloat64Instrument) Record(context.Context, float64, ...attribute.KeyValue) {
}
type nonrecordingSyncInt64Instrument struct {
instrument.Synchronous
}
var (
_ syncint64.InstrumentProvider = nonrecordingSyncInt64Instrument{}
_ syncint64.Counter = nonrecordingSyncInt64Instrument{}
_ syncint64.UpDownCounter = nonrecordingSyncInt64Instrument{}
_ syncint64.Histogram = nonrecordingSyncInt64Instrument{}
)
func (n nonrecordingSyncInt64Instrument) Counter(name string, opts ...instrument.Option) (syncint64.Counter, error) {
metrics = append(metrics, Metric{Name: strings.ReplaceAll(name, ".", "_"), Type: "COUNTER", Help: instrument.NewConfig(opts...).Description()})
return n, nil
}
func (n nonrecordingSyncInt64Instrument) UpDownCounter(name string, opts ...instrument.Option) (syncint64.UpDownCounter, error) {
metrics = append(metrics, Metric{Name: strings.ReplaceAll(name, ".", "_"), Type: "UPDOWNCOUNTER", Help: instrument.NewConfig(opts...).Description()})
return n, nil
}
func (n nonrecordingSyncInt64Instrument) Histogram(name string, opts ...instrument.Option) (syncint64.Histogram, error) {
metrics = append(metrics, Metric{Name: strings.ReplaceAll(name, ".", "_"), Type: "HISTOGRAM", Help: instrument.NewConfig(opts...).Description()})
return n, nil
}
func (nonrecordingSyncInt64Instrument) Add(context.Context, int64, ...attribute.KeyValue) {
}
func (nonrecordingSyncInt64Instrument) Record(context.Context, int64, ...attribute.KeyValue) {
}

View File

@ -0,0 +1,50 @@
package nonrecording
import (
"context"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/metric/instrument"
"go.opentelemetry.io/otel/metric/instrument/asyncfloat64"
"go.opentelemetry.io/otel/metric/instrument/asyncint64"
"go.opentelemetry.io/otel/metric/instrument/syncfloat64"
"go.opentelemetry.io/otel/metric/instrument/syncint64"
)
// NewNoopMeterProvider creates a MeterProvider that does not record any metrics.
func NewNoopMeterProvider() metric.MeterProvider {
return noopMeterProvider{}
}
type noopMeterProvider struct{}
var _ metric.MeterProvider = noopMeterProvider{}
func (noopMeterProvider) Meter(instrumentationName string, opts ...metric.MeterOption) metric.Meter {
return noopMeter{}
}
// NewNoopMeter creates a Meter that does not record any metrics.
func NewNoopMeter() metric.Meter {
return noopMeter{}
}
type noopMeter struct{}
var _ metric.Meter = noopMeter{}
func (noopMeter) AsyncInt64() asyncint64.InstrumentProvider {
return nonrecordingAsyncInt64Instrument{}
}
func (noopMeter) AsyncFloat64() asyncfloat64.InstrumentProvider {
return nonrecordingAsyncFloat64Instrument{}
}
func (noopMeter) SyncInt64() syncint64.InstrumentProvider {
return nonrecordingSyncInt64Instrument{}
}
func (noopMeter) SyncFloat64() syncfloat64.InstrumentProvider {
return nonrecordingSyncFloat64Instrument{}
}
func (noopMeter) RegisterCallback([]instrument.Asynchronous, func(context.Context)) error {
return nil
}