feat: verbose mode + custom attributes
This commit is contained in:
@@ -82,15 +82,25 @@ router.Run(":1234")
|
||||
// time=2023-04-10T14:00:0.000000Z level=INFO msg="Incoming request" status=200 method=GET path=/pong route=/pong ip=127.0.0.1 latency=25.5µs user-agent=curl/7.77.0 time=2023-04-10T14:00:00.000Z
|
||||
```
|
||||
|
||||
### Verbose
|
||||
|
||||
```go
|
||||
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
||||
|
||||
config := sloggin.Config{
|
||||
WithRequestBody: true,
|
||||
WithResponseBody: true,
|
||||
WithRequestHeader: true,
|
||||
WithResponseHeader: true,
|
||||
}
|
||||
|
||||
router := chi.NewRouter()
|
||||
router.Use(sloggin.NewWithConfig(logger, config))
|
||||
```
|
||||
|
||||
### Filters
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
sloggin "github.com/samber/slog-gin"
|
||||
"log/slog"
|
||||
)
|
||||
|
||||
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
||||
|
||||
router := gin.New()
|
||||
@@ -164,14 +174,6 @@ router.Run(":1234")
|
||||
### Using custom logger sub-group
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
sloggin "github.com/samber/slog-gin"
|
||||
"log/slog"
|
||||
)
|
||||
|
||||
// Create a slog logger, which:
|
||||
// - Logs to stdout.
|
||||
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
||||
|
||||
router := gin.New()
|
||||
@@ -194,14 +196,6 @@ router.Run(":1234")
|
||||
### Add logger to a single route
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
sloggin "github.com/samber/slog-gin"
|
||||
"log/slog"
|
||||
)
|
||||
|
||||
// Create a slog logger, which:
|
||||
// - Logs to stdout.
|
||||
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
||||
|
||||
router := gin.New()
|
||||
@@ -221,14 +215,6 @@ router.Run(":1234")
|
||||
### Adding custom attributes
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
sloggin "github.com/samber/slog-gin"
|
||||
"log/slog"
|
||||
)
|
||||
|
||||
// Create a slog logger, which:
|
||||
// - Logs to stdout.
|
||||
logger := slog.New(slog.NewTextHandler(os.Stdout, nil)).
|
||||
With("environment", "production").
|
||||
With("server", "gin/1.9.0").
|
||||
@@ -243,26 +229,20 @@ router.Use(sloggin.New(logger))
|
||||
|
||||
// Example pong request.
|
||||
router.GET("/pong", func(c *gin.Context) {
|
||||
// Add an attribute to a single log entry.
|
||||
sloggin.AddCustomAttributes(c, slog.String("foo", "bar"))
|
||||
c.String(http.StatusOK, "pong")
|
||||
})
|
||||
|
||||
router.Run(":1234")
|
||||
|
||||
// output:
|
||||
// time=2023-04-10T14:00:0.000000+02:00 level=INFO msg="Incoming request" environment=production server=gin/1.9.0 gin_mode=release server_start_time=2023-04-10T10:00:00.000+02:00 status=200 method=GET path=/pong route=/pong ip=127.0.0.1 latency=25.5µs user-agent=curl/7.77.0 time=2023-04-10T14:00:00.000+02:00
|
||||
// time=2023-04-10T14:00:0.000000+02:00 level=INFO msg="Incoming request" environment=production server=gin/1.9.0 gin_mode=release server_start_time=2023-04-10T10:00:00.000+02:00 status=200 method=GET path=/pong route=/pong ip=127.0.0.1 latency=25.5µs user-agent=curl/7.77.0 time=2023-04-10T14:00:00.000+02:00 foo=bar
|
||||
```
|
||||
|
||||
### JSON output
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
sloggin "github.com/samber/slog-gin"
|
||||
"log/slog"
|
||||
)
|
||||
|
||||
// Create a slog logger, which:
|
||||
// - Logs to stdout.
|
||||
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
|
||||
|
||||
router := gin.New()
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package sloggin
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type bodyWriter struct {
|
||||
gin.ResponseWriter
|
||||
body *bytes.Buffer
|
||||
}
|
||||
|
||||
// implements gin.ResponseWriter
|
||||
func (w bodyWriter) Write(b []byte) (int, error) {
|
||||
w.body.Write(b)
|
||||
return w.ResponseWriter.Write(b)
|
||||
}
|
||||
|
||||
func newBodyWriter(writer gin.ResponseWriter) *bodyWriter {
|
||||
return &bodyWriter{
|
||||
body: bytes.NewBufferString(""),
|
||||
ResponseWriter: writer,
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ func main() {
|
||||
slogformatter.TimezoneConverter(time.UTC),
|
||||
slogformatter.TimeFormatter(time.RFC3339, nil),
|
||||
)(
|
||||
slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{}),
|
||||
slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{}),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -33,9 +33,12 @@ func main() {
|
||||
// Add the sloggin middleware to all routes.
|
||||
// The middleware will log all requests attributes under a "http" group.
|
||||
router.Use(sloggin.New(logger))
|
||||
// config := sloggin.Config{WithRequestBody: true, WithResponseBody: true, WithRequestHeader: true, WithResponseHeader: true}
|
||||
// router.Use(sloggin.NewWithConfig(logger, config))
|
||||
|
||||
// Example pong request.
|
||||
router.GET("/pong", func(c *gin.Context) {
|
||||
sloggin.AddCustomAttributes(c, slog.String("foo", "bar"))
|
||||
c.String(http.StatusOK, "pong")
|
||||
})
|
||||
router.GET("/pong/:id", func(c *gin.Context) {
|
||||
@@ -43,8 +46,8 @@ func main() {
|
||||
})
|
||||
|
||||
logger.Info("Starting server")
|
||||
if err := router.Run(":1234"); err != nil {
|
||||
logger.Error("can' start server with 1234 port")
|
||||
if err := router.Run(":4242"); err != nil {
|
||||
logger.Error("can' start server with 4242 port")
|
||||
}
|
||||
|
||||
// output:
|
||||
+100
-4
@@ -1,8 +1,11 @@
|
||||
package sloggin
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"log/slog"
|
||||
@@ -11,14 +14,35 @@ import (
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
const requestIDCtx = "slog-gin.request-id"
|
||||
const (
|
||||
customAttributesCtxKey = "slog-gin.custom-attributes"
|
||||
requestIDCtx = "slog-gin.request-id"
|
||||
)
|
||||
|
||||
var (
|
||||
HiddenRequestHeaders = map[string]struct{}{
|
||||
"authorization": {},
|
||||
"cookie": {},
|
||||
"set-cookie": {},
|
||||
"x-auth-token": {},
|
||||
"x-csrf-token": {},
|
||||
"x-xsrf-token": {},
|
||||
}
|
||||
HiddenResponseHeaders = map[string]struct{}{
|
||||
"set-cookie": {},
|
||||
}
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
DefaultLevel slog.Level
|
||||
ClientErrorLevel slog.Level
|
||||
ServerErrorLevel slog.Level
|
||||
|
||||
WithRequestID bool
|
||||
WithRequestID bool
|
||||
WithRequestBody bool
|
||||
WithRequestHeader bool
|
||||
WithResponseBody bool
|
||||
WithResponseHeader bool
|
||||
|
||||
Filters []Filter
|
||||
}
|
||||
@@ -33,7 +57,11 @@ func New(logger *slog.Logger) gin.HandlerFunc {
|
||||
ClientErrorLevel: slog.LevelWarn,
|
||||
ServerErrorLevel: slog.LevelError,
|
||||
|
||||
WithRequestID: true,
|
||||
WithRequestID: true,
|
||||
WithRequestBody: false,
|
||||
WithRequestHeader: false,
|
||||
WithResponseBody: false,
|
||||
WithResponseHeader: false,
|
||||
|
||||
Filters: []Filter{},
|
||||
})
|
||||
@@ -49,7 +77,11 @@ func NewWithFilters(logger *slog.Logger, filters ...Filter) gin.HandlerFunc {
|
||||
ClientErrorLevel: slog.LevelWarn,
|
||||
ServerErrorLevel: slog.LevelError,
|
||||
|
||||
WithRequestID: true,
|
||||
WithRequestID: true,
|
||||
WithRequestBody: false,
|
||||
WithRequestHeader: false,
|
||||
WithResponseBody: false,
|
||||
WithResponseHeader: false,
|
||||
|
||||
Filters: filters,
|
||||
})
|
||||
@@ -67,6 +99,21 @@ func NewWithConfig(logger *slog.Logger, config Config) gin.HandlerFunc {
|
||||
c.Header("X-Request-ID", requestID)
|
||||
}
|
||||
|
||||
// dump request body
|
||||
var reqBody []byte
|
||||
if config.WithRequestBody {
|
||||
buf, err := io.ReadAll(c.Request.Body)
|
||||
if err == nil {
|
||||
c.Request.Body = io.NopCloser(bytes.NewBuffer(buf))
|
||||
reqBody = buf
|
||||
}
|
||||
}
|
||||
|
||||
// dump response body
|
||||
if config.WithResponseBody {
|
||||
c.Writer = newBodyWriter(c.Writer)
|
||||
}
|
||||
|
||||
c.Next()
|
||||
|
||||
end := time.Now()
|
||||
@@ -87,6 +134,42 @@ func NewWithConfig(logger *slog.Logger, config Config) gin.HandlerFunc {
|
||||
attributes = append(attributes, slog.String("request-id", requestID))
|
||||
}
|
||||
|
||||
// request
|
||||
if config.WithRequestBody {
|
||||
attributes = append(attributes, slog.Group("request", slog.String("body", string(reqBody))))
|
||||
}
|
||||
if config.WithRequestHeader {
|
||||
for k, v := range c.Request.Header {
|
||||
if _, found := HiddenRequestHeaders[strings.ToLower(k)]; found {
|
||||
continue
|
||||
}
|
||||
attributes = append(attributes, slog.Group("request", slog.Group("header", slog.Any(k, v))))
|
||||
}
|
||||
}
|
||||
|
||||
// response
|
||||
if config.WithResponseBody {
|
||||
if w, ok := c.Writer.(*bodyWriter); ok {
|
||||
attributes = append(attributes, slog.Group("response", slog.String("body", w.body.String())))
|
||||
}
|
||||
}
|
||||
if config.WithResponseHeader {
|
||||
for k, v := range c.Writer.Header() {
|
||||
if _, found := HiddenResponseHeaders[strings.ToLower(k)]; found {
|
||||
continue
|
||||
}
|
||||
attributes = append(attributes, slog.Group("response", slog.Group("header", slog.Any(k, v))))
|
||||
}
|
||||
}
|
||||
|
||||
// custom context values
|
||||
if v, ok := c.Get(customAttributesCtxKey); ok {
|
||||
switch attrs := v.(type) {
|
||||
case []slog.Attr:
|
||||
attributes = append(attributes, attrs...)
|
||||
}
|
||||
}
|
||||
|
||||
for _, filter := range config.Filters {
|
||||
if !filter(c) {
|
||||
return
|
||||
@@ -117,3 +200,16 @@ func GetRequestID(c *gin.Context) string {
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func AddCustomAttributes(c *gin.Context, attr slog.Attr) {
|
||||
v, exists := c.Get(customAttributesCtxKey)
|
||||
if !exists {
|
||||
c.Set(customAttributesCtxKey, []slog.Attr{attr})
|
||||
return
|
||||
}
|
||||
|
||||
switch attrs := v.(type) {
|
||||
case []slog.Attr:
|
||||
c.Set(customAttributesCtxKey, append(attrs, attr))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user