v2.0.0 ae3e752

logging.go RawHistoryFolder

1package middleware
2
3import (
4	"net/http"
5	"time"
6
7	"gordon/internal/logging"
8	"github.com/rs/zerolog/log"
9)
10
11// ResponseWriter wraps http.ResponseWriter to capture status code and bytes written
12type ResponseWriter struct {
13	http.ResponseWriter
14	statusCode int
15	bytes      int
16}
17
18func NewResponseWriter(w http.ResponseWriter) *ResponseWriter {
19	return &ResponseWriter{
20		ResponseWriter: w,
21		statusCode:     http.StatusOK, // Default status code
22	}
23}
24
25func (rw *ResponseWriter) WriteHeader(code int) {
26	rw.statusCode = code
27	rw.ResponseWriter.WriteHeader(code)
28}
29
30func (rw *ResponseWriter) Write(b []byte) (int, error) {
31	n, err := rw.ResponseWriter.Write(b)
32	rw.bytes += n
33	return n, err
34}
35
36func (rw *ResponseWriter) StatusCode() int {
37	return rw.statusCode
38}
39
40func (rw *ResponseWriter) BytesWritten() int {
41	return rw.bytes
42}
43
44// RequestLogger is a middleware that logs HTTP requests
45func RequestLogger(next http.Handler) http.Handler {
46	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
47		start := time.Now()
48		
49		// Wrap the response writer to capture status and bytes
50		rw := NewResponseWriter(w)
51		
52		// Get client IP (handle X-Forwarded-For and X-Real-IP headers)
53		clientIP := getClientIP(r)
54		
55		// Call the next handler
56		next.ServeHTTP(rw, r)
57		
58		// Calculate duration
59		duration := time.Since(start)
60		
61		// Log the request using proxy logger
62		logging.ProxyLogger.Info().
63			Str("method", r.Method).
64			Str("path", r.URL.Path).
65			Str("query", r.URL.RawQuery).
66			Str("host", r.Host).
67			Str("user_agent", r.UserAgent()).
68			Str("referer", r.Referer()).
69			Str("client_ip", clientIP).
70			Int("status", rw.StatusCode()).
71			Int("bytes", rw.BytesWritten()).
72			Dur("duration", duration).
73			Str("proto", r.Proto).
74			Msg("HTTP Request")
75	})
76}
77
78// getClientIP extracts the client IP from various headers
79func getClientIP(r *http.Request) string {
80	// Check X-Forwarded-For header (comma-separated list, first is original client)
81	if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
82		// Take the first IP (original client)
83		for idx := 0; idx < len(xff); idx++ {
84			if xff[idx] == ',' {
85				return xff[:idx]
86			}
87		}
88		return xff
89	}
90	
91	// Check X-Real-IP header
92	if xri := r.Header.Get("X-Real-IP"); xri != "" {
93		return xri
94	}
95	
96	// Check X-Forwarded header (less common)
97	if xf := r.Header.Get("X-Forwarded"); xf != "" {
98		return xf
99	}
100	
101	// Fallback to RemoteAddr
102	return r.RemoteAddr
103}
104
105// ContainerRequestLogger logs requests with container-specific information
106func ContainerRequestLogger(containerID string, domain string) func(http.Handler) http.Handler {
107	return func(next http.Handler) http.Handler {
108		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
109			start := time.Now()
110			
111			// Wrap the response writer to capture status and bytes
112			rw := NewResponseWriter(w)
113			
114			// Get client IP
115			clientIP := getClientIP(r)
116			
117			// Call the next handler
118			next.ServeHTTP(rw, r)
119			
120			// Calculate duration
121			duration := time.Since(start)
122			
123			// Log the request with container information using proxy logger
124			logging.ProxyLogger.Info().
125				Str("method", r.Method).
126				Str("path", r.URL.Path).
127				Str("query", r.URL.RawQuery).
128				Str("host", r.Host).
129				Str("domain", domain).
130				Str("container_id", containerID).
131				Str("user_agent", r.UserAgent()).
132				Str("referer", r.Referer()).
133				Str("client_ip", clientIP).
134				Int("status", rw.StatusCode()).
135				Int("bytes", rw.BytesWritten()).
136				Dur("duration", duration).
137				Str("proto", r.Proto).
138				Msg("Container Request")
139		})
140	}
141}
142
143// PanicRecovery middleware recovers from panics and logs them
144func PanicRecovery(next http.Handler) http.Handler {
145	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
146		defer func() {
147			if err := recover(); err != nil {
148				log.Error().
149					Interface("panic", err).
150					Str("method", r.Method).
151					Str("path", r.URL.Path).
152					Str("host", r.Host).
153					Msg("Panic recovered")
154				
155				http.Error(w, "Internal Server Error", http.StatusInternalServerError)
156			}
157		}()
158		
159		next.ServeHTTP(w, r)
160	})
161}
162
163// CORS middleware adds CORS headers
164func CORS(next http.Handler) http.Handler {
165	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
166		w.Header().Set("Access-Control-Allow-Origin", "*")
167		w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
168		w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
169		
170		// Handle preflight OPTIONS request
171		if r.Method == "OPTIONS" {
172			w.WriteHeader(http.StatusOK)
173			return
174		}
175		
176		next.ServeHTTP(w, r)
177	})
178}
179
180// Chain combines multiple middleware functions
181func Chain(middlewares ...func(http.Handler) http.Handler) func(http.Handler) http.Handler {
182	return func(final http.Handler) http.Handler {
183		for i := len(middlewares) - 1; i >= 0; i-- {
184			final = middlewares[i](final)
185		}
186		return final
187	}
188}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 7

Tags 106

↑↓ moveenter openesc close