Skip to content

Commit 31a7a20

Browse files
committed
Address PR review feedback
- Rename Handler() to LogHandler() - Rename LogFormatName/LogLevelName to LogFormat/LogLevel - Use functional options pattern with WithFields() - Use Field functions (FieldFunctionARN, FieldTenantID) for immutability - Add example tests in separate file with go1.21 build tag
1 parent 18f28ed commit 31a7a20

File tree

4 files changed

+141
-62
lines changed

4 files changed

+141
-62
lines changed

lambdacontext/context.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@ import (
1515
"strconv"
1616
)
1717

18-
// LogFormatName is the name of the Log Format, either TEXT or JSON
19-
var LogFormatName string
18+
// LogFormat is the log format, either TEXT or JSON (from AWS_LAMBDA_LOG_FORMAT)
19+
var LogFormat string
2020

21-
// LogLevelName is the name of the Log Levels for structured logging. Only available when LogFormatName is JSON
22-
var LogLevelName string
21+
// LogLevel is the log level for structured logging (from AWS_LAMBDA_LOG_LEVEL). Only available when LogFormat is JSON
22+
var LogLevel string
2323

2424
// LogGroupName is the name of the log group that contains the log streams of the current Lambda Function
2525
var LogGroupName string
@@ -39,8 +39,8 @@ var FunctionVersion string
3939
var maxConcurrency int
4040

