Skip to content

Commit

Permalink
feat: verbose mode + custom attributes
Browse files Browse the repository at this point in the history
  • Loading branch information
samber committed Oct 15, 2023
1 parent 1546b40 commit 2fa3d7e
Show file tree
Hide file tree
Showing 4 changed files with 150 additions and 46 deletions.
58 changes: 19 additions & 39 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```

### Filters
### Verbose

```go
import (
"github.com/gin-gonic/gin"
sloggin "github.com/samber/slog-gin"
"log/slog"
)
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
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))

router := gin.New()
Expand Down Expand Up @@ -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()
Expand All @@ -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()
Expand All @@ -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").
Expand All @@ -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()
Expand Down
25 changes: 25 additions & 0 deletions dump.go
Original file line number Diff line number Diff line change
@@ -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,
}
}
9 changes: 6 additions & 3 deletions examples/example.go → example/example.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{}),
),
)

Expand All @@ -33,18 +33,21 @@ 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) {
c.String(http.StatusOK, "pong")
})

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:
Expand Down
104 changes: 100 additions & 4 deletions middleware.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package sloggin

import (
"bytes"
"context"
"io"
"net/http"
"strings"
"time"

"log/slog"
Expand All @@ -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
}
Expand All @@ -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{},
})
Expand All @@ -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,
})
Expand All @@ -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()
Expand All @@ -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
Expand Down Expand Up @@ -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))
}
}

0 comments on commit 2fa3d7e

Please sign in to comment.