Skip to content

Commit da3963e

Browse files
committed
Implement httplog.LogRequestBody(ctx) and Resp overrides
1 parent aedf3a5 commit da3963e

File tree

5 files changed

+131
-110
lines changed

5 files changed

+131
-110
lines changed

README.md

+11-9
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,17 @@ A small but powerful structured logging package for HTTP request logging, built
1212

1313
## Example
1414

15-
See [_example/main.go](./_example/main.go) and try running it locally:
15+
See [_example/main.go](./_example/main.go). Try running it locally:
1616
```sh
17-
$ go run github.com/golang-cz/httplog/_example
17+
$ ENV=production go run github.com/golang-cz/httplog/_example
1818

1919
$ ENV=localhost go run github.com/golang-cz/httplog/_example
2020
```
2121

2222
## Usage
2323

24+
`go get github.com/golang-cz/httplog@latest`
25+
2426
```go
2527
package main
2628

@@ -48,16 +50,16 @@ func main() {
4850

4951
// Request logger
5052
r.Use(httplog.RequestLogger(logger, &httplog.Options{
51-
// Level defines the verbosity of the requests logs:
53+
// Level defines the verbosity of the request logs:
5254
// slog.LevelDebug - log both request starts & responses (incl. OPTIONS)
53-
// slog.LevelInfo - log responses (excl. OPTIONS)
55+
// slog.LevelInfo - log all responses (excl. OPTIONS)
5456
// slog.LevelWarn - log 4xx and 5xx responses only (except for 429)
5557
// slog.LevelError - log 5xx responses only
5658
Level: slog.LevelInfo,
5759

5860
// Concise mode causes fewer log attributes to be printed in request logs.
5961
// This is useful if your console is too noisy during development.
60-
Concise: isLocalhost,
62+
Concise: false,
6163

6264
// RecoverPanics recovers from panics occurring in the underlying HTTP handlers
6365
// and middlewares. It returns HTTP 500 unless response status was already set.
@@ -66,12 +68,12 @@ func main() {
6668
RecoverPanics: true,
6769

6870
// Select request/response headers to be logged explicitly.
69-
ReqHeaders: []string{"User-Agent", "Origin", "Referer", traceid.Header},
70-
RespHeaders: []string{traceid.Header},
71+
LogRequestHeaders: []string{"User-Agent", "Origin", "Referer"},
72+
LogResponseHeaders: []string{},
7173

7274
// You can log request/request body. Useful for debugging.
73-
ReqBody: false,
74-
RespBody: false,
75+
LogRequestBody: false,
76+
LogResponseBody: false,
7577
}))
7678

7779
r.Get("/", func(w http.ResponseWriter, r *http.Request) {

_example/main.go

+20-6
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,9 @@ func main() {
4848

4949
// Request logger
5050
r.Use(httplog.RequestLogger(logger, &httplog.Options{
51-
// Level defines the verbosity of the requests logs:
51+
// Level defines the verbosity of the request logs:
5252
// slog.LevelDebug - log both request starts & responses (incl. OPTIONS)
53-
// slog.LevelInfo - log responses (excl. OPTIONS)
53+
// slog.LevelInfo - log all responses (excl. OPTIONS)
5454
// slog.LevelWarn - log 4xx and 5xx responses only (except for 429)
5555
// slog.LevelError - log 5xx responses only
5656
Level: slog.LevelInfo,
@@ -66,12 +66,12 @@ func main() {
6666
RecoverPanics: true,
6767

6868
// Select request/response headers to be logged explicitly.
69-
ReqHeaders: []string{"User-Agent", "Origin", "Referer", traceid.Header},
70-
RespHeaders: []string{traceid.Header},
69+
LogRequestHeaders: []string{"User-Agent", "Origin", "Referer", traceid.Header},
70+
LogResponseHeaders: []string{traceid.Header},
7171

7272
// You can log request/request body. Useful for debugging.
73-
ReqBody: false,
74-
RespBody: false,
73+
LogRequestBody: false,
74+
LogResponseBody: false,
7575
}))
7676

7777
r.Use(func(next http.Handler) http.Handler {
@@ -124,6 +124,18 @@ func main() {
124124
w.Write([]byte("oops, err \n"))
125125
})
126126

127+
r.Post("/body", func(w http.ResponseWriter, r *http.Request) {
128+
ctx := r.Context()
129+
130+
// Log request/response bodies for Admin requests.
131+
if r.Header.Get("Authorization") == "Bearer ADMIN-SECRET" {
132+
httplog.LogRequestBody(ctx)
133+
httplog.LogResponseBody(ctx)
134+
}
135+
136+
w.Write([]byte(`{"response": "payload"}`))
137+
})
138+
127139
fmt.Println("Enable pretty logs with:")
128140
fmt.Println(" ENV=localhost go run ./")
129141
fmt.Println()
@@ -133,6 +145,8 @@ func main() {
133145
fmt.Println(" curl -v http://localhost:8000/info")
134146
fmt.Println(" curl -v http://localhost:8000/warn")
135147
fmt.Println(" curl -v http://localhost:8000/err")
148+
fmt.Println(` curl -v http://localhost:8000/body -X POST --data '{"request": "data"}'`)
149+
fmt.Println(` curl -v http://localhost:8000/body -X POST --data '{"request": "data"}' -H "Authorization: Bearer ADMIN-SECRET"`)
136150
fmt.Println()
137151

