pomerium/internal/log/headers.go
Caleb Doxsey 638d9f3d6c
proxy: add support for logging http request headers (#4388)
* config: add customization options for logging

* config: validate log fields

* proxy: add support for logging http request headers

* log subset of headers

* fix test name

* dont use log.HTTPHeaders for access logs

* canonicalize http/2 headers
2023-07-25 09:46:42 -06:00

65 lines
1.6 KiB
Go

package log
import (
"net/http"
"strings"
"github.com/rs/zerolog"
"github.com/pomerium/pomerium/internal/sets"
)
const (
headersFieldName = "headers"
headersFieldPrefix = headersFieldName + "."
)
// GetHeaderField returns the header name for a field that represents logging a header value.
func GetHeaderField[TField interface{ ~string }](field TField) (headerName string, ok bool) {
if strings.HasPrefix(string(field), headersFieldPrefix) {
return string(field)[len(headersFieldPrefix):], true
}
return "", false
}
// HTTPHeaders logs http headers based on supplied fields and a map of all headers.
func HTTPHeaders[TField interface{ ~string }](
evt *zerolog.Event,
fields []TField,
src map[string]string,
) *zerolog.Event {
all := false
include := sets.NewHash[string]()
for _, field := range fields {
if field == headersFieldName {
all = true
break
} else if strings.HasPrefix(string(field), headersFieldPrefix) {
include.Add(CanonicalHeaderKey(string(field[len(headersFieldPrefix):])))
}
}
// nothing to log
if include.Size() == 0 && !all {
return evt
}
hdrs := map[string]string{}
for k, v := range src {
h := CanonicalHeaderKey(k)
if all || include.Has(h) {
hdrs[h] = v
}
}
return evt.Interface(headersFieldName, hdrs)
}
// CanonicalHeaderKey converts a header name into its canonical form using http.CanonicalHeaderKey.
// It also supports HTTP/2 headers that start with : by lowercasing them.
func CanonicalHeaderKey(k string) string {
if strings.HasPrefix(k, ":") {
return strings.ToLower(k)
}
return http.CanonicalHeaderKey(k)
}