influxdb/http/middleware.go

169 lines
4.7 KiB
Go
Raw Normal View History

package http
import (
"bytes"
"errors"
"io"
"net/http"
"path"
"strings"
"time"
kithttp "github.com/influxdata/influxdb/v2/kit/transport/http"
"go.uber.org/zap"
)
// LoggingMW middleware for logging inflight http requests.
func LoggingMW(log *zap.Logger) kithttp.Middleware {
return func(next http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
srw := kithttp.NewStatusResponseWriter(w)
var buf bytes.Buffer
r.Body = &bodyEchoer{
rc: r.Body,
teedR: io.TeeReader(r.Body, &buf),
}
defer func(start time.Time) {
errField := zap.Skip()
if errStr := w.Header().Get(kithttp.PlatformErrorCodeHeader); errStr != "" {
errField = zap.Error(errors.New(errStr))
}
errReferenceField := zap.Skip()
if errReference := w.Header().Get(kithttp.PlatformErrorCodeHeader); errReference != "" {
errReferenceField = zap.String("error_code", errReference)
}
fields := []zap.Field{
zap.String("method", r.Method),
zap.String("host", r.Host),
zap.String("path", r.URL.Path),
zap.String("query", r.URL.Query().Encode()),
zap.String("proto", r.Proto),
zap.Int("status_code", srw.Code()),
zap.Int("response_size", srw.ResponseBytes()),
zap.Int64("content_length", r.ContentLength),
zap.String("referrer", r.Referer()),
zap.String("remote", r.RemoteAddr),
zap.String("user_agent", kithttp.UserAgent(r)),
zap.Duration("took", time.Since(start)),
errField,
errReferenceField,
}
invalidMethodFn, ok := mapURLPath(r.URL.Path)
if !ok || !invalidMethodFn(r.Method) {
fields = append(fields, zap.ByteString("body", buf.Bytes()))
}
log.Debug("Request", fields...)
}(time.Now())
next.ServeHTTP(srw, r)
}
return http.HandlerFunc(fn)
}
}
type isValidMethodFn func(method string) bool
func mapURLPath(rawPath string) (isValidMethodFn, bool) {
if fn, ok := blacklistEndpoints[rawPath]; ok {
return fn, true
}
shiftPath := func(p string) (head, tail string) {
p = path.Clean("/" + p)
i := strings.Index(p[1:], "/") + 1
if i <= 0 {
return p[1:], "/"
}
return p[1:i], p[i:]
}
// ugh, should probably make this whole operation use a trie
partsMatch := func(raw, source string) bool {
return raw == source || (strings.HasPrefix(source, ":") && raw != "")
}
compareRawSourceURLs := func(raw, source string) bool {
sourceHead, sourceTail := shiftPath(source)
for rawHead, rawTail := shiftPath(rawPath); rawHead != ""; {
if !partsMatch(rawHead, sourceHead) {
return false
}
rawHead, rawTail = shiftPath(rawTail)
sourceHead, sourceTail = shiftPath(sourceTail)
}
return sourceHead == ""
}
for sourcePath, fn := range blacklistEndpoints {
match := compareRawSourceURLs(rawPath, sourcePath)
if match {
return fn, true
}
}
return nil, false
}
func ignoreMethod(ignoredMethods ...string) isValidMethodFn {
if len(ignoredMethods) == 0 {
return func(string) bool { return true }
}
ignoreMap := make(map[string]bool)
for _, method := range ignoredMethods {
ignoreMap[method] = true
}
return func(method string) bool {
return ignoreMap[method]
}
}
const (
prefixSetup = "/api/v2/setup"
organizationsIDSecretsPath = "/api/v2/orgs/:id/secrets"
organizationsIDSecretsDeletePath = "/api/v2/orgs/:id/secrets/delete"
)
// TODO(@jsteenb2): make this a stronger type that handlers can register routes that should not be logged.
var blacklistEndpoints = map[string]isValidMethodFn{
"/api/v2/signin": ignoreMethod(),
"/api/v2/signout": ignoreMethod(),
"/api/v2/me": ignoreMethod(),
"/api/v2/me/password": ignoreMethod(),
"/api/v2/users/:id/password": ignoreMethod(),
"/api/v2/packages/apply": ignoreMethod(),
prefixWrite: ignoreMethod("POST"),
"/write": ignoreMethod("POST"),
organizationsIDSecretsPath: ignoreMethod("PATCH"),
organizationsIDSecretsDeletePath: ignoreMethod("POST"),
prefixSetup: ignoreMethod("POST"),
prefixNotificationEndpoints: ignoreMethod("POST"),
notificationEndpointsIDPath: ignoreMethod("PUT"),
restoreKVPath: ignoreMethod(),
restoreSqlPath: ignoreMethod(),
restoreBucketPath: ignoreMethod(),
restoreShardPath: ignoreMethod(),
"/api/v2/remotes": ignoreMethod("POST"),
"/api/v2/remotes/:id": ignoreMethod("PATCH"),
}
type bodyEchoer struct {
rc io.ReadCloser
teedR io.Reader
}
func (b *bodyEchoer) Read(p []byte) (int, error) {
return b.teedR.Read(p)
}
func (b *bodyEchoer) Close() error {
return b.rc.Close()
}