]> go.fuhry.dev Git - runtime.git/commitdiff
[http] add request IDs w/tracing support
authorDan Fuhry <dan@fuhry.com>
Wed, 6 Aug 2025 03:22:56 +0000 (23:22 -0400)
committerDan Fuhry <dan@fuhry.com>
Wed, 6 Aug 2025 03:23:04 +0000 (23:23 -0400)
http/server.go

index e056d2812c042bd7f47a55f39ce6614d56990a4f..b8457766eab9ba4874c316c98618a361386f8384 100644 (file)
@@ -3,13 +3,16 @@ package http
 import (
        "context"
        "crypto/tls"
+       "encoding/hex"
        "errors"
        "fmt"
+       "math/rand"
        "net"
        "net/http"
        "os"
        "regexp"
        "sync"
+       "time"
 
        "go.fuhry.dev/runtime/mtls"
        "go.fuhry.dev/runtime/utils/log"
@@ -33,11 +36,12 @@ type VirtualHost struct {
 }
 
 type Listener struct {
-       Addr          string                  `yaml:"listen"`
-       ProxyProtocol bool                    `yaml:"proxy_protocol"`
-       InsecureAddr  string                  `yaml:"listen_insecure"`
-       Certificate   string                  `yaml:"cert"`
-       VirtualHosts  map[string]*VirtualHost `yaml:"virtual_hosts"`
+       Addr                   string                  `yaml:"listen"`
+       ProxyProtocol          bool                    `yaml:"proxy_protocol"`
+       InsecureAddr           string                  `yaml:"listen_insecure"`
+       Certificate            string                  `yaml:"cert"`
+       TrustUpstreamRequestID bool                    `yaml:"trust_upstream_request_id"`
+       VirtualHosts           map[string]*VirtualHost `yaml:"virtual_hosts"`
 }
 
 type Server struct {
@@ -56,8 +60,10 @@ const (
        kListener
        kListenAddr
        kSamlDefaults
+       kRequestID
 )
 
+var randSrc = rand.New(rand.NewSource(time.Now().UnixNano()))
 var portSpec = regexp.MustCompile(":[0-9]{1,5}$")
 var initHooks []initHook
 var routeParseFuncs []routeParseFunc
@@ -237,6 +243,7 @@ func (l *Listener) NewHTTPServerWithContext(ctx context.Context) (*http.Server,
        lm := log.NewLoggingMiddlewareWithLogger(
                http.HandlerFunc(l.handle),
                logger.AppendPrefix(".access"))
+       lm.AddResponseHeader("x-request-id")
 
        server := &http.Server{
                Addr: l.Addr,
@@ -297,6 +304,14 @@ func (l *Listener) handle(w http.ResponseWriter, r *http.Request) {
                return
        }
 
+       reqIdCtx := r.Context()
+       rid := r.Header.Get("x-request-id")
+       if rid == "" || !l.TrustUpstreamRequestID {
+               rid = newRequestID()
+       }
+       w.Header().Set("x-request-id", rid)
+       reqIdCtx = context.WithValue(reqIdCtx, kRequestID, rid)
+
        // make sure this host is known
        vhost, ok := l.VirtualHosts[r.Host]
        if !ok {
@@ -309,7 +324,7 @@ func (l *Listener) handle(w http.ResponseWriter, r *http.Request) {
                }
        }
 
-       l.fulfill(w, r, vhost.Routes)
+       l.fulfill(w, r.WithContext(reqIdCtx), vhost.Routes)
 }
 
 func (l *Listener) fulfill(w http.ResponseWriter, r *http.Request, routes []*Route) {
@@ -355,8 +370,18 @@ func (l *Listener) fulfill(w http.ResponseWriter, r *http.Request, routes []*Rou
 func LoggerFromContext(ctx context.Context) log.Logger {
        l := ctx.Value(kLogger)
        if logger, ok := l.(log.Logger); ok {
+               if v, ok := ctx.Value(kRequestID).(string); ok {
+                       logger = logger.AppendPrefix(fmt.Sprintf("<id=%s>", v))
+               }
                return logger
        }
 
        return nil
 }
+
+func newRequestID() string {
+       buf := make([]byte, 10)
+       _, _ = randSrc.Read(buf)
+
+       return hex.EncodeToString(buf)
+}