-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathlogger.go
More file actions
168 lines (142 loc) · 6.61 KB
/
logger.go
File metadata and controls
168 lines (142 loc) · 6.61 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
package lambada
import (
"context"
"log/slog"
)
// Logger is the interface used by Lambada to log requests, responses, errors, and panics.
// Implementations should handle logging asynchronously if needed to avoid blocking request processing.
type Logger interface {
// LogRequest logs an incoming Lambda request.
LogRequest(ctx context.Context, req *Request)
// LogResponse logs the response generated for a request.
LogResponse(ctx context.Context, req *Request, res *Response)
// LogError logs an error that occurred during request processing.
LogError(ctx context.Context, req *Request, err error)
// LogPanic logs a panic that occurred during request processing.
LogPanic(ctx context.Context, req *Request, v any)
}
// NullLogger is a no-op Logger implementation that discards all log messages.
// This is the default logger used by Lambada when no logger is specified.
type NullLogger struct{}
// LogRequest implements the Logger interface by doing nothing.
func (l NullLogger) LogRequest(context.Context, *Request) {}
// LogResponse implements the Logger interface by doing nothing.
func (l NullLogger) LogResponse(context.Context, *Request, *Response) {}
// LogError implements the Logger interface by doing nothing.
func (l NullLogger) LogError(context.Context, *Request, error) {}
// LogPanic implements the Logger interface by doing nothing.
func (l NullLogger) LogPanic(context.Context, *Request, any) {}
var _ Logger = NullLogger{}
// SlogLogger is a Logger implementation that uses Go's structured logging (slog) package.
// It provides structured logging with customizable attribute extraction functions.
type SlogLogger struct {
// Logger is the underlying slog.Logger used for actual logging.
Logger *slog.Logger
// Level is the log the level to use for requests and responses
Level slog.Level
// GetRequestAttrs is an optional function to extract custom attributes from requests.
// If nil, DefaultGetRequestAttrs is used. The summary parameter indicates whether
// this is for a summary log (true) or a detailed log (false).
GetRequestAttrs func(req *Request, summary bool) []slog.Attr
// GetResponseAttrs is an optional function to extract custom attributes from responses.
// If nil, DefaultGetResponseAttrs is used.
GetResponseAttrs func(res *Response) []slog.Attr
}
func (l *SlogLogger) getRequestAttrs(req *Request, summary bool) []slog.Attr {
if l.GetRequestAttrs != nil {
return l.GetRequestAttrs(req, summary)
}
return DefaultGetRequestAttrs(req, summary)
}
func (l *SlogLogger) getResponseAttrs(res *Response) []slog.Attr {
if l.GetResponseAttrs != nil {
return l.GetResponseAttrs(res)
}
return DefaultGetResponseAttrs(res)
}
// LogRequest logs an incoming Lambda request at Info level with structured attributes.
func (l *SlogLogger) LogRequest(ctx context.Context, req *Request) {
l.Logger.LogAttrs(ctx, l.Level, "Request",
slog.Attr{Key: "request", Value: slog.GroupValue(l.getRequestAttrs(req, false)...)})
}
// LogResponse logs a Lambda response at Info level with both request and response attributes.
func (l *SlogLogger) LogResponse(ctx context.Context, req *Request, res *Response) {
l.Logger.LogAttrs(ctx, l.Level, "Response",
slog.Attr{Key: "request", Value: slog.GroupValue(l.getRequestAttrs(req, true)...)},
slog.Attr{Key: "response", Value: slog.GroupValue(l.getResponseAttrs(res)...)},
)
}
// LogError logs an error that occurred during request processing at Error level.
func (l *SlogLogger) LogError(ctx context.Context, req *Request, err error) {
l.Logger.LogAttrs(ctx, slog.LevelError, "Error",
slog.Attr{Key: "request", Value: slog.GroupValue(l.getRequestAttrs(req, true)...)},
slog.String("error", err.Error()),
)
}
// LogPanic logs a panic that occurred during request processing at Error level.
func (l *SlogLogger) LogPanic(ctx context.Context, req *Request, v any) {
l.Logger.LogAttrs(ctx, slog.LevelError, "Panic",
slog.Attr{Key: "request", Value: slog.GroupValue(l.getRequestAttrs(req, true)...)},
slog.Any("panic", v))
}
var _ Logger = (*SlogLogger)(nil)
func headersToAttrs(headers map[string]string) []slog.Attr {
res := make([]slog.Attr, len(headers))
i := 0
for k, v := range headers {
res[i] = slog.String(k, v)
i++
}
return res
}
const baseRequestAttrCount = 5
func appendBaseRequestAttrs(attrs []slog.Attr, req *Request) []slog.Attr {
if attrs == nil {
attrs = make([]slog.Attr, 0, baseRequestAttrCount)
}
return append(attrs, slog.String("requestId", req.RequestContext.RequestID),
slog.Int("version", int(req.version)),
slog.String("routeKey", req.RouteKey),
slog.String("rawPath", req.RawPath),
slog.String("rawQueryString", req.RawQueryString))
}
// DefaultGetRequestAttrs extracts default structured logging attributes from a Lambda request.
// The summary parameter indicates whether this is for a summary log entry (excludes some verbose fields).
func DefaultGetRequestAttrs(req *Request, summary bool) []slog.Attr {
return appendBaseRequestAttrs(nil, req)
}
// FullGetRequestAttrs extracts default structured logging attributes from a Lambda request.
// The summary parameter indicates whether this is for a summary log entry (excludes some verbose fields).
// FullGetRequestAttrs is similar to DefaultGetRequestAttrs, the difference being that the request body is
// included when summary is false.
func FullGetRequestAttrs(req *Request, summary bool) []slog.Attr {
if summary {
return appendBaseRequestAttrs(nil, req)
}
attrs := appendBaseRequestAttrs(make([]slog.Attr, 0, baseRequestAttrCount+2), req)
return append(attrs,
slog.Attr{Key: "headers", Value: slog.GroupValue(headersToAttrs(req.Headers)...)},
slog.String("body", req.Body))
}
const baseResponseAttrCount = 3
func appendBaseResponseAttrs(attrs []slog.Attr, res *Response) []slog.Attr {
if attrs == nil {
attrs = make([]slog.Attr, 0, baseResponseAttrCount)
}
return append(attrs, slog.Int("length", len(res.Body)),
slog.Bool("isBase64Encoded", res.IsBase64Encoded),
slog.Int("statusCode", res.StatusCode))
}
// DefaultGetResponseAttrs extracts default structured logging attributes from a Lambda response.
func DefaultGetResponseAttrs(res *Response) []slog.Attr {
return appendBaseResponseAttrs(nil, res)
}
// FullGetResponseAttrs extracts default structured logging attributes from a Lambda response.
// FullGetResponseAttrs is similar to DefaultGetResponseAttrs, the difference being that the response body is
// always included.
func FullGetResponseAttrs(res *Response) []slog.Attr {
attrs := appendBaseResponseAttrs(make([]slog.Attr, 0, baseResponseAttrCount+1), res)
return append(attrs,
slog.Attr{Key: "headers", Value: slog.GroupValue(headersToAttrs(res.Headers)...)},
slog.String("body", res.Body))
}