# lambdalog
Structured logging for AWS Lambda functions in Go, with zero magic.
- JSON output on stdout (one object per line)
- Request-ID correlation read from `context.Context`
- Adaptive sampling so a runaway log statement can't burn your bill
- No reflection in the hot path; allocations tracked in benchmarks
## Install
go get mercemay.top/src/lambdalog
Requires Go 1.22+.
## Quickstart
logger := lambdalog.New(os.Stdout).With("service", "checkout")
func Handle(ctx context.Context, evt Event) error {
log := logger.FromContext(ctx)
log.Info("received event", "items", len(evt.Items))
if err := process(ctx, evt); err != nil {
log.Error("process failed", "err", err)
return err
}
return nil
}
Every event emitted from `log` carries the Lambda request ID as `rid`,
as well as any attributes attached via `With(...)`.
## Adaptive sampling
Hot log sites can opt into sampling:
log := logger.Sampled("cache-miss", 100) // 1 in 100 once above threshold
The sampler is lock-free on the common path and resets its decision window
on cold start; see `sampler.go`.
## API
| Call | Description |
|----------------------------------------|----------------------------------------|
| `New(w io.Writer) *Logger` | Create a logger writing JSON to `w` |
| `(*Logger).With(k, v) *Logger` | Child logger with an extra attribute |
| `(*Logger).FromContext(ctx) *Logger` | Child enriched with request ID etc. |
| `(*Logger).Info(msg, kv...)` | Emit an Info record |
| `(*Logger).Error(msg, kv...)` | Emit an Error record |
| `(*Logger).Sampled(key, n) *Logger` | Child with 1-in-N adaptive sampling |
`kv...` arguments are pairs: `"user", id, "dur_ms", ms`. An odd count is
padded with the literal string `"MISSING"` to avoid panics in production.
## Output format
Each record is one JSON object:
{"ts":"2025-03-04T12:00:00.123456789Z","level":"info","msg":"ok","rid":"abc","service":"checkout"}
Keys are emitted in a stable order: `ts`, `level`, `msg`, `rid`, then any
user attributes in insertion order.
## Testing
go test ./...
Table-driven tests live in `lambdalog_test.go` and `context_test.go`; both
exercise the concurrent path with `t.Parallel()`.
## License
MIT. See `LICENSE`.