138152
if err := http.ListenAndServe("localhost:8000", r); err != http.ErrAbortHandler {

context.go

+27
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package httplog
22

33
import (
44
"context"
5+
"fmt"
6+
"io"
57
"log/slog"
68
)
79

@@ -35,3 +37,28 @@ func SetLevel(ctx context.Context, level slog.Level) {
3537
}
3638
log.Level = level
3739
}
40+
41+
func LogRequestBody(ctx context.Context) {
42+
log, ok := ctx.Value(logCtxKey{}).(*Log)
43+
if !ok {
44+
// Panic to stress test the use of this function. Later, we can return error.
45+
panic("use of httplog.SetLevel() outside of context set by httplog.RequestLogger")
46+
}
47+
if !log.LogRequestBody {
48+
fmt.Println("doing it now...")
49+
log.LogRequestBody = true
50+
log.Req.Body = io.NopCloser(io.TeeReader(log.Req.Body, &log.ReqBody))
51+
}
52+
}
53+
54+
func LogResponseBody(ctx context.Context) {
55+
log, ok := ctx.Value(logCtxKey{}).(*Log)
56+
if !ok {
57+
// Panic to stress test the use of this function. Later, we can return error.
58+
panic("use of httplog.SetLevel() outside of context set by httplog.RequestLogger")
59+
}
60+
if !log.LogResponseBody {
61+
log.LogResponseBody = true
62+
log.WW.Tee(&log.RespBody)
63+
}
64+
}

handler.go

+34-27
Original file line numberDiff line numberDiff line change
@@ -30,59 +30,66 @@ func (h *Handler) Enabled(ctx context.Context, level slog.Level) bool {
3030
return level >= h.opts.Level
3131
}
3232

