README.md

# 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`.