Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/aws-lambda-docs.md
Original file line number Diff line number Diff line change
@@ -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`).
5 changes: 5 additions & 0 deletions apps/docs/app/assets/icons/lambda.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions apps/docs/content/4.frameworks/00.overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down Expand Up @@ -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
Expand Down
116 changes: 116 additions & 0 deletions apps/docs/content/4.frameworks/16.aws-lambda.md
Original file line number Diff line number Diff line change
@@ -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
Loading