moira
134 строки · 3.9 Кб
1package middleware
2
3import (
4"bytes"
5"context"
6"encoding/json"
7"fmt"
8"net/http"
9"runtime/debug"
10"time"
11
12"github.com/go-chi/chi/middleware"
13"github.com/go-chi/render"
14
15"go.avito.ru/DO/moira"
16"go.avito.ru/DO/moira/api"
17)
18
19type apiLoggerEntry struct {
20logger moira.Logger
21request *http.Request
22buf *bytes.Buffer
23}
24
25// GetLoggerEntry gets logger entry with configured logger
26func GetLoggerEntry(request *http.Request) moira.Logger {
27return request.Context().Value(middleware.LogEntryCtxKey).(*apiLoggerEntry).logger
28}
29
30// WithLogEntry sets to context configured logger entry
31func WithLogEntry(r *http.Request, entry *apiLoggerEntry) *http.Request {
32return r.WithContext(context.WithValue(r.Context(), middleware.LogEntryCtxKey, entry))
33}
34
35// RequestLogger is overload method of go-chi.middleware RequestLogger with custom response logging
36func RequestLogger(logger moira.Logger) func(next http.Handler) http.Handler {
37return func(next http.Handler) http.Handler {
38fn := func(writer http.ResponseWriter, request *http.Request) {
39entry := newLogEntry(logger, request)
40wrapWriter := middleware.NewWrapResponseWriter(&responseWriterWithBody{ResponseWriter: writer}, request.ProtoMajor)
41
42t1 := time.Now()
43defer func() {
44if rvr := recover(); rvr != nil {
45render.Render(wrapWriter, request, api.ErrorInternalServer(fmt.Errorf("Internal Server Error")))
46entry.writePanic(wrapWriter.Status(), wrapWriter.BytesWritten(), time.Since(t1), rvr, debug.Stack())
47} else {
48entry.write(wrapWriter.Status(), wrapWriter.BytesWritten(), time.Since(t1), wrapWriter.Unwrap())
49}
50}()
51
52next.ServeHTTP(wrapWriter, WithLogEntry(request, entry))
53}
54return http.HandlerFunc(fn)
55}
56}
57
58func getErrorResponseIfItHas(writer http.ResponseWriter) *api.ErrorResponse {
59writerWithBody := writer.(*responseWriterWithBody)
60var errResp = &api.ErrorResponse{}
61json.NewDecoder(&writerWithBody.body).Decode(errResp)
62return errResp
63}
64
65func newLogEntry(logger moira.Logger, request *http.Request) *apiLoggerEntry {
66entry := &apiLoggerEntry{
67logger: logger,
68request: request,
69buf: &bytes.Buffer{},
70}
71
72entry.buf.WriteString("\"")
73entry.buf.WriteString(fmt.Sprintf("%s ", request.Method))
74scheme := "http"
75if request.TLS != nil {
76scheme = "https"
77}
78userName := GetLogin(request)
79if userName == "" {
80userName = "anonymous"
81}
82entry.buf.WriteString(fmt.Sprintf("%s:// %s%s %s\"", scheme, request.Host, request.RequestURI, request.Proto))
83entry.buf.WriteString(" from ")
84entry.buf.WriteString(request.RemoteAddr)
85entry.buf.WriteString(" by ")
86entry.buf.WriteString(userName)
87entry.buf.WriteString(" - ")
88return entry
89}
90
91func (entry *apiLoggerEntry) write(status, bytes int, elapsed time.Duration, response http.ResponseWriter) {
92if status == 0 {
93status = 200
94}
95entry.buf.WriteString(fmt.Sprintf("%03d", status))
96entry.buf.WriteString(fmt.Sprintf(" %dB", bytes))
97entry.buf.WriteString(" in ")
98entry.buf.WriteString(fmt.Sprintf("%s", elapsed))
99if status >= 500 {
100errorResponse := getErrorResponseIfItHas(response)
101if errorResponse != nil {
102entry.buf.WriteString(fmt.Sprintf(" - Error : %s", errorResponse.ErrorText))
103}
104entry.logger.Error(entry.buf.String())
105} else {
106entry.logger.Info(entry.buf.String())
107}
108}
109
110func (entry *apiLoggerEntry) writePanic(status, bytes int, elapsed time.Duration, v interface{}, stack []byte) {
111entry.buf.WriteString(fmt.Sprintf("%03d", status))
112entry.buf.WriteString(fmt.Sprintf(" %dB", bytes))
113entry.buf.WriteString(" in ")
114entry.buf.WriteString(fmt.Sprintf("%s", elapsed))
115entry.buf.WriteString(fmt.Sprintf(" - Panic: %+v", v))
116entry.buf.WriteString("\n")
117entry.buf.WriteString(string(stack))
118
119entry.logger.Error(entry.buf.String())
120}
121
122type responseWriterWithBody struct {
123http.ResponseWriter
124body bytes.Buffer
125}
126
127func (w *responseWriterWithBody) Write(buf []byte) (int, error) {
128n, err := w.ResponseWriter.Write(buf)
129_, err2 := w.body.Write(buf[:n])
130if err == nil {
131err = err2
132}
133return n, err
134}
135