4141
func init() {
42-
LogFormatName = os.Getenv("AWS_LAMBDA_LOG_FORMAT")
43-
LogLevelName = os.Getenv("AWS_LAMBDA_LOG_LEVEL")
42+
LogFormat = os.Getenv("AWS_LAMBDA_LOG_FORMAT")
43+
LogLevel = os.Getenv("AWS_LAMBDA_LOG_LEVEL")
4444
LogGroupName = os.Getenv("AWS_LAMBDA_LOG_GROUP_NAME")
4545
LogStreamName = os.Getenv("AWS_LAMBDA_LOG_STREAM_NAME")
4646
FunctionName = os.Getenv("AWS_LAMBDA_FUNCTION_NAME")
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
//go:build go1.21
2+
// +build go1.21
3+
4+
package lambdacontext_test
5+
6+
import (
7+
"context"
8+
"log/slog"
9+
10+
"github.com/aws/aws-lambda-go/lambda"
11+
"github.com/aws/aws-lambda-go/lambdacontext"
12+
)
13+
14+
// ExampleLogHandler demonstrates basic usage of LogHandler for structured logging.
15+
// The handler automatically injects requestId from Lambda context into each log record.
16+
func ExampleLogHandler() {
17+
// Set up the Lambda-aware slog handler
18+
slog.SetDefault(slog.New(lambdacontext.LogHandler()))
19+
20+
lambda.Start(func(ctx context.Context) (string, error) {
21+
// Use slog.InfoContext to include Lambda context in logs
22+
slog.InfoContext(ctx, "processing request", "action", "example")
23+
return "success", nil
24+
})
25+
}
26+
27+
// ExampleLogHandler_withFields demonstrates LogHandler with additional fields.
28+
// Use WithFields with FieldFunctionARN() and FieldTenantID() to include extra context.
29+
func ExampleLogHandler_withFields() {
30+
// Set up handler with function ARN and tenant ID fields
31+
slog.SetDefault(slog.New(lambdacontext.LogHandler(
32+
lambdacontext.WithFields(lambdacontext.FieldFunctionARN(), lambdacontext.FieldTenantID()),
33+
)))
34+
35+
lambda.Start(func(ctx context.Context) (string, error) {
36+
slog.InfoContext(ctx, "multi-tenant request", "tenant", "acme-corp")
37+
return "success", nil
38+
})
39+
}
40+
41+
// ExampleWithFields demonstrates using WithFields to include specific Lambda context fields.
42+
func ExampleWithFields() {
43+
// Include only function ARN
44+
handler := lambdacontext.LogHandler(
45+
lambdacontext.WithFields(lambdacontext.FieldFunctionARN()),
46+
)
47+
slog.SetDefault(slog.New(handler))
48+
49+
lambda.Start(func(ctx context.Context) (string, error) {
50+
// Log output will include "functionArn" field
51+
slog.InfoContext(ctx, "function invoked")
52+
return "success", nil
53+
})
54+
}

lambdacontext/logger.go

Lines changed: 43 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11,44 +11,63 @@ import (
1111
"os"
1212
)
1313

14-
// Field represents an optional field to include in log records.
14+
// Field represents a Lambda context field to include in log records.
1515
type Field struct {
1616
key string
1717
value func(*LambdaContext) string
1818
}
1919

20-
// FunctionArn includes the invoked function ARN in log records.
21-
var FunctionArn = Field{"functionArn", func(lc *LambdaContext) string { return lc.InvokedFunctionArn }} //nolint: staticcheck
20+
// FieldFunctionARN returns a Field that includes the invoked function ARN in log records.
21+
func FieldFunctionARN() Field {
22+
return Field{"functionArn", func(lc *LambdaContext) string { return lc.InvokedFunctionArn }}
23+
}
24+
25+
// FieldTenantID returns a Field that includes the tenant ID in log records (for multi-tenant functions).
26+
func FieldTenantID() Field {
27+
return Field{"tenantId", func(lc *LambdaContext) string { return lc.TenantID }}
28+
}
29+
30+
// logOptions holds configuration for the Lambda log handler.
31+
type logOptions struct {
32+
fields []Field
33+
}
34+
35+
// LogOption is a functional option for configuring the Lambda log handler.
36+
type LogOption func(*logOptions)
2237

23-
// TenantId includes the tenant ID in log records (for multi-tenant functions).
24-
var TenantId = Field{"tenantId", func(lc *LambdaContext) string { return lc.TenantID }} //nolint: staticcheck
38+
// WithFields includes the specified fields in log records.
39+
func WithFields(fields ...Field) LogOption {
40+
return func(o *logOptions) {
41+
o.fields = append(o.fields, fields...)
42+
}
43+
}
2544

26-
// Handler returns a [slog.Handler] for AWS Lambda structured logging.
45+
// LogHandler returns a [slog.Handler] for AWS Lambda structured logging.
2746
// It reads AWS_LAMBDA_LOG_FORMAT and AWS_LAMBDA_LOG_LEVEL from environment,
2847
// and injects requestId from Lambda context into each log record.
2948
//
30-
// By default, only requestId is injected. Pass optional fields to include more:
31-
//
32-
// // Default: only requestId
33-
// slog.SetDefault(slog.New(lambdacontext.Handler()))
34-
//
35-
// // With functionArn and tenantId
36-
// slog.SetDefault(slog.New(lambdacontext.Handler(lambdacontext.FunctionArn, lambdacontext.TenantId)))
37-
func Handler(fields ...Field) slog.Handler {
49+
// By default, only requestId is injected. Use WithFields to include more.
50+
// See the package examples for usage.
51+
func LogHandler(opts ...LogOption) slog.Handler {
52+
options := &logOptions{}
53+
for _, opt := range opts {
54+
opt(options)
55+
}
56+
3857
level := parseLogLevel()
39-
opts := &slog.HandlerOptions{
58+
handlerOpts := &slog.HandlerOptions{
4059
Level: level,
4160
ReplaceAttr: ReplaceAttr,
4261
}
4362

4463
var h slog.Handler
45-
if LogFormatName == "JSON" {
46-
h = slog.NewJSONHandler(os.Stdout, opts)
64+
if LogFormat == "JSON" {
65+
h = slog.NewJSONHandler(os.Stdout, handlerOpts)
4766
} else {
48-
h = slog.NewTextHandler(os.Stdout, opts)
67+
h = slog.NewTextHandler(os.Stdout, handlerOpts)
4968
}
5069

51-
return &lambdaHandler{handler: h, fields: fields}
70+
return &lambdaHandler{handler: h, fields: options.fields}
5271
}
5372

5473
// ReplaceAttr maps slog's default keys to AWS Lambda's log format (time->timestamp, msg->message).
@@ -67,7 +86,7 @@ func ReplaceAttr(groups []string, attr slog.Attr) slog.Attr {
6786
}
6887

6988
// Attrs returns Lambda context fields as slog-compatible key-value pairs.
70-
// For most use cases, using [Handler] with slog.InfoContext is preferred.
89+
// For most use cases, using [LogHandler] with slog.InfoContext is preferred.
7190
func (lc *LambdaContext) Attrs() []any {
7291
return []any{"requestId", lc.AwsRequestID}
7392
}
@@ -88,9 +107,9 @@ func (h *lambdaHandler) Handle(ctx context.Context, r slog.Record) error {
88107
if lc, ok := FromContext(ctx); ok {
89108
r.AddAttrs(slog.String("requestId", lc.AwsRequestID))
90109

91-
for _, f := range h.fields {
92-
if v := f.value(lc); v != "" {
93-
r.AddAttrs(slog.String(f.key, v))
110+
for _, field := range h.fields {
111+
if v := field.value(lc); v != "" {
112+
r.AddAttrs(slog.String(field.key, v))
94113
}
95114
}
96115
}
@@ -114,7 +133,7 @@ func (h *lambdaHandler) WithGroup(name string) slog.Handler {
114133
}
115134

116135
func parseLogLevel() slog.Level {
117-
switch LogLevelName {
136+
switch LogLevel {
118137
case "DEBUG":
119138
return slog.LevelDebug
120139
case "INFO":

0 commit comments

Comments
 (0)