|
| 1 | +--- |
| 2 | +title: AWS Lambda |
| 3 | +description: Wide events and structured logging in AWS Lambda functions, including SQS consumers and event-driven handlers. |
| 4 | +navigation: |
| 5 | + title: AWS Lambda |
| 6 | + icon: i-custom-lambda |
| 7 | +--- |
| 8 | + |
| 9 | +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. |
| 10 | + |
| 11 | +::code-collapse |
| 12 | + |
| 13 | +```txt [Prompt] |
| 14 | +Set up evlog in an AWS Lambda function (e.g. SQS consumer). |
| 15 | +
|
| 16 | +- Install evlog: pnpm add evlog |
| 17 | +- Call initLogger({ env: { service: 'my-fn' } }) once at module load (cold start) |
| 18 | +- In the handler, create a new createLogger({ messageId, ... }) per invocation or per message |
| 19 | +- Use log.set() to accumulate context; call log.emit() when done |
| 20 | +- Avoid a single module-level logger instance reused across invocations (Lambda reuses runtimes) |
| 21 | +
|
| 22 | +Docs: https://www.evlog.dev/frameworks/aws-lambda |
| 23 | +Adapters: https://www.evlog.dev/adapters |
| 24 | +``` |
| 25 | + |
| 26 | +:: |
| 27 | + |
| 28 | +## Why not one global `createLogger`? |
| 29 | + |
| 30 | +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. |
| 31 | + |
| 32 | +**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. |
| 33 | + |
| 34 | +**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. |
| 35 | + |
| 36 | +## Quick Start |
| 37 | + |
| 38 | +### 1. Install |
| 39 | + |
| 40 | +```bash [Terminal] |
| 41 | +bun add evlog |
| 42 | +``` |
| 43 | + |
| 44 | +### 2. Initialize once, log per invocation |
| 45 | + |
| 46 | +```typescript [src/handler.ts] |
| 47 | +import type { SQSEvent } from 'aws-lambda' |
| 48 | +import { initLogger, createLogger } from 'evlog' |
| 49 | + |
| 50 | +initLogger({ |
| 51 | + env: { service: 'sqs-consumer', environment: process.env.NODE_ENV }, |
| 52 | +}) |
| 53 | + |
| 54 | +export async function handler(event: SQSEvent) { |
| 55 | + for (const record of event.Records) { |
| 56 | + const log = createLogger({ |
| 57 | + messageId: record.messageId, |
| 58 | + approximateReceiveCount: record.attributes?.ApproximateReceiveCount, |
| 59 | + }) |
| 60 | + |
| 61 | + try { |
| 62 | + log.set({ queue: { name: record.eventSourceARN } }) |
| 63 | + // … parse record.body and process the message |
| 64 | + log.set({ status: 'ok' }) |
| 65 | + } catch (error) { |
| 66 | + log.error(error instanceof Error ? error : new Error(String(error))) |
| 67 | + log.set({ status: 'error' }) |
| 68 | + throw error |
| 69 | + } finally { |
| 70 | + log.emit() |
| 71 | + } |
| 72 | + } |
| 73 | +} |
| 74 | +``` |
| 75 | + |
| 76 | +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. |
| 77 | + |
| 78 | +## Stdout and `silent` |
| 79 | + |
| 80 | +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). |
| 81 | + |
| 82 | +```typescript [src/handler.ts] |
| 83 | +import { createAxiomDrain } from 'evlog/axiom' |
| 84 | +import { initLogger } from 'evlog' |
| 85 | + |
| 86 | +initLogger({ |
| 87 | + env: { service: 'sqs-consumer' }, |
| 88 | + silent: process.env.NODE_ENV === 'production', |
| 89 | + drain: createAxiomDrain(), |
| 90 | +}) |
| 91 | +``` |
| 92 | + |
| 93 | +::callout{icon="i-lucide-alert-triangle" color="warning"} |
| 94 | +If `silent` is enabled without a `drain`, events may not be visible anywhere. See the configuration docs for details. |
| 95 | +:: |
| 96 | + |
| 97 | +## Error handling |
| 98 | + |
| 99 | +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. |
| 100 | + |
| 101 | +```typescript [src/handler.ts] |
| 102 | +import { createError } from 'evlog' |
| 103 | + |
| 104 | +throw createError({ |
| 105 | + message: 'Invalid payload', |
| 106 | + status: 400, |
| 107 | + why: 'Required field missing', |
| 108 | + fix: 'Include orderId in the message body', |
| 109 | +}) |
| 110 | +``` |
| 111 | + |
| 112 | +## Related |
| 113 | + |
| 114 | +- [Standalone TypeScript](/frameworks/standalone): same `initLogger` + `createLogger` + `emit()` model |
| 115 | +- [Configuration](/core-concepts/configuration): `silent`, `env.region` (`AWS_REGION`), drains |
| 116 | +- [Wide Events](/logging/wide-events): designing one comprehensive event per unit of work |
0 commit comments