Merge branch 'master' into prefer-wildcard

This commit is contained in:
Matt Holt 2024-10-30 11:02:54 -06:00 committed by GitHub
commit 14bc38010e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
28 changed files with 257 additions and 87 deletions

View File

@ -70,4 +70,4 @@ jobs:
continue-on-error: true
working-directory: ./cmd/caddy
run: |
GOOS=$GOOS GOARCH=$GOARCH go build -tags nobadger -trimpath -o caddy-"$GOOS"-$GOARCH 2> /dev/null
GOOS=$GOOS GOARCH=$GOARCH go build -tags=nobadger,nomysql,nopgx -trimpath -o caddy-"$GOOS"-$GOARCH 2> /dev/null

View File

@ -83,6 +83,8 @@ builds:
- -s -w
tags:
- nobadger
- nomysql
- nopgx
signs:
- cmd: cosign

View File

@ -131,7 +131,7 @@ $ xcaddy build
4. Initialize a Go module: `go mod init caddy`
5. (Optional) Pin Caddy version: `go get github.com/caddyserver/caddy/v2@version` replacing `version` with a git tag, commit, or branch name.
6. (Optional) Add plugins by adding their import: `_ "import/path/here"`
7. Compile: `go build`
7. Compile: `go build -tags=nobadger,nomysql,nopgx`

View File

@ -31,7 +31,7 @@ import (
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
)
// mapAddressToServerBlocks returns a map of listener address to list of server
// mapAddressToProtocolToServerBlocks returns a map of listener address to list of server
// blocks that will be served on that address. To do this, each server block is
// expanded so that each one is considered individually, although keys of a
// server block that share the same address stay grouped together so the config
@ -329,8 +329,12 @@ func (st *ServerType) listenersForServerBlockAddress(sblock serverBlock, addr Ad
// use a map to prevent duplication
listeners := map[string]map[string]struct{}{}
for _, lnCfgVal := range lnCfgVals {
for _, lnHost := range lnCfgVal.addresses {
networkAddr, err := caddy.ParseNetworkAddressFromHostPort(lnHost, lnPort)
for _, lnAddr := range lnCfgVal.addresses {
lnNetw, lnHost, _, err := caddy.SplitNetworkAddress(lnAddr)
if err != nil {
return nil, fmt.Errorf("splitting listener address: %v", err)
}
networkAddr, err := caddy.ParseNetworkAddress(caddy.JoinNetworkAddress(lnNetw, lnHost, lnPort))
if err != nil {
return nil, fmt.Errorf("parsing network address: %v", err)
}

View File

@ -15,6 +15,7 @@
package httpcaddyfile
import (
"cmp"
"encoding/json"
"fmt"
"net"
@ -186,12 +187,25 @@ func (st ServerType) Setup(
return nil, warnings, err
}
// hoist the metrics config from per-server to global
metrics, _ := options["metrics"].(*caddyhttp.Metrics)
for _, s := range servers {
if s.Metrics != nil {
metrics = cmp.Or[*caddyhttp.Metrics](metrics, &caddyhttp.Metrics{})
metrics = &caddyhttp.Metrics{
PerHost: metrics.PerHost || s.Metrics.PerHost,
}
s.Metrics = nil // we don't need it anymore
}
}
// now that each server is configured, make the HTTP app
httpApp := caddyhttp.App{
HTTPPort: tryInt(options["http_port"], &warnings),
HTTPSPort: tryInt(options["https_port"], &warnings),
GracePeriod: tryDuration(options["grace_period"], &warnings),
ShutdownDelay: tryDuration(options["shutdown_delay"], &warnings),
Metrics: metrics,
Servers: servers,
}

View File

@ -24,6 +24,7 @@ import (
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
"github.com/caddyserver/caddy/v2/modules/caddytls"
)
@ -53,6 +54,7 @@ func init() {
RegisterGlobalOption("local_certs", parseOptTrue)
RegisterGlobalOption("key_type", parseOptSingleString)
RegisterGlobalOption("auto_https", parseOptAutoHTTPS)
RegisterGlobalOption("metrics", parseMetricsOptions)
RegisterGlobalOption("servers", parseServerOptions)
RegisterGlobalOption("ocsp_stapling", parseOCSPStaplingOptions)
RegisterGlobalOption("cert_lifetime", parseOptDuration)
@ -446,6 +448,24 @@ func parseOptAutoHTTPS(d *caddyfile.Dispenser, _ any) (any, error) {
return val, nil
}
func unmarshalCaddyfileMetricsOptions(d *caddyfile.Dispenser) (any, error) {
d.Next() // consume option name
metrics := new(caddyhttp.Metrics)
for d.NextBlock(0) {
switch d.Val() {
case "per_host":
metrics.PerHost = true
default:
return nil, d.Errf("unrecognized servers option '%s'", d.Val())
}
}
return metrics, nil
}
func parseMetricsOptions(d *caddyfile.Dispenser, _ any) (any, error) {
return unmarshalCaddyfileMetricsOptions(d)
}
func parseServerOptions(d *caddyfile.Dispenser, _ any) (any, error) {
return unmarshalCaddyfileServerOptions(d)
}

View File

@ -240,6 +240,7 @@ func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) {
}
case "metrics":
caddy.Log().Warn("The nested 'metrics' option inside `servers` is deprecated and will be removed in the next major version. Use the global 'metrics' option instead.")
serverOpts.Metrics = new(caddyhttp.Metrics)
for nesting := d.Nesting(); d.NextBlock(nesting); {
switch d.Val() {

View File

@ -0,0 +1,39 @@
{
metrics
servers :80 {
metrics {
per_host
}
}
}
:80 {
respond "Hello"
}
----------
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":80"
],
"routes": [
{
"handle": [
{
"body": "Hello",
"handler": "static_response"
}
]
}
]
}
},
"metrics": {
"per_host": true
}
}
}
}

View File

@ -26,11 +26,11 @@
}
]
}
],
"metrics": {
"per_host": true
}
]
}
},
"metrics": {
"per_host": true
}
}
}

