Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/openstatusHQ/openstatus. ๐ซ Status page with uptime monitoring & API monitoring as code ๐ซ openstatus.dev
bun drizzle-orm monitoring monitoring-as-code nextjs observability on-call open-source shadcn-ui status-page statuspage synthetic-monitoring tinybird turso uptime uptime-checker uptime-monitor
Something went wrong. Try again.
6.7 kB ยท 262 lines
Go
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263package main
import ( "bytes" "context" "errors" "fmt" "io" "net" "net/http" "net/http/httputil" "net/url" "os" "os/signal" "syscall" "time"
"github.com/gin-gonic/gin" "github.com/rs/zerolog/log")
// retryTransport retries a request when the underlying dial fails.// Safe because a dial failure means the request never reached the upstream,// so there's no risk of duplicating side effects.type retryTransport struct { base http.RoundTripper maxRetries int backoff time.Duration}
func (rt *retryTransport) RoundTrip(req *http.Request) (*http.Response, error) { var body []byte if req.Body != nil && req.Body != http.NoBody { b, err := io.ReadAll(req.Body) req.Body.Close() if err != nil { return nil, err } body = b }
var lastErr error for attempt := 0; attempt <= rt.maxRetries; attempt++ { if attempt > 0 { select { case <-req.Context().Done(): return nil, req.Context().Err() case <-time.After(rt.backoff): } }
if body != nil { req.Body = io.NopCloser(bytes.NewReader(body)) }
resp, err := rt.base.RoundTrip(req) if err == nil { return resp, nil } lastErr = err
if !isDialError(err) { return nil, err }
log.Warn(). Err(err). Int("attempt", attempt+1). Str("url", req.URL.String()). Msg("retrying after dial failure") } return nil, lastErr}
func isDialError(err error) bool { var opErr *net.OpError if errors.As(err, &opErr) { return opErr.Op == "dial" } return false}
// Tuned for Railway internal networking: short dial timeout so a dead pod// fails fast instead of stalling for the default 30s, plus aggressive idle// pruning so we don't reuse connections that the platform has silently torn down.func newTransport() *http.Transport { return &http.Transport{ DialContext: (&net.Dialer{ Timeout: 5 * time.Second, KeepAlive: 30 * time.Second, }).DialContext, MaxIdleConns: 200, MaxIdleConnsPerHost: 50, IdleConnTimeout: 60 * time.Second, TLSHandshakeTimeout: 10 * time.Second, ExpectContinueTimeout: 1 * time.Second, ResponseHeaderTimeout: 60 * time.Second, ForceAttemptHTTP2: true, }}
var regionTargets = map[string]string{ "europe-west4-drams3a": "http://openstatus-checker-eu-west.railway.internal:8080", "us-east4-eqdc4a": "http://openstatus-checker-us-east.railway.internal:8080", "us-west2": "http://openstatus-checker-us-west.railway.internal:8080", "asia-southeast1-eqsg3a": "http://checker-southeast-asia.railway.internal:8080",}
func buildProxies(transport http.RoundTripper) (map[string]*httputil.ReverseProxy, error) { proxies := make(map[string]*httputil.ReverseProxy, len(regionTargets)) for region, target := range regionTargets { remote, err := url.Parse(target) if err != nil { return nil, fmt.Errorf("parse target for region %s: %w", region, err) }
p := &httputil.ReverseProxy{ Transport: transport, Rewrite: func(r *httputil.ProxyRequest) { r.SetURL(remote) r.Out.Host = remote.Host }, } p.ErrorHandler = func(rw http.ResponseWriter, req *http.Request, err error) { log.Error(). Err(err). Str("region", region). Str("target", remote.String()). Str("method", req.Method). Str("path", req.URL.Path). Msg("reverse proxy transport error") rw.WriteHeader(http.StatusBadGateway) } p.ModifyResponse = func(resp *http.Response) error { if resp.StatusCode >= 500 { log.Error(). Str("region", region). Str("target", remote.String()). Str("method", resp.Request.Method). Str("path", resp.Request.URL.Path). Int("status", resp.StatusCode). Msg("upstream returned server error") } else if resp.StatusCode >= 400 { log.Warn(). Str("region", region). Str("target", remote.String()). Str("method", resp.Request.Method). Str("path", resp.Request.URL.Path). Int("status", resp.StatusCode). Msg("upstream returned client error") } return nil } proxies[region] = p } return proxies, nil}
func requestLogger() gin.HandlerFunc { return func(c *gin.Context) { start := time.Now() c.Next()
status := c.Writer.Status() evt := log.Info() switch { case status >= 500: evt = log.Error() case status >= 400: evt = log.Warn() }
evt. Str("method", c.Request.Method). Str("path", c.Request.URL.Path). Str("region", c.Request.Header.Get("railway-region")). Int("status", status). Dur("duration", time.Since(start)). Msg("request") }}
func proxyHandler(proxies map[string]*httputil.ReverseProxy) gin.HandlerFunc { return func(c *gin.Context) { region := c.Request.Header.Get("railway-region") p, ok := proxies[region] if !ok { log.Warn(). Str("region", region). Str("path", c.Request.URL.Path). Msg("unknown or missing railway-region header") c.JSON(http.StatusBadGateway, gin.H{"error": "unknown region", "region": region}) return } p.ServeHTTP(c.Writer, c.Request) }}
func main() { ctx, cancel := context.WithCancel(context.Background()) defer cancel() done := make(chan os.Signal, 1) signal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
go func() { <-done cancel() }()
cloudProvider := env("CLOUD_PROVIDER", "railway") region := env("RAILWAY_REPLICA_REGION", env("REGION", "local"))
transport := &retryTransport{ base: newTransport(), maxRetries: 2, backoff: 200 * time.Millisecond, } proxies, err := buildProxies(transport) if err != nil { log.Fatal().Err(err).Msg("failed to build reverse proxies") }
router := gin.New() router.Use(gin.Recovery(), requestLogger()) router.NoRoute(proxyHandler(proxies))
router.GET("/healthz", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"message": "pong", "region": region, "provider": cloudProvider}) })
httpServer := &http.Server{ Addr: fmt.Sprintf("0.0.0.0:%s", env("PORT", "8080")), Handler: router, ReadHeaderTimeout: 10 * time.Second, ReadTimeout: 60 * time.Second, WriteTimeout: 60 * time.Second, IdleTimeout: 120 * time.Second, }
go func() { if err := httpServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { log.Ctx(ctx).Error().Err(err).Msg("failed to start http server") cancel() } }()
<-ctx.Done()
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 30*time.Second) defer shutdownCancel() if err := httpServer.Shutdown(shutdownCtx); err != nil { log.Error().Err(err).Msg("failed to shutdown http server") }}
func env(key, fallback string) string { if value, ok := os.LookupEnv(key); ok { return value }
return fallback}