...

Source file src/oss.terrastruct.com/util-go/xhttp/log.go

Documentation: oss.terrastruct.com/util-go/xhttp

     1  package xhttp
     2  
     3  import (
     4  	"bufio"
     5  	"errors"
     6  	"fmt"
     7  	"log"
     8  	"net"
     9  	"net/http"
    10  	"runtime/debug"
    11  	"time"
    12  
    13  	"golang.org/x/text/message"
    14  
    15  	"oss.terrastruct.com/util-go/cmdlog"
    16  )
    17  
    18  type ResponseWriter interface {
    19  	http.ResponseWriter
    20  	http.Hijacker
    21  	http.Flusher
    22  	writtenResponseWriter
    23  }
    24  
    25  var _ ResponseWriter = &responseWriter{}
    26  
    27  type responseWriter struct {
    28  	rw http.ResponseWriter
    29  
    30  	written bool
    31  	status  int
    32  	length  int
    33  }
    34  
    35  func (rw *responseWriter) Header() http.Header {
    36  	return rw.rw.Header()
    37  }
    38  
    39  func (rw *responseWriter) WriteHeader(statusCode int) {
    40  	if !rw.written {
    41  		rw.written = true
    42  		rw.status = statusCode
    43  	}
    44  	rw.rw.WriteHeader(statusCode)
    45  }
    46  
    47  func (rw *responseWriter) Write(p []byte) (int, error) {
    48  	if !rw.written && len(p) > 0 {
    49  		rw.written = true
    50  		if rw.status == 0 {
    51  			rw.status = http.StatusOK
    52  		}
    53  	}
    54  	rw.length += len(p)
    55  	return rw.rw.Write(p)
    56  }
    57  
    58  func (rw *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
    59  	hj, ok := rw.rw.(http.Hijacker)
    60  	if !ok {
    61  		return nil, nil, fmt.Errorf("underlying response writer does not implement http.Hijacker: %T", rw.rw)
    62  	}
    63  	return hj.Hijack()
    64  }
    65  
    66  func (rw *responseWriter) Flush() {
    67  	f, ok := rw.rw.(http.Flusher)
    68  	if !ok {
    69  		return
    70  	}
    71  	f.Flush()
    72  }
    73  
    74  func (rw *responseWriter) Written() bool {
    75  	return rw.written
    76  }
    77  
    78  func Log(clog *cmdlog.Logger, next http.Handler) http.Handler {
    79  	englishPrinter := message.NewPrinter(message.MatchLanguage("en"))
    80  	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    81  		defer func() {
    82  			rec := recover()
    83  			if rec != nil {
    84  				clog.Error.Printf("caught panic: %#v\n%s", rec, debug.Stack())
    85  				JSON(clog, w, http.StatusInternalServerError, map[string]interface{}{
    86  					"error": http.StatusText(http.StatusInternalServerError),
    87  				})
    88  			}
    89  		}()
    90  
    91  		rw := &responseWriter{
    92  			rw: w,
    93  		}
    94  
    95  		start := time.Now()
    96  		next.ServeHTTP(rw, r)
    97  		dur := time.Since(start)
    98  
    99  		if !rw.Written() {
   100  			_, err := rw.Write(nil)
   101  			if errors.Is(err, http.ErrHijacked) {
   102  				clog.Success.Printf("%s %s %v: hijacked", r.Method, r.URL, dur)
   103  				return
   104  			}
   105  
   106  			clog.Warn.Printf("%s %s %v: no response written", r.Method, r.URL, dur)
   107  			return
   108  		}
   109  
   110  		var statusLogger *log.Logger
   111  		switch {
   112  		case 100 <= rw.status && rw.status <= 299:
   113  			statusLogger = clog.Success
   114  		case 300 <= rw.status && rw.status <= 399:
   115  			statusLogger = clog.Info
   116  		case 400 <= rw.status && rw.status <= 499:
   117  			statusLogger = clog.Warn
   118  		case 500 <= rw.status && rw.status <= 599:
   119  			statusLogger = clog.Error
   120  		}
   121  		lengthStr := englishPrinter.Sprint(rw.length)
   122  		statusLogger.Printf("%s %s %d %sB %v", r.Method, r.URL, rw.status, lengthStr, dur)
   123  	})
   124  }
   125  

View as plain text