2024-04-07 23:41:00 +07:00
|
|
|
package middleware
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"log/slog"
|
|
|
|
"net/http"
|
2024-04-08 15:48:45 +07:00
|
|
|
"strings"
|
2024-04-07 23:41:00 +07:00
|
|
|
"time"
|
|
|
|
|
|
|
|
chimiddleware "github.com/go-chi/chi/v5/middleware"
|
|
|
|
"github.com/tigorlazuardi/redmage/pkg/log"
|
|
|
|
)
|
|
|
|
|
|
|
|
type ChiLogger struct{}
|
|
|
|
|
|
|
|
func (ChiLogger) NewLogEntry(r *http.Request) chimiddleware.LogEntry {
|
|
|
|
return &ChiEntry{request: r}
|
|
|
|
}
|
|
|
|
|
|
|
|
type ChiEntry struct {
|
|
|
|
request *http.Request
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ch *ChiEntry) Write(status int, bytes int, header http.Header, elapsed time.Duration, extra interface{}) {
|
2024-04-08 15:48:45 +07:00
|
|
|
elasedStr := formatDuration(elapsed)
|
2024-04-08 16:20:16 +07:00
|
|
|
message := fmt.Sprintf("%s %s %d %s", ch.request.Method, ch.request.URL, status, elasedStr)
|
2024-04-07 23:41:00 +07:00
|
|
|
|
|
|
|
requestLog := slog.Attr{Key: "request", Value: ch.extractRequestLog()}
|
2024-04-24 13:01:13 +07:00
|
|
|
responseLog := slog.Group("response", "status", status, "headers", flat(header), "bytes", bytes)
|
2024-04-08 15:48:45 +07:00
|
|
|
roundtripLog := slog.String("elapsed", elasedStr)
|
2024-04-07 23:41:00 +07:00
|
|
|
|
2024-04-08 15:48:45 +07:00
|
|
|
group := slog.Group("http", requestLog, responseLog, roundtripLog)
|
2024-04-07 23:41:00 +07:00
|
|
|
if status >= 400 {
|
2024-04-08 15:48:45 +07:00
|
|
|
log.New(ch.request.Context()).With(group).Error(message)
|
2024-04-07 23:41:00 +07:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-04-08 15:48:45 +07:00
|
|
|
log.New(ch.request.Context()).With(group).Info(message)
|
2024-04-07 23:41:00 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
func (ch *ChiEntry) Panic(v interface{}, stack []byte) {
|
2024-04-08 15:48:45 +07:00
|
|
|
group := slog.Group("http", slog.Attr{Key: "request", Value: ch.extractRequestLog()})
|
|
|
|
entry := log.New(ch.request.Context())
|
|
|
|
message := fmt.Sprintf("[PANIC] %s %s", ch.request.Method, ch.request.URL)
|
2024-04-07 23:41:00 +07:00
|
|
|
if err, ok := v.(error); ok {
|
2024-04-08 15:48:45 +07:00
|
|
|
entry.Err(err).With(group).Error(message, "stack", string(stack))
|
2024-04-07 23:41:00 +07:00
|
|
|
} else {
|
2024-04-08 15:48:45 +07:00
|
|
|
entry.With(group).Error(message, "panic_data", v, "stack", string(stack))
|
2024-04-07 23:41:00 +07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ch *ChiEntry) extractRequestLog() slog.Value {
|
|
|
|
values := make([]slog.Attr, 0, 4)
|
|
|
|
values = append(values,
|
|
|
|
slog.String("method", ch.request.Method),
|
|
|
|
slog.String("path", ch.request.URL.Path),
|
|
|
|
)
|
|
|
|
queries := ch.request.URL.Query()
|
|
|
|
if len(queries) > 0 {
|
2024-04-24 13:01:13 +07:00
|
|
|
values = append(values, slog.Any("query", flat(queries)))
|
2024-04-07 23:41:00 +07:00
|
|
|
}
|
2024-04-24 13:01:13 +07:00
|
|
|
values = append(values, slog.Any("headers", flat(ch.request.Header)))
|
2024-04-07 23:41:00 +07:00
|
|
|
return slog.GroupValue(values...)
|
|
|
|
}
|
2024-04-08 15:48:45 +07:00
|
|
|
|
2024-04-24 13:01:13 +07:00
|
|
|
func flat(header map[string][]string) map[string]string {
|
2024-04-08 15:48:45 +07:00
|
|
|
m := make(map[string]string, len(header))
|
|
|
|
|
|
|
|
for k := range header {
|
2024-04-10 22:38:19 +07:00
|
|
|
m[k] = strings.Join(header[k], ", ")
|
2024-04-08 15:48:45 +07:00
|
|
|
}
|
|
|
|
return m
|
|
|
|
}
|
|
|
|
|
|
|
|
func formatDuration(dur time.Duration) string {
|
|
|
|
nanosecs := float64(dur)
|
|
|
|
|
|
|
|
return fmt.Sprintf("%.3fms", nanosecs/float64(time.Millisecond))
|
|
|
|
}
|
2024-04-08 21:50:52 +07:00
|
|
|
|
|
|
|
type ChiSimpleLogger struct{}
|
|
|
|
|
|
|
|
func (ChiSimpleLogger) NewLogEntry(r *http.Request) chimiddleware.LogEntry {
|
|
|
|
return &ChiSimpleEntry{request: r}
|
|
|
|
}
|
|
|
|
|
|
|
|
type ChiSimpleEntry struct {
|
|
|
|
request *http.Request
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ch *ChiSimpleEntry) Write(status int, bytes int, header http.Header, elapsed time.Duration, extra interface{}) {
|
|
|
|
elapsedStr := formatDuration(elapsed)
|
|
|
|
message := fmt.Sprintf("%s %s %d %s", ch.request.Method, ch.request.URL, status, elapsedStr)
|
|
|
|
|
|
|
|
level := slog.LevelInfo
|
|
|
|
if status >= 400 {
|
|
|
|
level = slog.LevelError
|
|
|
|
}
|
|
|
|
log.New(ch.request.Context()).Level(level).Log(message)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ch *ChiSimpleEntry) Panic(v interface{}, stack []byte) {
|
|
|
|
(&ChiEntry{ch.request}).Panic(v, stack)
|
|
|
|
}
|