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"
"github.com/foomo/keel"
"github.com/foomo/keel/service"
)
func main() {
@ -39,7 +40,7 @@ func main() {
svs := newService()
svr.AddService(
keel.NewServiceHTTP(l, "demo", ":8080", svs),
service.NewHTTP(l, "demo", "localhost:8080", svs),
)
svr.Run()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -6,6 +6,7 @@ import (
"github.com/foomo/keel"
"github.com/foomo/keel/config"
"github.com/foomo/keel/service"
)
func main() {
@ -42,7 +43,7 @@ func main() {
// curl localhost:8080
svr.AddService(
keel.NewServiceHTTP(l, "demo", "localhost:8080", http.HandlerFunc(
service.NewHTTP(l, "demo", "localhost:8080", http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
fmt.Println("current foo:", fooFn()) //nolint:forbidigo
}),

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,6 @@
package main
import (
"net/http"
"os"
"github.com/foomo/keel"
@ -29,24 +28,11 @@ func main() {
keel.WithHTTPPProfService(false),
)
l := svr.Logger()
// alternatively you can add them manually
// svr.AddServices(keel.NewDefaultServiceHTTPZap())
// svr.AddServices(keel.NewDefaultServiceHTTPViper())
// svr.AddServices(keel.NewDefaultServiceHTTPPProf())
// svr.AddServices(keel.NewDefaultServiceHTTPPrometheus())
// create demo service
svs := http.NewServeMux()
svs.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("OK"))
})
svr.AddService(
keel.NewServiceHTTP(l, "demo", ":8080", svs),
)
svr.Run()
}

View File

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

View File

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

View File

@ -4,6 +4,9 @@ import (
"math/rand"
"net/http"
"github.com/foomo/keel/service"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/metric/instrument"
@ -58,6 +61,14 @@ func main() {
})
}
promauto.NewCounter(prometheus.CounterOpts{
Namespace: "foo",
Subsystem: "",
Name: "bar",
Help: "blubb",
ConstLabels: nil,
})
{ // up down
upDown, err := meter.SyncInt64().UpDownCounter(
"a.updown",
@ -92,7 +103,7 @@ func main() {
}
svr.AddService(
keel.NewServiceHTTP(l, "demo", "localhost:8080", svs,
service.NewHTTP(l, "demo", "localhost:8080", svs,
middleware.Telemetry(),
middleware.Recover(),
),

1
go.mod
View File

@ -5,6 +5,7 @@ go 1.20
require (
github.com/avast/retry-go v3.0.0+incompatible
github.com/davecgh/go-spew v1.1.1
github.com/fbiville/markdown-table-formatter v0.3.0
github.com/foomo/gotsrpc/v2 v2.7.2
github.com/go-logr/logr v1.2.4
github.com/golang-jwt/jwt v3.2.2+incompatible

2
go.sum
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.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/fbiville/markdown-table-formatter v0.3.0 h1:PIm1UNgJrFs8q1htGTw+wnnNYvwXQMMMIKNZop2SSho=
github.com/fbiville/markdown-table-formatter v0.3.0/go.mod h1:q89TDtSEVDdTaufgSbfHpNVdPU/bmfvqNkrC5HagmLY=
github.com/felixge/httpsnoop v1.0.2 h1:+nS9g82KMXccJ/wp0zyRW9ZBHFETmMGtkk+2CTTrW4o=
github.com/felixge/httpsnoop v1.0.2/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/foomo/gotsrpc/v2 v2.7.2 h1:a94V/a8LSssq+aRN3Fv1lJPjWoyMilOvRq+yEaDTHVM=

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"
@ -16,6 +16,10 @@ func (h healther) Healthz(ctx context.Context) error {
return h.handle(ctx)
}
func (h healther) Close(ctx context.Context) error {
return h.handle(ctx)
}
// BoolHealthzer interface
type BoolHealthzer interface {
Healthz() bool

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/
type HealthzType string
type Type string
const (
// HealthzTypeAlways will run on any checks
HealthzTypeAlways HealthzType = "always"
// HealthzTypeStartup will run on /healthz/startup checks
// TypeAlways will run on any checks
TypeAlways Type = "always"
// TypeStartup will run on /healthz/startup checks
// > The kubelet uses startup probes to know when a container application has started. If such a probe is configured,
// > it disables liveness and readiness checks until it succeeds, making sure those probes don't interfere with the
// > application startup. This can be used to adopt liveness checks on slow starting containers, avoiding them getting
// > killed by the kubelet before they are up and running.
HealthzTypeStartup HealthzType = "startup"
// HealthzTypeReadiness will run on /healthz/readiness checks
TypeStartup Type = "startup"
// TypeReadiness will run on /healthz/readiness checks
// > The kubelet uses readiness probes to know when a container is ready to start accepting traffic.
// > A Pod is considered ready when all of its containers are ready. One use of this signal is to control
// > which Pods are used as backends for Services. When a Pod is not ready, it is removed from Service load balancers.
HealthzTypeReadiness HealthzType = "readiness"
// HealthzTypeLiveness will run on /healthz/liveness checks
TypeReadiness Type = "readiness"
// TypeLiveness will run on /healthz/liveness checks
// > The kubelet uses liveness probes to know when to restart a container. For example, liveness probes could catch
// > a deadlock, where an application is running, but unable to make progress. Restarting a container in such a state
// > can help to make the application more available despite bugs.
HealthzTypeLiveness HealthzType = "liveness"
TypeLiveness Type = "liveness"
)
// String interface
func (t HealthzType) String() string {
func (t Type) String() string {
return string(t)
}

View File

@ -1,6 +1,8 @@
package keel
package interfaces
import "context"
import (
"context"
)
type closer struct {
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
type Closer interface {
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"

View File

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

View File

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

View File

@ -1,4 +1,4 @@
package keel
package interfaces
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 (
ServiceTypeKey = "service_type"
// ServiceNameKey represents the NameKey of the service.
ServiceNameKey = "service_name"
@ -35,6 +37,10 @@ const (
ServiceVersionKey = "service_version"
)
func FServiceType(name string) zap.Field {
return zap.String(ServiceTypeKey, name)
}
func FServiceName(name string) zap.Field {
return zap.String(ServiceNameKey, name)
}

View File

@ -7,6 +7,7 @@ import (
"net/http"
"strings"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
"go.uber.org/zap"
@ -20,6 +21,17 @@ func With(l *zap.Logger, fields ...zap.Field) *zap.Logger {
return l.With(fields...)
}
func WithAttributes(l *zap.Logger, attrs ...attribute.KeyValue) *zap.Logger {
if l == nil {
l = Logger()
}
fields := make([]zap.Field, len(attrs))
for i, attr := range attrs {
fields[i] = zap.Any(strings.ReplaceAll(string(attr.Key), ".", "_"), attr.Value.AsInterface())
}
return l.With(fields...)
}
func WithError(l *zap.Logger, err error) *zap.Logger {
return With(l, FErrorType(err), FError(err))
}

View File

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

View File

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

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

10
service/errors.go Normal file
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 (
"context"
"fmt"
"net"
"net/http"
"strings"
@ -14,22 +15,25 @@ import (
"github.com/foomo/keel/net/http/middleware"
)
// ServiceHTTP struct
type ServiceHTTP struct {
// HTTP struct
type HTTP struct {
running atomic.Bool
server *http.Server
name string
l *zap.Logger
}
func NewServiceHTTP(l *zap.Logger, name, addr string, handler http.Handler, middlewares ...middleware.Middleware) *ServiceHTTP {
func NewHTTP(l *zap.Logger, name, addr string, handler http.Handler, middlewares ...middleware.Middleware) *HTTP {
if l == nil {
l = log.Logger()
}
// enrich the log
l = log.WithHTTPServerName(l, name)
l = log.WithAttributes(l,
log.KeelServiceTypeKey.String("http"),
log.KeelServiceNameKey.String(name),
)
return &ServiceHTTP{
return &HTTP{
server: &http.Server{
Addr: addr,
ErrorLog: zap.NewStdLog(l),
@ -40,18 +44,22 @@ func NewServiceHTTP(l *zap.Logger, name, addr string, handler http.Handler, midd
}
}
func (s *ServiceHTTP) Name() string {
func (s *HTTP) Name() string {
return s.name
}
func (s *ServiceHTTP) Healthz() error {
func (s *HTTP) Healthz() error {
if !s.running.Load() {
return ErrServiceNotRunning
}
return nil
}
func (s *ServiceHTTP) Start(ctx context.Context) error {
func (s *HTTP) String() string {
return fmt.Sprintf("address: `%s`", s.server.Addr)
}
func (s *HTTP) Start(ctx context.Context) error {
var fields []zap.Field
if value := strings.Split(s.server.Addr, ":"); len(value) == 2 {
ip, port := value[0], value[1]
@ -60,20 +68,24 @@ func (s *ServiceHTTP) Start(ctx context.Context) error {
}
fields = append(fields, log.FNetHostIP(ip), log.FNetHostPort(port))
}
s.l.Info("starting http service", fields...)
s.l.Info("starting keel service", fields...)
s.server.BaseContext = func(_ net.Listener) context.Context { return ctx }
s.server.RegisterOnShutdown(func() {
s.running.Store(false)
})
s.running.Store(true)
if err := s.server.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
log.WithError(s.l, err).Error("service error")
return err
if err := s.server.ListenAndServe(); errors.Is(err, http.ErrServerClosed) {
return nil
} else if err != nil {
return errors.Wrap(err, "failed to start service")
}
return nil
}
func (s *ServiceHTTP) Close(ctx context.Context) error {
s.l.Info("stopping http service")
return s.server.Shutdown(ctx)
func (s *HTTP) Close(ctx context.Context) error {
s.l.Info("stopping keel service")
if err := s.server.Shutdown(ctx); err != nil {
return errors.Wrap(err, "failed to stop service")
}
return nil
}

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

View File

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

View File

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

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

View File

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