package main import ( "fmt" "io" "net/http" "strings" "time" ) const formatPattern = "%s - - [%s] \"%s\" %d %d %.4f\n" // LogRecord implements an Apache-compatible HTTP logging type LogRecord struct { http.ResponseWriter status int responseBytes int64 ip string method, uri, protocol string time time.Time elapsedTime time.Duration } // Log writes a log entry in the passed io.Writer stream func (r *LogRecord) Log(out io.Writer) { timeFormat := r.time.Format("02/Jan/2006 15:04:05") request := fmt.Sprintf("%s %s %s", r.method, r.uri, r.protocol) _, _ = fmt.Fprintf(out, formatPattern, r.ip, timeFormat, request, r.status, r.responseBytes, r.elapsedTime.Seconds()) } // Write acts like a proxy passing the given bytes buffer to the ResponseWritter // and additionally counting the passed amount of bytes for logging usage. func (r *LogRecord) Write(p []byte) (int, error) { written, err := r.ResponseWriter.Write(p) r.responseBytes += int64(written) return written, err } // WriteHeader calls ResponseWriter.WriteHeader() and sets the status code func (r *LogRecord) WriteHeader(status int) { r.status = status r.ResponseWriter.WriteHeader(status) } // LogHandler maps the HTTP handler with a custom io.Writer compatible stream type LogHandler struct { handler http.Handler io io.Writer logLevel string } // NewLog creates a new logger func NewLog(handler http.Handler, io io.Writer, logLevel string) http.Handler { return &LogHandler{handler, io, logLevel} } // Implements the required method as standard HTTP handler, serving the request. func (h *LogHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { clientIP := r.RemoteAddr if colon := strings.LastIndex(clientIP, ":"); colon != -1 { clientIP = clientIP[:colon] } record := &LogRecord{ ResponseWriter: w, ip: clientIP, time: time.Time{}, method: r.Method, uri: r.RequestURI, protocol: r.Proto, status: http.StatusOK, elapsedTime: time.Duration(0), } startTime := time.Now() h.handler.ServeHTTP(record, r) finishTime := time.Now() record.time = finishTime.UTC() record.elapsedTime = finishTime.Sub(startTime) switch h.logLevel { case "error": if record.status >= http.StatusInternalServerError { record.Log(h.io) } case "warning": if record.status >= http.StatusBadRequest { record.Log(h.io) } case "info": record.Log(h.io) } }