New logging middleware that captures response
This commit is contained in:
5
go.mod
5
go.mod
@@ -2,10 +2,7 @@ module git.nice.net.nz/hads/snice
|
|||||||
|
|
||||||
go 1.21.3
|
go 1.21.3
|
||||||
|
|
||||||
require (
|
require github.com/urfave/cli/v2 v2.25.7
|
||||||
github.com/urfave/cli/v2 v2.25.7
|
|
||||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d
|
|
||||||
)
|
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||||
|
|||||||
2
go.sum
2
go.sum
@@ -6,5 +6,3 @@ github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs=
|
|||||||
github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
|
github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
|
||||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
|
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
|
||||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
|
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
|
||||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
|
|
||||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
|
|
||||||
|
|||||||
107
main.go
107
main.go
@@ -3,33 +3,97 @@ package main
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"log/slog"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"golang.org/x/exp/slog"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func serveLogger(logger *log.Logger, next http.Handler) http.Handler {
|
type (
|
||||||
|
// struct for holding response details
|
||||||
|
responseData struct {
|
||||||
|
status int
|
||||||
|
size int
|
||||||
|
}
|
||||||
|
|
||||||
|
// our http.ResponseWriter implementation
|
||||||
|
loggingResponseWriter struct {
|
||||||
|
http.ResponseWriter // compose original http.ResponseWriter
|
||||||
|
responseData *responseData
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r *loggingResponseWriter) Write(b []byte) (int, error) {
|
||||||
|
size, err := r.ResponseWriter.Write(b) // write response using original http.ResponseWriter
|
||||||
|
r.responseData.size += size // capture size
|
||||||
|
return size, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *loggingResponseWriter) WriteHeader(statusCode int) {
|
||||||
|
r.ResponseWriter.WriteHeader(statusCode) // write status code using original http.ResponseWriter
|
||||||
|
r.responseData.status = statusCode // capture status code
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithLogging(h http.Handler) http.Handler {
|
||||||
|
loggingFn := func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
start := time.Now()
|
||||||
|
|
||||||
|
responseData := &responseData{
|
||||||
|
status: 0,
|
||||||
|
size: 0,
|
||||||
|
}
|
||||||
|
lrw := loggingResponseWriter{
|
||||||
|
ResponseWriter: w, // compose original http.ResponseWriter
|
||||||
|
responseData: responseData,
|
||||||
|
}
|
||||||
|
h.ServeHTTP(&lrw, r) // inject our implementation of http.ResponseWriter
|
||||||
|
|
||||||
|
duration := time.Since(start)
|
||||||
|
|
||||||
|
slog.Info("Request:",
|
||||||
|
slog.String("method", r.Method),
|
||||||
|
slog.String("path", r.RequestURI),
|
||||||
|
slog.String("url", r.URL.Path),
|
||||||
|
slog.String("host", r.Host),
|
||||||
|
slog.Int("status", responseData.status),
|
||||||
|
slog.Int64("duration", duration.Microseconds()),
|
||||||
|
slog.Int("size", responseData.size),
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
return http.HandlerFunc(loggingFn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoggingMiddleware(logger *slog.Logger, next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
remoteHost, _, _ := strings.Cut(r.RemoteAddr, ":")
|
|
||||||
logger.Printf("%v %v %v\n", remoteHost, r.Method, r.URL.Path)
|
|
||||||
next.ServeHTTP(w, r)
|
next.ServeHTTP(w, r)
|
||||||
|
logger.Info("Request:",
|
||||||
|
slog.String("method", r.Method),
|
||||||
|
slog.String("path", r.RequestURI),
|
||||||
|
slog.String("url", r.URL.Path),
|
||||||
|
slog.String("host", r.Host),
|
||||||
|
)
|
||||||
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func timeHandler(w http.ResponseWriter, r *http.Request) {
|
func pingHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
fmt.Fprint(w, time.Now().Format("02 Jan 2006 15:04:05 MST"))
|
w.Write([]byte("OK"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// handler := slog.NewJSONHandler(os.Stdout, nil)
|
logLevel := &slog.LevelVar{} // INFO
|
||||||
handler := slog.NewTextHandler(os.Stdout, nil)
|
|
||||||
slog.SetDefault(slog.New(handler))
|
opts := slog.HandlerOptions{
|
||||||
logger := slog.NewLogLogger(handler, slog.LevelError)
|
Level: logLevel,
|
||||||
|
}
|
||||||
|
logLevel.Set(slog.LevelDebug)
|
||||||
|
handler := slog.NewTextHandler(os.Stdout, &opts)
|
||||||
|
logger := slog.New(handler)
|
||||||
|
slog.SetDefault(logger)
|
||||||
|
|
||||||
var host string
|
var host string
|
||||||
var port string
|
var port string
|
||||||
@@ -49,23 +113,21 @@ func main() {
|
|||||||
var addr string = host + ":" + port
|
var addr string = host + ":" + port
|
||||||
|
|
||||||
srv := &http.Server{
|
srv := &http.Server{
|
||||||
Addr: addr,
|
Addr: addr,
|
||||||
ErrorLog: logger,
|
|
||||||
}
|
}
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
mux.HandleFunc("/time", timeHandler)
|
mux.Handle("/ping", http.HandlerFunc(pingHandler))
|
||||||
fileHandler := serveLogger(logger, http.FileServer(http.Dir(directory)))
|
fileHandler := http.FileServer(http.Dir(directory))
|
||||||
mux.Handle("/", fileHandler)
|
mux.Handle("/", fileHandler)
|
||||||
srv.Handler = mux
|
srv.Handler = WithLogging(mux)
|
||||||
listener, err := net.Listen("tcp", addr)
|
listener, err := net.Listen("tcp", addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Println(err)
|
logger.Error("Listen error", err)
|
||||||
}
|
}
|
||||||
scheme := "http://"
|
logger.Info(fmt.Sprintf("Serving directory %q on http://%v", directory, listener.Addr()))
|
||||||
logger.Printf("Serving directory %q on %v%v", directory, scheme, listener.Addr())
|
|
||||||
err = srv.Serve(listener)
|
err = srv.Serve(listener)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Println(err)
|
logger.Error("Serve error", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
@@ -81,7 +143,7 @@ func main() {
|
|||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "host",
|
Name: "host",
|
||||||
EnvVars: []string{"HOST"},
|
EnvVars: []string{"HOST"},
|
||||||
Value: "127.0.0.1",
|
Value: "0.0.0.0",
|
||||||
Usage: "Host to listen on",
|
Usage: "Host to listen on",
|
||||||
Destination: &host,
|
Destination: &host,
|
||||||
}},
|
}},
|
||||||
@@ -91,10 +153,11 @@ func main() {
|
|||||||
Aliases: []string{"hc"},
|
Aliases: []string{"hc"},
|
||||||
Usage: "Call healthcheck endpoint",
|
Usage: "Call healthcheck endpoint",
|
||||||
Action: func(cCtx *cli.Context) error {
|
Action: func(cCtx *cli.Context) error {
|
||||||
_, err := http.Get(fmt.Sprintf("http://127.0.0.1:%s/health", port))
|
_, err := http.Get(fmt.Sprintf("http://127.0.0.1:%s/ping", port))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
os.Exit(0)
|
||||||
return nil
|
return nil
|
||||||
}}},
|
}}},
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
|
|||||||
Reference in New Issue
Block a user