1 // Package httplog is used to create wrappers around http.Handler's to gather 2 // information about a request and its response. 3 package httplog // import "vimagination.zapto.org/httplog" 4 5 import ( 6 "errors" 7 "io" 8 "net/http" 9 "sync" 10 "text/template" 11 "time" 12 13 "vimagination.zapto.org/httpwrap" 14 ) 15 16 // DefaultFormat is a simple template to output log data in something reminiscent 17 // on the Apache default format. 18 const DefaultFormat = "{{.RemoteAddr}} - {{.URL.User.Username}} - [{{.StartTime.Format \"02/01/2006:15:04:05 +0700\"}}] \"{{.Method}} {{.URL.RequestURI}} {{.Proto}}\" {{.Status}} {{.RequestLength}} {{.StartTime.Sub .EndTime}}" 19 20 // Details is a collection of data about the request and response. 21 type Details struct { 22 *http.Request 23 Status, ResponseLength int 24 StartTime, EndTime time.Time 25 } 26 27 type wrapRW struct { 28 http.ResponseWriter 29 status, contentLength *int 30 } 31 32 func (w *wrapRW) WriteHeader(n int) { 33 *w.status = n 34 35 w.ResponseWriter.WriteHeader(n) 36 } 37 38 type logMux struct { 39 http.Handler 40 Logger 41 } 42 43 // Logger allows clients to specify how collected data is handled. 44 type Logger interface { 45 Log(d Details) 46 } 47 48 // Wrap wraps an existing http.Handler and collects data about the request 49 // and response and passes it to a logger. 50 func Wrap(m http.Handler, l Logger) http.Handler { 51 if m == nil { 52 m = http.DefaultServeMux 53 } 54 55 return &logMux{Handler: m, Logger: l} 56 } 57 58 var responsePool = sync.Pool{ 59 New: func() interface{} { 60 return new(wrapRW) 61 }, 62 } 63 64 // ServeHTTP satisfies the http.Handler interface. 65 func (l *logMux) ServeHTTP(w http.ResponseWriter, r *http.Request) { 66 d := Details{ 67 Request: r, 68 Status: 200, 69 } 70 71 rw := responsePool.Get().(*wrapRW) 72 73 *rw = wrapRW{ 74 w, 75 &d.Status, 76 &d.ResponseLength, 77 } 78 d.StartTime = time.Now() 79 80 l.Handler.ServeHTTP( 81 httpwrap.Wrap(w, httpwrap.OverrideWriter(rw), httpwrap.OverrideHeaderWriter(rw)), 82 r, 83 ) 84 85 d.EndTime = time.Now() 86 *rw = wrapRW{} 87 88 responsePool.Put(rw) 89 90 go l.Logger.Log(d) 91 } 92 93 // WriteLogger is a Logger which formats log data to a given template and 94 // writes it to a given io.Writer. 95 type WriteLogger struct { 96 mu sync.Mutex 97 w io.Writer 98 template *template.Template 99 } 100 101 // NewWriteLogger uses the given format as a template to write log data to the 102 // given io.Writer. 103 func NewWriteLogger(w io.Writer, format string) (Logger, error) { 104 if format == "" { 105 return nil, errors.New("invalid format") 106 } 107 108 if format[len(format)-1] != '\n' { 109 format += "\n" 110 } 111 112 t, err := template.New("").Parse(format) 113 if err != nil { 114 return nil, err 115 } 116 117 return &WriteLogger{ 118 w: w, 119 template: t, 120 }, nil 121 } 122 123 // Log satisfies the Logger interface. 124 func (w *WriteLogger) Log(d Details) { 125 w.mu.Lock() 126 w.template.Execute(w.w, d) 127 w.mu.Unlock() 128 } 129