GoDoxy/internal/logging/accesslog/formatter.go
2025-05-10 10:46:31 +08:00

153 lines
3.8 KiB
Go

package accesslog
import (
"bytes"
"iter"
"net"
"net/http"
"strconv"
"github.com/rs/zerolog"
maxmind "github.com/yusing/go-proxy/internal/maxmind/types"
"github.com/yusing/go-proxy/internal/utils"
)
type (
CommonFormatter struct {
cfg *Fields
}
CombinedFormatter struct{ CommonFormatter }
JSONFormatter struct{ CommonFormatter }
ACLLogFormatter struct{}
)
const LogTimeFormat = "02/Jan/2006:15:04:05 -0700"
func scheme(req *http.Request) string {
if req.TLS != nil {
return "https"
}
return "http"
}
func appendRequestURI(line []byte, req *http.Request, query iter.Seq2[string, []string]) []byte {
uri := req.URL.EscapedPath()
line = append(line, uri...)
isFirst := true
for k, v := range query {
if isFirst {
line = append(line, '?')
isFirst = false
} else {
line = append(line, '&')
}
line = append(line, k...)
line = append(line, '=')
for _, v := range v {
line = append(line, v...)
}
}
return line
}
func clientIP(req *http.Request) string {
clientIP, _, err := net.SplitHostPort(req.RemoteAddr)
if err == nil {
return clientIP
}
return req.RemoteAddr
}
func (f *CommonFormatter) AppendRequestLog(line []byte, req *http.Request, res *http.Response) []byte {
query := f.cfg.Query.IterQuery(req.URL.Query())
line = append(line, req.Host...)
line = append(line, ' ')
line = append(line, clientIP(req)...)
line = append(line, " - - ["...)
line = utils.TimeNow().AppendFormat(line, LogTimeFormat)
line = append(line, `] "`...)
line = append(line, req.Method...)
line = append(line, ' ')
line = appendRequestURI(line, req, query)
line = append(line, ' ')
line = append(line, req.Proto...)
line = append(line, '"')
line = append(line, ' ')
line = strconv.AppendInt(line, int64(res.StatusCode), 10)
line = append(line, ' ')
line = strconv.AppendInt(line, res.ContentLength, 10)
return line
}
func (f *CombinedFormatter) AppendRequestLog(line []byte, req *http.Request, res *http.Response) []byte {
line = f.CommonFormatter.AppendRequestLog(line, req, res)
line = append(line, " \""...)
line = append(line, req.Referer()...)
line = append(line, "\" \""...)
line = append(line, req.UserAgent()...)
line = append(line, '"')
return line
}
func (f *JSONFormatter) AppendRequestLog(line []byte, req *http.Request, res *http.Response) []byte {
query := f.cfg.Query.ZerologQuery(req.URL.Query())
headers := f.cfg.Headers.ZerologHeaders(req.Header)
cookies := f.cfg.Cookies.ZerologCookies(req.Cookies())
contentType := res.Header.Get("Content-Type")
writer := bytes.NewBuffer(line)
logger := zerolog.New(writer)
event := logger.Info().
Str("time", utils.TimeNow().Format(LogTimeFormat)).
Str("ip", clientIP(req)).
Str("method", req.Method).
Str("scheme", scheme(req)).
Str("host", req.Host).
Str("path", req.URL.Path).
Str("protocol", req.Proto).
Int("status", res.StatusCode).
Str("type", contentType).
Int64("size", res.ContentLength).
Str("referer", req.Referer()).
Str("useragent", req.UserAgent()).
Object("query", query).
Object("headers", headers).
Object("cookies", cookies)
if res.StatusCode >= 400 {
if res.Status != "" {
event.Str("error", res.Status)
} else {
event.Str("error", http.StatusText(res.StatusCode))
}
}
// NOTE: zerolog will append a newline to the buffer
event.Send()
return writer.Bytes()
}
func (f ACLLogFormatter) AppendACLLog(line []byte, info *maxmind.IPInfo, blocked bool) []byte {
writer := bytes.NewBuffer(line)
logger := zerolog.New(writer)
event := logger.Info().
Str("time", utils.TimeNow().Format(LogTimeFormat)).
Str("ip", info.Str)
if blocked {
event.Str("action", "block")
} else {
event.Str("action", "allow")
}
if info.City != nil {
event.Str("iso_code", info.City.Country.IsoCode)
event.Str("time_zone", info.City.Location.TimeZone)
}
// NOTE: zerolog will append a newline to the buffer
event.Send()
return writer.Bytes()
}