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 w.ResponseWriter.WriteHeader(n) 35 } 36 37 type logMux struct { 38 http.Handler 39 Logger 40 } 41 42 // Logger allows clients to specifiy how collected data is handled 43 type Logger interface { 44 Log(d Details) 45 } 46 47 // Wrap wraps an existing http.Handler and collects data about the request 48 // and response and passes it to a logger. 49 func Wrap(m http.Handler, l Logger) http.Handler { 50 if m == nil { 51 m = http.DefaultServeMux 52 } 53 return &logMux{Handler: m, Logger: l} 54 } 55 56 var responsePool = sync.Pool{ 57 New: func() interface{} { 58 return new(wrapRW) 59 }, 60 } 61 62 // ServeHTTP satisfies the http.Handler interface 63 func (l *logMux) ServeHTTP(w http.ResponseWriter, r *http.Request) { 64 d := Details{ 65 Request: r, 66 Status: 200, 67 } 68 69 rw := responsePool.Get().(*wrapRW) 70 71 *rw = wrapRW{ 72 w, 73 &d.Status, 74 &d.ResponseLength, 75 } 76 d.StartTime = time.Now() 77 l.Handler.ServeHTTP( 78 httpwrap.Wrap(w, httpwrap.OverrideWriter(rw), httpwrap.OverrideHeaderWriter(rw)), 79 r, 80 ) 81 d.EndTime = time.Now() 82 83 *rw = wrapRW{} 84 responsePool.Put(rw) 85 86 go l.Logger.Log(d) 87 } 88 89 // WriteLogger is a Logger which formats log data to a given template and 90 // writes it to a given io.Writer 91 type WriteLogger struct { 92 mu sync.Mutex 93 w io.Writer 94 template *template.Template 95 } 96 97 // NewWriteLogger uses the given format as a template to write log data to the 98 // given io.Writer 99 func NewWriteLogger(w io.Writer, format string) (Logger, error) { 100 if format == "" { 101 return nil, errors.New("invalid format") 102 } 103 if format[len(format)-1] != '\n' { 104 format += "\n" 105 } 106 t, err := template.New("").Parse(format) 107 if err != nil { 108 return nil, err 109 } 110 return &WriteLogger{ 111 w: w, 112 template: t, 113 }, nil 114 } 115 116 // Log satisfies the Logger interface 117 func (w *WriteLogger) Log(d Details) { 118 w.mu.Lock() 119 w.template.Execute(w.w, d) 120 w.mu.Unlock() 121 }