Skip to content

Commit 98e09d6

Browse files
committed
Fix CURL logging, unexport some types
1 parent c94efe7 commit 98e09d6

File tree

5 files changed

+67
-35
lines changed

5 files changed

+67
-35
lines changed

README.md

+7-3
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,13 @@ A small but powerful structured logging package for HTTP request logging, built
1414

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

19-
$ ENV=localhost go run github.com/golang-cz/httplog/_example
19+
# JSON logger
20+
$ ENV=production go run .
21+
22+
# pretty logger
23+
$ ENV=localhost go run .
2024
```
2125

2226
## Usage
@@ -64,7 +68,7 @@ func main() {
6468
// RecoverPanics recovers from panics occurring in the underlying HTTP handlers
6569
// and middlewares. It returns HTTP 500 unless response status was already set.
6670
//
67-
// NOTE: The request logger logs all panics automatically, regardless of this setting.
71+
// NOTE: Panics are logged as errors automatically, regardless of this setting.
6872
RecoverPanics: true,
6973

7074
// Select request/response headers to be logged explicitly.

_example/main.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ func main() {
6262
// RecoverPanics recovers from panics occurring in the underlying HTTP handlers
6363
// and middlewares. It returns HTTP 500 unless response status was already set.
6464
//
65-
// NOTE: The request logger logs all panics automatically, regardless of this setting.
65+
// NOTE: Panics are logged as errors automatically, regardless of this setting.
6666
RecoverPanics: true,
6767

6868
// Select request/response headers to be logged explicitly.

context.go

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

33
import (
44
"context"
5-
"fmt"
65
"io"
76
"log/slog"
87
)
@@ -17,7 +16,7 @@ func (c *logCtxKey) String() string {
1716
//
1817
// NOTE: Not safe for concurrent access. Don't use outside of HTTP request goroutine.
1918
func SetAttrs(ctx context.Context, attrs ...slog.Attr) {
20-
log, ok := ctx.Value(logCtxKey{}).(*Log)
19+
log, ok := ctx.Value(logCtxKey{}).(*log)
2120
if !ok {
2221
// Panic to stress test the use of this function. Later, we can return error.
2322
panic("use of httplog.SetAttrs() outside of context set by httplog.RequestLogger")
@@ -30,7 +29,7 @@ func SetAttrs(ctx context.Context, attrs ...slog.Attr) {
3029
//
3130
// NOTE: Not safe for concurrent access. Don't use outside of HTTP request goroutine.
3231
func SetLevel(ctx context.Context, level slog.Level) {
33-
log, ok := ctx.Value(logCtxKey{}).(*Log)
32+
log, ok := ctx.Value(logCtxKey{}).(*log)
3433
if !ok {
3534
// Panic to stress test the use of this function. Later, we can return error.
3635
panic("use of httplog.SetLevel() outside of context set by httplog.RequestLogger")
@@ -39,23 +38,22 @@ func SetLevel(ctx context.Context, level slog.Level) {
3938
}
4039

4140
func LogRequestBody(ctx context.Context) {
42-
log, ok := ctx.Value(logCtxKey{}).(*Log)
41+
log, ok := ctx.Value(logCtxKey{}).(*log)
4342
if !ok {
4443
// 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")
44+
panic("use of httplog.LogRequestBody() outside of context set by httplog.RequestLogger")
4645
}
4746
if !log.LogRequestBody {
48-
fmt.Println("doing it now...")
4947
log.LogRequestBody = true
5048
log.Req.Body = io.NopCloser(io.TeeReader(log.Req.Body, &log.ReqBody))
5149
}
5250
}
5351

5452
func LogResponseBody(ctx context.Context) {
55-
log, ok := ctx.Value(logCtxKey{}).(*Log)
53+
log, ok := ctx.Value(logCtxKey{}).(*log)
5654
if !ok {
5755
// 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")
56+
panic("use of httplog.LogResponseBody() outside of context set by httplog.RequestLogger")
5957
}
6058
if !log.LogResponseBody {
6159
log.LogResponseBody = true

handler.go

+11-5
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,14 @@ func (h *Handler) Handle(ctx context.Context, rec slog.Record) error {
3737
panic("use of httplog.DefaultHandler outside of context set by http.RequestLogger middleware")
3838
}
3939

40-
if h.opts.LogRequestCURL {
41-
rec.AddAttrs(slog.String("curl", log.curl()))
42-
}
43-
4440
if h.opts.Concise {
4541
reqAttrs := []slog.Attr{}
4642
respAttrs := []slog.Attr{}
4743

44+
if log.LogRequestCURL {
45+
reqAttrs = append(reqAttrs, slog.String("curl", log.curl()))
46+
}
47+
4848
reqAttrs = append(reqAttrs, slog.Any("headers", slog.GroupValue(getHeaderAttrs(log.Req.Header, h.opts.LogRequestHeaders)...)))
4949
if log.LogRequestBody && log.Resp != nil {
5050
reqAttrs = append(reqAttrs, slog.String("body", log.ReqBody.String()))
@@ -63,7 +63,7 @@ func (h *Handler) Handle(ctx context.Context, rec slog.Record) error {
6363
rec.Message = fmt.Sprintf("%s %s://%s%s", log.Req.Method, log.scheme(), log.Req.Host, log.Req.URL)
6464
}
6565

66-
if log.WW.Status() >= 400 {
66+
if log.WW.Status() >= 400 || log.Level == slog.LevelDebug {
6767
rec.AddAttrs(slog.Any("request", slog.GroupValue(reqAttrs...)))
6868
rec.AddAttrs(slog.Any("response", slog.GroupValue(respAttrs...)))
6969
}
@@ -77,12 +77,18 @@ func (h *Handler) Handle(ctx context.Context, rec slog.Record) error {
7777
slog.String("proto", log.Req.Proto),
7878
slog.Any("headers", slog.GroupValue(getHeaderAttrs(log.Req.Header, h.opts.LogRequestHeaders)...)),
7979
}
80+
81+
if log.LogRequestCURL {
82+
reqAttrs = append(reqAttrs, slog.String("curl", log.curl()))
83+
}
84+
8085
if log.LogRequestBody && log.Resp != nil {
8186
reqAttrs = append(reqAttrs, slog.String("body", log.ReqBody.String()))
8287
if !log.ReqBodyFullyRead {
8388
reqAttrs = append(reqAttrs, slog.Bool("bodyFullyRead", false))
8489
}
8590
}
91+
8692
rec.AddAttrs(slog.Any("request", slog.GroupValue(reqAttrs...)))
8793

8894
if log.Resp != nil {

logger.go

+42-18
Original file line numberDiff line numberDiff line change
@@ -40,47 +40,68 @@ type Options struct {
4040
// slog.LevelInfo - log responses (excl. OPTIONS)
4141
// slog.LevelWarn - log 4xx and 5xx responses only (except for 429)
4242
// slog.LevelError - log 5xx responses only
43+
//
44+
// Use httplog.SetLevel(ctx, slog.DebugLevel) to override the level per-request.
4345
Level slog.Level
4446

4547
// Concise mode causes fewer log attributes to be printed in request logs.
4648
// This is useful if your console is too noisy during development.
4749
Concise bool
4850

4951
// RecoverPanics recovers from panics occurring in the underlying HTTP handlers
50-
// and middlewares. It returns HTTP 500 unless response status was already set.
52+
// and middlewares and returns HTTP 500 unless response status was already set.
5153
//
52-
// NOTE: The request logger logs all panics automatically, regardless of this setting.
54+
// NOTE: Panics are logged as errors automatically, regardless of this setting.
5355
RecoverPanics bool
5456

5557
// LogRequestHeaders is an explicit list of headers to be logged as attributes.
58+
// If not provided, the default headers are User-Agent, Referer and Origin.
5659
LogRequestHeaders []string
5760

5861
// LogRequestBody enables logging of request body into a response log attribute.
62+
//
63+
// Use httplog.LogRequestBody(ctx) to enable on per-request basis instead.
5964
LogRequestBody bool
6065

66+
// LogRequestCURL enables logging of request body incl. all headers as a CURL command.
67+
//
68+
// Use httplog.LogRequestCURL(ctx) to enable on per-request basis instead.
69+
LogRequestCURL bool
70+
6171
// LogResponseHeaders is an explicit list of headers to be logged as attributes.
72+
//
73+
// If not provided, there are no default headers.
6274
LogResponseHeaders []string
6375

6476
// LogResponseBody enables logging of response body into a response log attribute.
77+
// The Content-Type of the response must match.
78+
//
79+
// Use httplog.LogResponseBody(ctx) to enable on per-request basis instead.
6580
LogResponseBody bool
81+
82+
// LogResponseBodyContentType defines list of Content-Types for which LogResponseBody is enabled.
83+
//
84+
// If not provided, the default list is application/json and text/plain.
85+
LogResponseBodyContentType []string
6686
}
6787

6888
var defaultOptions = Options{
69-
Level: slog.LevelInfo,
70-
Concise: false,
71-
RecoverPanics: true,
72-
LogRequestHeaders: []string{"User-Agent", "Referer", "Origin"},
73-
LogResponseHeaders: []string{""},
89+
Level: slog.LevelInfo,
90+
RecoverPanics: true,
91+
LogRequestHeaders: []string{"User-Agent", "Referer", "Origin"},
92+
LogResponseHeaders: []string{""},
93+
LogResponseBodyContentType: []string{"application/json", "text/plain"},
7494
}
7595

7696
func (l *Logger) Handle(next http.Handler) http.Handler {
7797
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
7898
ctx := r.Context()
7999

80-
log := &Log{
100+
log := &log{
81101
Level: l.opts.Level,
82102
Req: r,
83-
LogRequestBody: l.opts.LogRequestBody,
103+
LogRequestCURL: l.opts.LogRequestCURL,
104+
LogRequestBody: l.opts.LogRequestBody || l.opts.LogRequestCURL,
84105
LogResponseBody: l.opts.LogResponseBody,
85106
}
86107

@@ -121,7 +142,7 @@ func (l *Logger) Handle(next http.Handler) http.Handler {
121142

122143
status := ww.Status()
123144

124-
log.Resp = &ResponseLog{
145+
log.Resp = &respLog{
125146
Header: w.Header,
126147
Duration: time.Since(start),
127148
}
@@ -153,24 +174,27 @@ func (l *Logger) Handle(next http.Handler) http.Handler {
153174
})
154175
}
155176

156-
type Log struct {
157-
Level slog.Level // Use httplog.SetLevel(ctx, slog.DebugLevel) to override level
158-
Attrs []slog.Attr // Use httplog.SetAttrs(ctx, slog.String("key", "value")) to append
159-
LogRequestBody bool // Use httplog.LogRequestBody(ctx) to force-enable
160-
LogResponseBody bool // Use httplog.LogResponseBody(ctx) to force-enable
177+
type log struct {
178+
Level slog.Level
179+
Attrs []slog.Attr
180+
LogRequestCURL bool
181+
LogRequestBody bool
182+
LogResponseBody bool
161183

162-
// Fields automatically set by httplog.RequestLogger middleware:
184+
// Fields set by httplog.RequestLogger middleware:
163185
Req *http.Request
164186
ReqBody bytes.Buffer
165187
ReqBodyFullyRead bool
166188
WW middleware.WrapResponseWriter
167-
Resp *ResponseLog
189+
Resp *respLog
168190
RespBody bytes.Buffer
169191
Panic any
170192
PanicPC []uintptr
193+
194+
// Fields internal to httplog:
171195
}
172196

173-
type ResponseLog struct {
197+
type respLog struct {
174198
Header func() http.Header
175199
Duration time.Duration
176200
}

0 commit comments

Comments
 (0)