github.com/ethersphere/bee/v2@v2.2.0/pkg/log/httpaccess/http_access.go (about) 1 // Copyright 2022 The Swarm Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package httpaccess 6 7 import ( 8 "bufio" 9 "net" 10 "net/http" 11 "time" 12 13 "github.com/ethersphere/bee/v2/pkg/log" 14 "github.com/ethersphere/bee/v2/pkg/tracing" 15 ) 16 17 // NewHTTPAccessSuppressLogHandler creates a 18 // handler that will suppress access log messages. 19 func NewHTTPAccessSuppressLogHandler() func(h http.Handler) http.Handler { 20 return func(h http.Handler) http.Handler { 21 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 22 if rr, ok := w.(*responseRecorder); ok { 23 w = rr.ResponseWriter 24 } 25 h.ServeHTTP(w, r) 26 }) 27 } 28 } 29 30 // NewHTTPAccessLogHandler creates a handler that 31 // will log a message after a request has been served. 32 func NewHTTPAccessLogHandler(logger log.Logger, tracer *tracing.Tracer, message string) func(h http.Handler) http.Handler { 33 return func(h http.Handler) http.Handler { 34 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 35 rr, ok := w.(*responseRecorder) 36 if !ok { // No need to layer on another responseRecorder. 37 rr = &responseRecorder{ResponseWriter: w} 38 } 39 40 now := time.Now() 41 h.ServeHTTP(rr, r) 42 if logger.Verbosity() < log.VerbosityInfo { 43 return 44 } 45 duration := time.Since(now) 46 47 ctx, _ := tracer.WithContextFromHTTPHeaders(r.Context(), r.Header) 48 49 logger := tracing.NewLoggerWithTraceID(ctx, logger) 50 51 status := rr.status 52 if status == 0 { 53 status = http.StatusOK 54 } 55 56 ip, _, err := net.SplitHostPort(r.RemoteAddr) 57 if err != nil { 58 ip = r.RemoteAddr 59 } 60 61 fields := []interface{}{ 62 "ip", ip, 63 "method", r.Method, 64 "host", r.Host, 65 "uri", r.RequestURI, 66 "proto", r.Proto, 67 "status", status, 68 "size", rr.size, 69 "duration", duration, 70 } 71 if v := r.Referer(); v != "" { 72 fields = append(fields, "referrer", v) 73 } 74 if v := r.UserAgent(); v != "" { 75 fields = append(fields, "user-agent", v) 76 } 77 if v := r.Header.Get("X-Forwarded-For"); v != "" { 78 fields = append(fields, "x-forwarded-for", v) 79 } 80 if v := r.Header.Get("X-Real-Ip"); v != "" { 81 fields = append(fields, "x-real-ip", v) 82 } 83 84 logger.WithValues(fields...).Build().Info(message) 85 }) 86 } 87 } 88 89 // responseRecorder is an implementation of 90 // http.ResponseWriter that records various metrics. 91 type responseRecorder struct { 92 http.ResponseWriter 93 94 // Metrics. 95 status int 96 size int 97 } 98 99 // Write implements http.ResponseWriter. 100 func (rr *responseRecorder) Write(b []byte) (int, error) { 101 size, err := rr.ResponseWriter.Write(b) 102 rr.size += size 103 return size, err 104 } 105 106 // WriteHeader implements http.ResponseWriter. 107 func (rr *responseRecorder) WriteHeader(s int) { 108 rr.ResponseWriter.WriteHeader(s) 109 if rr.status == 0 { 110 rr.status = s 111 } 112 } 113 114 // CloseNotify implements http.CloseNotifier. 115 func (rr *responseRecorder) CloseNotify() <-chan bool { 116 // staticcheck SA1019 CloseNotifier interface is required by gorilla compress handler. 117 // nolint:staticcheck 118 return rr.ResponseWriter.(http.CloseNotifier).CloseNotify() 119 } 120 121 // Hijack implements http.Hijacker. 122 func (rr *responseRecorder) Hijack() (net.Conn, *bufio.ReadWriter, error) { 123 return rr.ResponseWriter.(http.Hijacker).Hijack() 124 } 125 126 // Flush implements http.Flusher. 127 func (rr *responseRecorder) Flush() { 128 rr.ResponseWriter.(http.Flusher).Flush() 129 } 130 131 // Push implements http.Pusher. 132 func (rr *responseRecorder) Push(target string, opts *http.PushOptions) error { 133 return rr.ResponseWriter.(http.Pusher).Push(target, opts) 134 }