forked from h2non/imaginary
-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathlog.go
130 lines (113 loc) · 4.05 KB
/
log.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
package main
import (
"fmt"
"github.com/kumparan/go-utils"
log "github.com/sirupsen/logrus"
"io"
"net/http"
"net/url"
"strings"
"time"
)
const formatPattern = `{"remote_ip": "%s", "time": "%s", "method": "%s", "uri": "%s", "protocol": "%s", "status": "%d", "imaginary_bytes_out": %d, "imaginary_duration_in_ms": %d, "latency_human": "%s", "imaginary_request_uri_scheme": "%s"}%s`
var ignoredFields = []string{"s3", "text", "image", "font"}
// 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) {
go func(record *LogRecord) {
timeFormat := record.time.Format(time.RFC3339Nano)
splited := strings.Split(record.uri, "?")
if len(splited) <= 0 {
_, _ = fmt.Fprintf(out, formatPattern, record.ip, timeFormat, record.method, record.uri, record.protocol, record.status, record.responseBytes, record.elapsedTime.Milliseconds(), record.elapsedTime.String(), "", "\n")
return
}
if len(splited) <= 1 {
_, _ = fmt.Fprintf(out, formatPattern, record.ip, timeFormat, record.method, record.uri, record.protocol, record.status, record.responseBytes, record.elapsedTime.Milliseconds(), record.elapsedTime.String(), splited[0], "\n")
return
}
maskedURI := splited[0]
queryParam, err := url.ParseQuery(splited[1])
if err != nil {
log.WithField("queryParam", splited[1]).Error(err)
_, _ = fmt.Fprintf(out, formatPattern, record.ip, timeFormat, record.method, record.uri, record.protocol, record.status, record.responseBytes, record.elapsedTime.Milliseconds(), record.elapsedTime.String(), maskedURI, "\n")
return
}
newQueryParam := url.Values{}
for k, params := range queryParam {
if utils.Contains(ignoredFields, k) {
newQueryParam.Add(k, "_")
continue
}
for _, v := range params {
newQueryParam.Add(k, v)
}
}
maskedURI += "?" + newQueryParam.Encode()
_, _ = fmt.Fprintf(out, formatPattern, record.ip, timeFormat, record.method, record.uri, record.protocol, record.status, record.responseBytes, record.elapsedTime.Milliseconds(), record.elapsedTime.String(), maskedURI, "\n")
}(r)
}
// 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)
}
}