View File

@ -34,7 +34,7 @@ func (MockDNSProvider) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
return nil
}
// AppendsRecords appends DNS records to the zone.
// AppendRecords appends DNS records to the zone.
func (MockDNSProvider) AppendRecords(ctx context.Context, zone string, recs []libdns.Record) ([]libdns.Record, error) {
return nil, nil
}

View File

@ -409,12 +409,13 @@ latest versions. EXPERIMENTAL: May be changed or removed.
RegisterCommand(Command{
Name: "add-package",
Usage: "<packages...>",
Usage: "<package[@version]...>",
Short: "Adds Caddy packages (EXPERIMENTAL)",
Long: `
Downloads an updated Caddy binary with the specified packages (module/plugin)
added. Retains existing packages. Returns an error if the any of packages are
already included. EXPERIMENTAL: May be changed or removed.
added, with an optional version specified (e.g., "package@version"). Retains
existing packages. Returns an error if any of the specified packages are already
included. EXPERIMENTAL: May be changed or removed.
`,
CobraFunc: func(cmd *cobra.Command) {
cmd.Flags().BoolP("keep-backup", "k", false, "Keep the backed up binary, instead of deleting it")

View File

@ -46,6 +46,25 @@ func cmdUpgrade(fl Flags) (int, error) {
return upgradeBuild(pluginPkgs, fl)
}
func splitModule(arg string) (module, version string, err error) {
const versionSplit = "@"
// accommodate module paths that have @ in them, but we can only tolerate that if there's also
// a version, otherwise we don't know if it's a version separator or part of the file path
lastVersionSplit := strings.LastIndex(arg, versionSplit)
if lastVersionSplit < 0 {
module = arg
} else {
module, version = arg[:lastVersionSplit], arg[lastVersionSplit+1:]
}
if module == "" {
err = fmt.Errorf("module name is required")
}
return
}
func cmdAddPackage(fl Flags) (int, error) {
if len(fl.Args()) == 0 {
return caddy.ExitCodeFailedStartup, fmt.Errorf("at least one package name must be specified")
@ -60,10 +79,15 @@ func cmdAddPackage(fl Flags) (int, error) {
}
for _, arg := range fl.Args() {
if _, ok := pluginPkgs[arg]; ok {
module, version, err := splitModule(arg)
if err != nil {
return caddy.ExitCodeFailedStartup, fmt.Errorf("invalid module name: %v", err)
}
// only allow a version to be specified if it's different from the existing version
if _, ok := pluginPkgs[module]; ok && !(version != "" && pluginPkgs[module].Version != version) {
return caddy.ExitCodeFailedStartup, fmt.Errorf("package is already added")
}
pluginPkgs[arg] = struct{}{}
pluginPkgs[module] = pluginPackage{Version: version, Path: module}
}
return upgradeBuild(pluginPkgs, fl)
@ -83,7 +107,11 @@ func cmdRemovePackage(fl Flags) (int, error) {
}
for _, arg := range fl.Args() {
if _, ok := pluginPkgs[arg]; !ok {
module, _, err := splitModule(arg)
if err != nil {
return caddy.ExitCodeFailedStartup, fmt.Errorf("invalid module name: %v", err)
}
if _, ok := pluginPkgs[module]; !ok {
// package does not exist
return caddy.ExitCodeFailedStartup, fmt.Errorf("package is not added")
}
@ -93,7 +121,7 @@ func cmdRemovePackage(fl Flags) (int, error) {
return upgradeBuild(pluginPkgs, fl)
}
func upgradeBuild(pluginPkgs map[string]struct{}, fl Flags) (int, error) {
func upgradeBuild(pluginPkgs map[string]pluginPackage, fl Flags) (int, error) {
l := caddy.Log()
thisExecPath, err := os.Executable()
@ -120,8 +148,8 @@ func upgradeBuild(pluginPkgs map[string]struct{}, fl Flags) (int, error) {
"os": {runtime.GOOS},
"arch": {runtime.GOARCH},
}
for pkg := range pluginPkgs {
qs.Add("p", pkg)
for _, pkgInfo := range pluginPkgs {
qs.Add("p", pkgInfo.String())
}
// initiate the build
@ -276,14 +304,14 @@ func downloadBuild(qs url.Values) (*http.Response, error) {
return resp, nil
}
func getPluginPackages(modules []moduleInfo) (map[string]struct{}, error) {
pluginPkgs := make(map[string]struct{})
func getPluginPackages(modules []moduleInfo) (map[string]pluginPackage, error) {
pluginPkgs := make(map[string]pluginPackage)
for _, mod := range modules {
if mod.goModule.Replace != nil {
return nil, fmt.Errorf("cannot auto-upgrade when Go module has been replaced: %s => %s",
mod.goModule.Path, mod.goModule.Replace.Path)
}
pluginPkgs[mod.goModule.Path] = struct{}{}
pluginPkgs[mod.goModule.Path] = pluginPackage{Version: mod.goModule.Version, Path: mod.goModule.Path}
}
return pluginPkgs, nil
}
@ -312,3 +340,15 @@ func writeCaddyBinary(path string, body *io.ReadCloser, fileInfo os.FileInfo) er
}
const downloadPath = "https://caddyserver.com/api/download"
type pluginPackage struct {
Version string
Path string
}
func (p pluginPackage) String() string {
if p.Version == "" {
return p.Path
}
return p.Path + "@" + p.Version
}

2
go.mod
View File

@ -19,7 +19,7 @@ require (
github.com/klauspost/cpuid/v2 v2.2.8
github.com/mholt/acmez/v2 v2.0.3
github.com/prometheus/client_golang v1.19.1
github.com/quic-go/quic-go v0.48.0
github.com/quic-go/quic-go v0.48.1
github.com/smallstep/certificates v0.26.1
github.com/smallstep/nosql v0.6.1
github.com/smallstep/truststore v0.13.0

4
go.sum
View File

@ -392,8 +392,8 @@ github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
github.com/quic-go/quic-go v0.48.0 h1:2TCyvBrMu1Z25rvIAlnp2dPT4lgh/uTqLqiXVpp5AeU=
github.com/quic-go/quic-go v0.48.0/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs=
github.com/quic-go/quic-go v0.48.1 h1:y/8xmfWI9qmGTc+lBr4jKRUWLGSlSigv847ULJ4hYXA=
github.com/quic-go/quic-go v0.48.1/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=

View File

@ -305,25 +305,6 @@ func IsFdNetwork(netw string) bool {
return strings.HasPrefix(netw, "fd")
}
// normally we would simply append the port,
// but if host is IPv6, we need to ensure it
// is enclosed in [ ]; net.JoinHostPort does
// this for us, but host might also have a
// network type in front (e.g. "tcp/") leading
// to "[tcp/::1]" which causes parsing failures
// later; what we need is "tcp/[::1]", so we have
// to split the network and host, then re-combine
func ParseNetworkAddressFromHostPort(host, port string) (NetworkAddress, error) {
network, addr, ok := strings.Cut(host, "/")
if !ok {
addr = network
network = ""
}
addr = strings.Trim(addr, "[]") // IPv6
networkAddr := JoinNetworkAddress(network, addr, port)
return ParseNetworkAddress(networkAddr)
}
// ParseNetworkAddress parses addr into its individual
// components. The input string is expected to be of
// the form "network/host:port-range" where any part is
@ -399,25 +380,28 @@ func SplitNetworkAddress(a string) (network, host, port string, err error) {
if slashFound {
network = strings.ToLower(strings.TrimSpace(beforeSlash))
a = afterSlash
if IsUnixNetwork(network) || IsFdNetwork(network) {
host = a
return
}
}
if IsUnixNetwork(network) || IsFdNetwork(network) {
host = a
return
}
host, port, err = net.SplitHostPort(a)
if err == nil || a == "" {
return
}
// in general, if there was an error, it was likely "missing port",
// so try adding a bogus port to take advantage of standard library's
// robust parser, then strip the artificial port before returning
// (don't overwrite original error though; might still be relevant)
var err2 error
host, port, err2 = net.SplitHostPort(a + ":0")
if err2 == nil {
err = nil
firstErr := err
if err != nil {
// in general, if there was an error, it was likely "missing port",
// so try removing square brackets around an IPv6 host, adding a bogus
// port to take advantage of standard library's robust parser, then
// strip the artificial port.
host, _, err = net.SplitHostPort(net.JoinHostPort(strings.Trim(a, "[]"), "0"))
port = ""
}
if err != nil {
err = errors.Join(firstErr, err)
}
return
}

View File

@ -31,7 +31,7 @@ func TestSplitNetworkAddress(t *testing.T) {
}{
{
input: "",
expectErr: true,
expectHost: "",
},
{
input: "foo",
@ -42,7 +42,7 @@ func TestSplitNetworkAddress(t *testing.T) {
},
{
input: "::",
expectErr: true,
expectHost: "::",
},
{
input: "[::]",
@ -77,7 +77,7 @@ func TestSplitNetworkAddress(t *testing.T) {
{
input: "udp/",
expectNetwork: "udp",
expectErr: true,
expectHost: "",
},
{
input: "unix//foo/bar",
@ -185,7 +185,8 @@ func TestParseNetworkAddress(t *testing.T) {
}{
{
input: "",
expectErr: true,
expectAddr: NetworkAddress{
},
},
{
input: ":",
@ -311,7 +312,8 @@ func TestParseNetworkAddressWithDefaults(t *testing.T) {
}{
{
input: "",
expectErr: true,
expectAddr: NetworkAddress{
},
},
{
input: ":",

View File

@ -15,6 +15,7 @@
package caddyhttp
import (
"cmp"
"context"
"crypto/tls"
"fmt"
@ -142,6 +143,10 @@ type App struct {
// affect functionality.
Servers map[string]*Server `json:"servers,omitempty"`
// If set, metrics observations will be enabled.
// This setting is EXPERIMENTAL and subject to change.
Metrics *Metrics `json:"metrics,omitempty"`
ctx caddy.Context
logger *zap.Logger
tlsApp *caddytls.TLS
@ -184,6 +189,10 @@ func (app *App) Provision(ctx caddy.Context) error {
return err
}
if app.Metrics != nil {
app.Metrics.init = sync.Once{}
app.Metrics.httpMetrics = &httpMetrics{}
}
// prepare each server
oldContext := ctx.Context
for srvName, srv := range app.Servers {
@ -196,6 +205,15 @@ func (app *App) Provision(ctx caddy.Context) error {
srv.errorLogger = app.logger.Named("log.error")
srv.shutdownAtMu = new(sync.RWMutex)
if srv.Metrics != nil {
srv.logger.Warn("per-server 'metrics' is deprecated; use 'metrics' in the root 'http' app instead")
app.Metrics = cmp.Or[*Metrics](app.Metrics, &Metrics{
init: sync.Once{},
httpMetrics: &httpMetrics{},
})
app.Metrics.PerHost = app.Metrics.PerHost || srv.Metrics.PerHost
}
// only enable access logs if configured
if srv.Logs != nil {
srv.accessLogger = app.logger.Named("log.access")
@ -342,16 +360,11 @@ func (app *App) Provision(ctx caddy.Context) error {
srv.listenerWrappers = append([]caddy.ListenerWrapper{new(tlsPlaceholderWrapper)}, srv.listenerWrappers...)
}
}
// pre-compile the primary handler chain, and be sure to wrap it in our
// route handler so that important security checks are done, etc.
primaryRoute := emptyHandler
if srv.Routes != nil {
if srv.Metrics != nil {
srv.Metrics.init = sync.Once{}
srv.Metrics.httpMetrics = &httpMetrics{}
}
err := srv.Routes.ProvisionHandlers(ctx, srv.Metrics)
err := srv.Routes.ProvisionHandlers(ctx, app.Metrics)
if err != nil {
return fmt.Errorf("server %s: setting up route handlers: %v", srvName, err)
}
@ -370,7 +383,7 @@ func (app *App) Provision(ctx caddy.Context) error {
// provision the named routes (they get compiled at runtime)
for name, route := range srv.NamedRoutes {
err := route.Provision(ctx, srv.Metrics)
err := route.Provision(ctx, app.Metrics)
if err != nil {
return fmt.Errorf("server %s: setting up named route '%s' handlers: %v", name, srvName, err)
}

View File

@ -191,7 +191,7 @@ func (MatchFile) CELLibrary(ctx caddy.Context) (cel.Library, error) {
var try_policy string
if len(values["try_policy"]) > 0 {
root = values["try_policy"][0]
try_policy = values["try_policy"][0]
}
m := MatchFile{

View File

@ -289,6 +289,7 @@ var expressionTests = []struct {
wantErr bool
wantResult bool
clientCertificate []byte
expectedPath string
}{
{
name: "file error no args (MatchFile)",
@ -354,6 +355,15 @@ var expressionTests = []struct {
urlTarget: "https://example.com/nopenope.txt",
wantResult: false,
},
{
name: "file match long pattern foo.txt with try_policy (MatchFile)",
expression: &caddyhttp.MatchExpression{
Expr: `file({"root": "./testdata", "try_policy": "largest_size", "try_files": ["foo.txt", "large.txt"]})`,
},
urlTarget: "https://example.com/",
wantResult: true,
expectedPath: "/large.txt",
},
}
func TestMatchExpressionMatch(t *testing.T) {
@ -382,6 +392,16 @@ func TestMatchExpressionMatch(t *testing.T) {
if tc.expression.Match(req) != tc.wantResult {
t.Errorf("MatchExpression.Match() expected to return '%t', for expression : '%s'", tc.wantResult, tc.expression.Expr)
}
if tc.expectedPath != "" {
path, ok := repl.Get("http.matchers.file.relative")
if !ok {
t.Errorf("MatchExpression.Match() expected to return path '%s', but got none", tc.expectedPath)
}
if path != tc.expectedPath {
t.Errorf("MatchExpression.Match() expected to return path '%s', but got '%s'", tc.expectedPath, path)
}
}
})
}
}

View File

@ -0,0 +1,3 @@
This is a file with more content than the other files in this directory
such that tests using the largest_size policy pick this file, or the
smallest_size policy avoids this file.

View File

@ -200,9 +200,7 @@ func (ops HeaderOps) ApplyTo(hdr http.Header, repl *caddy.Replacer) {
for _, fieldName := range ops.Delete {
fieldName = repl.ReplaceKnown(fieldName, "")
if fieldName == "*" {
for existingField := range hdr {
delete(hdr, existingField)
}
clear(hdr)
}
}

View File

@ -4,6 +4,7 @@ import (
"context"
"errors"
"net/http"
"strings"
"sync"
"time"
@ -133,8 +134,8 @@ func (h *metricsInstrumentedHandler) ServeHTTP(w http.ResponseWriter, r *http.Re
statusLabels := prometheus.Labels{"server": server, "handler": h.handler, "method": method, "code": ""}
if h.metrics.PerHost {
labels["host"] = r.Host
statusLabels["host"] = r.Host
labels["host"] = strings.ToLower(r.Host)
statusLabels["host"] = strings.ToLower(r.Host)
}
inFlight := h.metrics.httpMetrics.requestInFlight.With(labels)

View File

@ -807,17 +807,26 @@ func (h *Handler) reverseProxy(rw http.ResponseWriter, req *http.Request, origRe
shouldLogCredentials := server.Logs != nil && server.Logs.ShouldLogCredentials
// Forward 1xx status codes, backported from https://github.com/golang/go/pull/53164
var (
roundTripMutex sync.Mutex
roundTripDone bool
)
trace := &httptrace.ClientTrace{
Got1xxResponse: func(code int, header textproto.MIMEHeader) error {
roundTripMutex.Lock()
defer roundTripMutex.Unlock()
if roundTripDone {
// If RoundTrip has returned, don't try to further modify
// the ResponseWriter's header map.
return nil
}
h := rw.Header()
copyHeader(h, http.Header(header))
rw.WriteHeader(code)
// Clear headers coming from the backend
// (it's not automatically done by ResponseWriter.WriteHeader() for 1xx responses)
for k := range header {
delete(h, k)
}
clear(h)
return nil
},
@ -833,11 +842,18 @@ func (h *Handler) reverseProxy(rw http.ResponseWriter, req *http.Request, origRe
req = req.WithContext(context.WithoutCancel(req.Context()))
}
// do the round-trip; emit debug log with values we know are
// safe, or if there is no error, emit fuller log entry
// do the round-trip
start := time.Now()
res, err := h.Transport.RoundTrip(req)
duration := time.Since(start)
// record that the round trip is done for the 1xx response handler
roundTripMutex.Lock()
roundTripDone = true
roundTripMutex.Unlock()
// emit debug log with values we know are safe,
// or if there is no error, emit fuller log entry
logger := h.logger.With(
zap.String("upstream", di.Upstream.String()),
zap.Duration("duration", duration),

View File

@ -103,7 +103,7 @@ func (h *Handler) handleUpgradeResponse(logger *zap.Logger, wg *sync.WaitGroup,
start := time.Now()
defer func() {
conn.Close()
if c := logger.Check(zapcore.DebugLevel, "hijack failed on protocol switch"); c != nil {
if c := logger.Check(zapcore.DebugLevel, "connection closed"); c != nil {
c.Write(zap.Duration("duration", time.Since(start)))
}
}()

View File

@ -226,6 +226,7 @@ type Server struct {
// If set, metrics observations will be enabled.
// This setting is EXPERIMENTAL and subject to change.
// DEPRECATED: Use the app-level `metrics` field.
Metrics *Metrics `json:"metrics,omitempty"`
name string

View File

@ -69,12 +69,13 @@ func TestServer_LogRequest(t *testing.T) {
}`, buf.String())
}
func TestServer_LogRequest_WithTraceID(t *testing.T) {
func TestServer_LogRequest_WithTrace(t *testing.T) {
s := &Server{}
extra := new(ExtraLogFields)
ctx := context.WithValue(context.Background(), ExtraLogFieldsCtxKey, extra)
extra.Add(zap.String("traceID", "1234567890abcdef"))
extra.Add(zap.String("spanID", "12345678"))
req := httptest.NewRequest(http.MethodGet, "/", nil).WithContext(ctx)
rec := httptest.NewRecorder()
@ -93,7 +94,8 @@ func TestServer_LogRequest_WithTraceID(t *testing.T) {
"msg":"handled request", "level":"info", "bytes_read":0,
"duration":"50ms", "resp_headers": {}, "size":0,
"status":0, "user_id":"",
"traceID":"1234567890abcdef"
"traceID":"1234567890abcdef",
"spanID":"12345678"
}`, buf.String())
}
@ -144,12 +146,13 @@ func BenchmarkServer_LogRequest_NopLogger(b *testing.B) {
}
}
func BenchmarkServer_LogRequest_WithTraceID(b *testing.B) {
func BenchmarkServer_LogRequest_WithTrace(b *testing.B) {
s := &Server{}
extra := new(ExtraLogFields)
ctx := context.WithValue(context.Background(), ExtraLogFieldsCtxKey, extra)
extra.Add(zap.String("traceID", "1234567890abcdef"))
extra.Add(zap.String("spanID", "12345678"))
req := httptest.NewRequest(http.MethodGet, "/", nil).WithContext(ctx)
rec := httptest.NewRecorder()

View File

@ -88,11 +88,15 @@ func (ot *openTelemetryWrapper) serveHTTP(w http.ResponseWriter, r *http.Request
spanCtx := trace.SpanContextFromContext(ctx)
if spanCtx.IsValid() {
traceID := spanCtx.TraceID().String()
spanID := spanCtx.SpanID().String()
// Add a trace_id placeholder, accessible via `{http.vars.trace_id}`.
caddyhttp.SetVar(ctx, "trace_id", traceID)
// Add the trace id to the log fields for the request.
// Add a span_id placeholder, accessible via `{http.vars.span_id}`.
caddyhttp.SetVar(ctx, "span_id", spanID)
// Add the traceID and spanID to the log fields for the request.
if extra, ok := ctx.Value(caddyhttp.ExtraLogFieldsCtxKey).(*caddyhttp.ExtraLogFields); ok {
extra.Add(zap.String("traceID", traceID))
extra.Add(zap.String("spanID", spanID))
}
}
next := ctx.Value(nextCallCtxKey).(*nextCall)

View File

@ -28,6 +28,10 @@ import (
// trapSignalsPosix captures POSIX-only signals.
func trapSignalsPosix() {
// Ignore all SIGPIPE signals to prevent weird issues with systemd: https://github.com/dunglas/frankenphp/issues/1020
// Docker/Moby has a similar hack: https://github.com/moby/moby/blob/d828b032a87606ae34267e349bf7f7ccb1f6495a/cmd/dockerd/docker.go#L87-L90
signal.Ignore(syscall.SIGPIPE)
go func() {
sigchan := make(chan os.Signal, 1)
signal.Notify(sigchan, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGUSR1, syscall.SIGUSR2)