-
Notifications
You must be signed in to change notification settings - Fork 1
/
access_logs.go
187 lines (167 loc) · 5.42 KB
/
access_logs.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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
package main
import (
"bufio"
"fmt"
"log"
"net"
"net/http"
"os"
"strings"
"time"
)
const (
// ANSI color codes for access logs
ANSIReset = "\033[0m"
ANSIRed = "\033[31m"
ANSIGreen = "\033[32m"
ANSIYellow = "\033[33m"
ANSIPurple = "\033[35m"
ANSIFaint = "\033[2m"
ANSIBold = "\033[1m"
ANSICyan = "\033[36m"
ANSIBgRed = "\033[101m"
ANSIBgBlue = "\033[104m"
ANSIBgMagenta = "\033[105m"
)
func isColorTerminal() bool {
// NOTE: hack
return os.Getenv("TERM") == "xterm-256color"
}
// getClientIP retrieves the client IP address from the request,
// considering the X-Forwarded-For header if present.
func getClientIP(r *http.Request) string {
if useXForwardedFor {
xff := r.Header.Get("X-Forwarded-For")
if xff != "" {
ips := strings.Split(xff, ",")
return strings.TrimSpace(ips[0])
}
}
host, _, _ := net.SplitHostPort(r.RemoteAddr)
return host
}
// captureStatusCodeResponseWriter is a custom http.ResponseWriter that captures the status code
type captureStatusCodeResponseWriter struct {
http.ResponseWriter
statusCode int
}
// websocket doesn't work without this
func (u *captureStatusCodeResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
return u.ResponseWriter.(http.Hijacker).Hijack()
}
// just for good measure
func (u *captureStatusCodeResponseWriter) Flush() {
u.ResponseWriter.(http.Flusher).Flush()
}
func (u *captureStatusCodeResponseWriter) Push(target string, opts *http.PushOptions) error {
return u.ResponseWriter.(http.Pusher).Push(target, opts)
}
// newResponseWriter creates a new captureStatusCodeResponseWriter
func newResponseWriter(w http.ResponseWriter) *captureStatusCodeResponseWriter {
return &captureStatusCodeResponseWriter{w, http.StatusOK}
}
// WriteHeader captures the status code
func (rw *captureStatusCodeResponseWriter) WriteHeader(code int) {
rw.statusCode = code
rw.ResponseWriter.WriteHeader(code)
}
// logRequest logs each request in Apache/Nginx standard format with ANSI colors
func logRequest(handler http.Handler, ignoredPaths []string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Check if the request path should be ignored
for _, ignoredPath := range ignoredPaths {
if strings.HasPrefix(r.URL.Path, ignoredPath) {
// If path matches one of the ignored paths, just serve the request without logging
handler.ServeHTTP(w, r)
return
}
}
start := time.Now()
rw := newResponseWriter(w)
handler.ServeHTTP(rw, r)
status := rw.statusCode
latency := time.Since(start)
clientIP := getClientIP(r)
log.SetFlags(log.Flags() &^ (log.Ldate | log.Ltime))
if isColorTerminal() || lumberjackLogger == nil {
statusColor := ANSIGreen
// Determine the status color
if status >= 400 && status < 500 {
statusColor = ANSIYellow
} else if status >= 500 {
statusColor = ANSIRed
}
latencyColor := getLatencyGradientColor(latency)
clientIPColor := ANSICyan
if r.Header.Get("X-Forwarded-For") != "" {
clientIPColor = ANSIBgMagenta
}
var query string
if r.URL.RawQuery != "" {
query += "?"
}
query += r.URL.RawQuery
queryColored := colorQueryParameters(query)
// so many colors.....
log.Println(clientIPColor + clientIP + ANSIReset +
" - - [" + start.Format("02/Jan/2006:15:04:05 -0700") + "] \"" +
ANSIGreen + r.Method + " " + r.URL.Path + queryColored + " " + ANSIReset +
ANSIFaint + r.Proto + ANSIReset + "\" " +
statusColor + fmt.Sprint(status) + ANSIReset + " " +
fmt.Sprint(r.ContentLength) + " \"" +
ANSIPurple + r.Referer() + ANSIReset + "\" \"" +
ANSIFaint + r.UserAgent() + ANSIReset + "\" " +
latencyColor + fmt.Sprint(latency) + ANSIReset)
} else {
// apache/nginx request format with latency at the end
log.Println(clientIP + " - - [" + start.Format("02/Jan/2006:15:04:05 -0700") + "] \"" +
r.Method + " " + r.RequestURI + " " + r.Proto + "\" " +
fmt.Sprint(status) + " " + fmt.Sprint(r.ContentLength) + " \"" +
r.Referer() + "\" \"" + r.UserAgent() + "\" " +
fmt.Sprint(latency))
}
log.SetFlags(0)
})
}
// Color ranges for latency gradient
var latencyColors = []string{
"\033[38;5;39m", // Blue
"\033[38;5;51m", // Light blue
"\033[38;5;27m", // Added color (Dark blue)
"\033[38;5;82m", // Green
"\033[38;5;34m", // Added color (Forest green)
"\033[38;5;154m", // Light green
"\033[38;5;220m", // Yellow
"\033[38;5;208m", // Orange
"\033[38;5;198m", // Light red
}
// getLatencyGradientColor returns a gradient color based on the latency
func getLatencyGradientColor(latency time.Duration) string {
millis := latency.Milliseconds()
// Define latency thresholds
thresholds := []int64{40, 60, 85, 100, 150, 230, 400, 600}
for i, threshold := range thresholds {
if millis < threshold {
return latencyColors[i]
}
}
return latencyColors[len(latencyColors)-1]
}
// colorQueryParameters colors the query parameters
func colorQueryParameters(query string) string {
if query == "" {
return ""
}
// NOTE: the question mark and first query key are colored the same
params := strings.Split(query, "&")
var coloredParams []string
for _, param := range params {
keyValue := strings.Split(param, "=")
if len(keyValue) == 2 {
coloredParams = append(coloredParams, fmt.Sprintf("%s%s%s=%s%s%s", ANSICyan, keyValue[0], ANSIReset, ANSIYellow, keyValue[1], ANSIReset))
} else {
coloredParams = append(coloredParams, param)
}
}
return strings.Join(coloredParams, "&")
}