diff --git a/.changeset/aws-lambda-docs.md b/.changeset/aws-lambda-docs.md new file mode 100644 index 00000000..f58836b0 --- /dev/null +++ b/.changeset/aws-lambda-docs.md @@ -0,0 +1,5 @@ +--- +'evlog': patch +--- + +Add an [AWS Lambda](https://www.evlog.dev/frameworks/aws-lambda) guide to the documentation site (`initLogger` once, `createLogger` per invocation, manual `emit`). diff --git a/apps/docs/app/assets/icons/lambda.svg b/apps/docs/app/assets/icons/lambda.svg new file mode 100644 index 00000000..a3f8a986 --- /dev/null +++ b/apps/docs/app/assets/icons/lambda.svg @@ -0,0 +1,5 @@ + diff --git a/apps/docs/content/4.frameworks/00.overview.md b/apps/docs/content/4.frameworks/00.overview.md index 39004a90..34cb35b7 100644 --- a/apps/docs/content/4.frameworks/00.overview.md +++ b/apps/docs/content/4.frameworks/00.overview.md @@ -25,6 +25,7 @@ evlog provides native integrations for every major TypeScript framework. The sam | [Fastify](/frameworks/fastify) | `evlog/fastify` | Plugin | `request.log` / `useLogger()` | Stable | | [Elysia](/frameworks/elysia) | `evlog/elysia` | Plugin | `log` (context) / `useLogger()` | Stable | | [Cloudflare Workers](/frameworks/cloudflare-workers) | `evlog/workers` | Factory | `createWorkersLogger()` | Stable | +| [AWS Lambda](/frameworks/aws-lambda) | `evlog` | Manual | `createLogger()` / `createRequestLogger()` | Guide | | [Standalone](/frameworks/standalone) | `evlog` | Manual | `createLogger()` / `createRequestLogger()` | Stable | | [Astro](/frameworks/astro) | `evlog` | Manual | `createRequestLogger()` | Guide | | [Custom](/frameworks/custom-integration) | `evlog/toolkit` | Build your own | `createMiddlewareLogger()` | Beta | @@ -147,6 +148,15 @@ evlog provides native integrations for every major TypeScript framework. The sam ::: :::card --- + icon: i-custom-lambda + title: AWS Lambda + to: /frameworks/aws-lambda + color: neutral + --- + `initLogger` once per runtime; `createLogger` per invocation (SQS, events, HTTP API). + ::: + :::card + --- icon: i-simple-icons-typescript title: Standalone to: /frameworks/standalone diff --git a/apps/docs/content/4.frameworks/16.aws-lambda.md b/apps/docs/content/4.frameworks/16.aws-lambda.md new file mode 100644 index 00000000..43176d14 --- /dev/null +++ b/apps/docs/content/4.frameworks/16.aws-lambda.md @@ -0,0 +1,116 @@ +--- +title: AWS Lambda +description: Wide events and structured logging in AWS Lambda functions, including SQS consumers and event-driven handlers. +navigation: + title: AWS Lambda + icon: i-custom-lambda +--- + +AWS Lambda has **no HTTP middleware lifecycle** like Nuxt or Express, so evlog behaves like [standalone TypeScript](/frameworks/standalone): call `initLogger()` once, create a logger **per invocation** (or per SQS message) with `createLogger()`, then call `log.emit()` when work finishes. + +::code-collapse + +```txt [Prompt] +Set up evlog in an AWS Lambda function (e.g. SQS consumer). + +- Install evlog: pnpm add evlog +- Call initLogger({ env: { service: 'my-fn' } }) once at module load (cold start) +- In the handler, create a new createLogger({ messageId, ... }) per invocation or per message +- Use log.set() to accumulate context; call log.emit() when done +- Avoid a single module-level logger instance reused across invocations (Lambda reuses runtimes) + +Docs: https://www.evlog.dev/frameworks/aws-lambda +Adapters: https://www.evlog.dev/adapters +``` + +:: + +## Why not one global `createLogger`? + +Lambda **execution environments are reused**: the same process can handle many invocations in sequence. Module-level variables persist, so **one shared logger instance** can leak fields from a previous invocation into the next. + +**Do this:** `initLogger()` once at the top level (configuration only), and **`createLogger()` inside the handler** (or inside the loop over SQS records) for each unit of work. + +**Dependency injection** (passing `log` into functions) is optional—it helps tests and clarity—but what matters is **one logger per invocation**, not whether you use DI. + +## Quick Start + +### 1. Install + +```bash [Terminal] +bun add evlog +``` + +### 2. Initialize once, log per invocation + +```typescript [src/handler.ts] +import type { SQSEvent } from 'aws-lambda' +import { initLogger, createLogger } from 'evlog' + +initLogger({ + env: { service: 'sqs-consumer', environment: process.env.NODE_ENV }, +}) + +export async function handler(event: SQSEvent) { + for (const record of event.Records) { + const log = createLogger({ + messageId: record.messageId, + approximateReceiveCount: record.attributes?.ApproximateReceiveCount, + }) + + try { + log.set({ queue: { name: record.eventSourceARN } }) + // … parse record.body and process the message + log.set({ status: 'ok' }) + } catch (error) { + log.error(error instanceof Error ? error : new Error(String(error))) + log.set({ status: 'error' }) + throw error + } finally { + log.emit() + } + } +} +``` + +If you process the whole batch as one logical unit, use a **single** `createLogger()` per handler invocation with batch metadata instead of one logger per record. + +## Stdout and `silent` + +Many teams ingest Lambda logs from **CloudWatch** via stdout. If you use a **drain adapter** (OTLP, Datadog, Axiom, etc.) and want JSON or platform-specific formatting without duplicate console noise, set `silent: true` in production—see [Configuration](/core-concepts/configuration#silent-mode). + +```typescript [src/handler.ts] +import { createAxiomDrain } from 'evlog/axiom' +import { initLogger } from 'evlog' + +initLogger({ + env: { service: 'sqs-consumer' }, + silent: process.env.NODE_ENV === 'production', + drain: createAxiomDrain(), +}) +``` + +::callout{icon="i-lucide-alert-triangle" color="warning"} +If `silent` is enabled without a `drain`, events may not be visible anywhere. See the configuration docs for details. +:: + +## Error handling + +Use `createError` where you want structured fields (`why`, `fix`, `link`). Map failures to your Lambda return or rethrow so SQS retry/DLQ behavior stays correct—evlog does not replace AWS error semantics. + +```typescript [src/handler.ts] +import { createError } from 'evlog' + +throw createError({ + message: 'Invalid payload', + status: 400, + why: 'Required field missing', + fix: 'Include orderId in the message body', +}) +``` + +## Related + +- [Standalone TypeScript](/frameworks/standalone): same `initLogger` + `createLogger` + `emit()` model +- [Configuration](/core-concepts/configuration): `silent`, `env.region` (`AWS_REGION`), drains +- [Wide Events](/logging/wide-events): designing one comprehensive event per unit of work