mirror of
https://github.com/TecharoHQ/anubis.git
synced 2026-04-15 21:04:56 +00:00
feat(osiris): serve metrics and healthz
Signed-off-by: Xe Iaso <me@xeiaso.net>
This commit is contained in:
@@ -1,13 +1,20 @@
|
|||||||
package entrypoint
|
package entrypoint
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
"github.com/TecharoHQ/anubis/cmd/osiris/internal/config"
|
"github.com/TecharoHQ/anubis/cmd/osiris/internal/config"
|
||||||
"github.com/TecharoHQ/anubis/internal"
|
"github.com/TecharoHQ/anubis/internal"
|
||||||
"github.com/hashicorp/hcl/v2/hclsimple"
|
"github.com/hashicorp/hcl/v2/hclsimple"
|
||||||
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
healthv1 "google.golang.org/grpc/health/grpc_health_v1"
|
healthv1 "google.golang.org/grpc/health/grpc_health_v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -18,6 +25,9 @@ type Options struct {
|
|||||||
func Main(opts Options) error {
|
func Main(opts Options) error {
|
||||||
internal.SetHealth("osiris", healthv1.HealthCheckResponse_NOT_SERVING)
|
internal.SetHealth("osiris", healthv1.HealthCheckResponse_NOT_SERVING)
|
||||||
|
|
||||||
|
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
var cfg config.Toplevel
|
var cfg config.Toplevel
|
||||||
if err := hclsimple.DecodeFile(opts.ConfigFname, nil, &cfg); err != nil {
|
if err := hclsimple.DecodeFile(opts.ConfigFname, nil, &cfg); err != nil {
|
||||||
return fmt.Errorf("can't read configuration file %s:\n\n%w", opts.ConfigFname, err)
|
return fmt.Errorf("can't read configuration file %s:\n\n%w", opts.ConfigFname, err)
|
||||||
@@ -32,6 +42,71 @@ func Main(opts Options) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
slog.Info("listening on", "http", cfg.Bind.HTTP)
|
g, gCtx := errgroup.WithContext(ctx)
|
||||||
return http.ListenAndServe(cfg.Bind.HTTP, rtr)
|
|
||||||
|
// HTTP
|
||||||
|
g.Go(func() error {
|
||||||
|
ln, err := net.Listen("tcp", cfg.Bind.HTTP)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("(HTTP) can't bind to tcp %s: %w", cfg.Bind.HTTP, err)
|
||||||
|
}
|
||||||
|
defer ln.Close()
|
||||||
|
|
||||||
|
go func(ctx context.Context) {
|
||||||
|
<-ctx.Done()
|
||||||
|
ln.Close()
|
||||||
|
}(gCtx)
|
||||||
|
|
||||||
|
slog.Info("listening for HTTP", "bind", cfg.Bind.HTTP)
|
||||||
|
|
||||||
|
srv := http.Server{Handler: rtr, ErrorLog: internal.GetFilteredHTTPLogger()}
|
||||||
|
|
||||||
|
return srv.Serve(ln)
|
||||||
|
})
|
||||||
|
|
||||||
|
// HTTPS
|
||||||
|
|
||||||
|
// Metrics
|
||||||
|
g.Go(func() error {
|
||||||
|
ln, err := net.Listen("tcp", cfg.Bind.Metrics)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("(metrics) can't bind to tcp %s: %w", cfg.Bind.Metrics, err)
|
||||||
|
}
|
||||||
|
defer ln.Close()
|
||||||
|
|
||||||
|
go func(ctx context.Context) {
|
||||||
|
<-ctx.Done()
|
||||||
|
ln.Close()
|
||||||
|
}(gCtx)
|
||||||
|
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
|
||||||
|
mux.Handle("/metrics", promhttp.Handler())
|
||||||
|
mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
st, ok := internal.GetHealth("osiris")
|
||||||
|
if !ok {
|
||||||
|
slog.Error("health service osiris does not exist, file a bug")
|
||||||
|
}
|
||||||
|
|
||||||
|
switch st {
|
||||||
|
case healthv1.HealthCheckResponse_NOT_SERVING:
|
||||||
|
http.Error(w, "NOT OK", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
case healthv1.HealthCheckResponse_SERVING:
|
||||||
|
fmt.Fprintln(w, "OK")
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
http.Error(w, "UNKNOWN", http.StatusFailedDependency)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
slog.Info("listening for Metrics", "bind", cfg.Bind.Metrics)
|
||||||
|
|
||||||
|
srv := http.Server{Handler: mux, ErrorLog: internal.GetFilteredHTTPLogger()}
|
||||||
|
|
||||||
|
return srv.Serve(ln)
|
||||||
|
})
|
||||||
|
|
||||||
|
return g.Wait()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,11 +14,25 @@ import (
|
|||||||
|
|
||||||
"github.com/TecharoHQ/anubis/cmd/osiris/internal/config"
|
"github.com/TecharoHQ/anubis/cmd/osiris/internal/config"
|
||||||
"github.com/lum8rjack/go-ja4h"
|
"github.com/lum8rjack/go-ja4h"
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrTargetInvalid = errors.New("[unexpected] target invalid")
|
ErrTargetInvalid = errors.New("[unexpected] target invalid")
|
||||||
ErrNoHandler = errors.New("[unexpected] no handler for domain")
|
ErrNoHandler = errors.New("[unexpected] no handler for domain")
|
||||||
|
|
||||||
|
requestsPerDomain = promauto.NewGaugeVec(prometheus.GaugeOpts{
|
||||||
|
Namespace: "techaro",
|
||||||
|
Subsystem: "osiris",
|
||||||
|
Name: "request_count",
|
||||||
|
}, []string{"domain"})
|
||||||
|
|
||||||
|
unresolvedRequests = promauto.NewGauge(prometheus.GaugeOpts{
|
||||||
|
Namespace: "techaro",
|
||||||
|
Subsystem: "osiris",
|
||||||
|
Name: "unresolved_requests",
|
||||||
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
type Router struct {
|
type Router struct {
|
||||||
@@ -86,12 +100,12 @@ func NewRouter(c config.Toplevel) (*Router, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%#v\n", result.routes)
|
|
||||||
|
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rtr *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (rtr *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
requestsPerDomain.WithLabelValues(r.Host).Inc()
|
||||||
|
|
||||||
var h http.Handler
|
var h http.Handler
|
||||||
var ok bool
|
var ok bool
|
||||||
|
|
||||||
@@ -104,11 +118,12 @@ func (rtr *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
rtr.lock.RUnlock()
|
rtr.lock.RUnlock()
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
|
unresolvedRequests.Inc()
|
||||||
http.NotFound(w, r) // TODO(Xe): brand this
|
http.NotFound(w, r) // TODO(Xe): brand this
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
r.Header.Set("X-HTTP-JA4H-Fingerprint", ja4hFP)
|
r.Header.Set("X-Http-Ja4h-Fingerprint", ja4hFP)
|
||||||
|
|
||||||
h.ServeHTTP(w, r)
|
h.ServeHTTP(w, r)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user