33-
func (h *Handler) Handle(ctx context.Context, r slog.Record) error {
33+
func (h *Handler) Handle(ctx context.Context, rec slog.Record) error {
3434
log, ok := ctx.Value(logCtxKey{}).(*Log)
3535
if !ok {
3636
// Panic to stress test the use of this handler. Later, we can return error.
3737
panic("use of httplog.DefaultHandler outside of context set by http.RequestLogger middleware")
3838
}
3939

40+
// r := log.Req
41+
42+
scheme := "http"
43+
if log.Req.TLS != nil {
44+
scheme = "https"
45+
}
46+
4047
if h.opts.Concise {
41-
r.AddAttrs(slog.Any("requestHeaders", slog.GroupValue(getHeaderAttrs(log.Req.Header, h.opts.ReqHeaders)...)))
42-
if h.opts.ReqBody && log.Resp != nil {
43-
r.AddAttrs(slog.String("requestBody", log.Req.Body.String()))
44-
if !log.Req.BodyFullyRead {
45-
r.AddAttrs(slog.Bool("requestBodyFullyRead", false))
48+
rec.AddAttrs(slog.Any("requestHeaders", slog.GroupValue(getHeaderAttrs(log.Req.Header, h.opts.LogRequestHeaders)...)))
49+
if log.LogRequestBody && log.Resp != nil {
50+
rec.AddAttrs(slog.String("requestBody", log.ReqBody.String()))
51+
if !log.ReqBodyFullyRead {
52+
rec.AddAttrs(slog.Bool("requestBodyFullyRead", false))
4653
}
4754
}
4855

4956
if log.Resp != nil {
50-
r.Message = fmt.Sprintf("HTTP %v (%v): %s %s", log.Resp.Status, log.Resp.Duration, log.Req.Method, log.Req.URL)
51-
r.AddAttrs(slog.Any("responseHeaders", slog.GroupValue(getHeaderAttrs(log.Resp.Header(), h.opts.RespHeaders)...)))
52-
if h.opts.RespBody {
53-
r.AddAttrs(slog.String("responseBody", log.Resp.Body.String()))
57+
rec.Message = fmt.Sprintf("HTTP %v (%v): %s %s", log.WW.Status(), log.Resp.Duration, log.Req.Method, log.Req.URL)
58+
rec.AddAttrs(slog.Any("responseHeaders", slog.GroupValue(getHeaderAttrs(log.Resp.Header(), h.opts.LogResponseHeaders)...)))
59+
if log.LogResponseBody {
60+
rec.AddAttrs(slog.String("responseBody", log.RespBody.String()))
5461
}
5562
} else {
56-
r.Message = fmt.Sprintf("%s %s://%s%s", log.Req.Method, log.Req.Scheme, log.Req.Host, log.Req.URL)
63+
rec.Message = fmt.Sprintf("%s %s://%s%s", log.Req.Method, scheme, log.Req.Host, log.Req.URL)
5764
}
5865
} else {
5966
reqAttrs := []slog.Attr{
60-
slog.String("url", fmt.Sprintf("%s://%s%s", log.Req.Scheme, log.Req.Host, log.Req.URL)),
67+
slog.String("url", fmt.Sprintf("%s://%s%s", scheme, log.Req.Host, log.Req.URL)),
6168
slog.String("method", log.Req.Method),
6269
slog.String("path", log.Req.URL.Path),
6370
slog.String("remoteIp", log.Req.RemoteAddr),
6471
slog.String("proto", log.Req.Proto),
65-
slog.Any("headers", slog.GroupValue(getHeaderAttrs(log.Req.Header, h.opts.ReqHeaders)...)),
72+
slog.Any("headers", slog.GroupValue(getHeaderAttrs(log.Req.Header, h.opts.LogRequestHeaders)...)),
6673
}
67-
if h.opts.ReqBody && log.Resp != nil {
68-
reqAttrs = append(reqAttrs, slog.String("body", log.Req.Body.String()))
69-
if !log.Req.BodyFullyRead {
74+
if log.LogRequestBody && log.Resp != nil {
75+
reqAttrs = append(reqAttrs, slog.String("body", log.ReqBody.String()))
76+
if !log.ReqBodyFullyRead {
7077
reqAttrs = append(reqAttrs, slog.Bool("bodyFullyRead", false))
7178
}
7279
}
73-
r.AddAttrs(slog.Any("request", slog.GroupValue(reqAttrs...)))
80+
rec.AddAttrs(slog.Any("request", slog.GroupValue(reqAttrs...)))
7481

7582
if log.Resp != nil {
7683
respAttrs := []slog.Attr{
77-
slog.Any("headers", slog.GroupValue(getHeaderAttrs(log.Resp.Header(), h.opts.RespHeaders)...)),
78-
slog.Int("status", log.Resp.Status),
79-
slog.Int("bytes", log.Resp.Bytes),
84+
slog.Any("headers", slog.GroupValue(getHeaderAttrs(log.Resp.Header(), h.opts.LogResponseHeaders)...)),
85+
slog.Int("status", log.WW.Status()),
86+
slog.Int("bytes", log.WW.BytesWritten()),
8087
slog.Float64("duration", float64(log.Resp.Duration.Nanoseconds()/1000000.0)), // in milliseconds
8188
}
82-
if h.opts.RespBody {
83-
respAttrs = append(respAttrs, slog.String("body", log.Resp.Body.String()))
89+
if log.LogResponseBody {
90+
respAttrs = append(respAttrs, slog.String("body", log.RespBody.String()))
8491
}
85-
r.AddAttrs(slog.Any("response", slog.GroupValue(respAttrs...)))
92+
rec.AddAttrs(slog.Any("response", slog.GroupValue(respAttrs...)))
8693
}
8794
}
8895

@@ -99,16 +106,16 @@ func (h *Handler) Handle(ctx context.Context, r slog.Record) error {
99106
break
100107
}
101108
}
102-
r.AddAttrs(
109+
rec.AddAttrs(
103110
slog.Any("panic", log.Panic),
104111
slog.Any("panicStack", stackValues),
105112
)
106113
}
107114

108-
r.AddAttrs(h.attrs...)
109-
r.AddAttrs(log.Attrs...)
115+
rec.AddAttrs(h.attrs...)
116+
rec.AddAttrs(log.Attrs...)
110117

111-
return h.Handler.Handle(ctx, r)
118+
return h.Handler.Handle(ctx, rec)
112119
}
113120

114121
func (c *Handler) WithAttrs(attrs []slog.Attr) slog.Handler {

0 commit comments

Comments
 (0)