---
title: "AWS Lambda"
description: "Wide events and structured logging in AWS Lambda functions, including SQS consumers and event-driven handlers."
canonical_url: "https://www.evlog.dev/integrate/frameworks/aws-lambda"
---
# AWS Lambda

> Wide events and structured logging in AWS Lambda functions, including SQS consumers and event-driven handlers.

AWS Lambda has **no HTTP middleware lifecycle** like Nuxt or Express, so evlog behaves like [standalone TypeScript](https://www.evlog.dev/integrate/frameworks/standalone): call `initLogger()` once, create a logger **per invocation** (or per SQS message) with `createLogger()`, then call `log.emit()` when work finishes.

<prompt :actions="["copy","cursor","claude"]" description="Set up evlog in my AWS Lambda function" icon="i-custom-lambda">

Set up evlog in an AWS Lambda function (e.g. SQS consumer).

- Install evlog: pnpm add evlog
- Create evlog.config.ts with export default defineEvlog({ service: 'my-fn' })
- Call initLogger(toLoggerConfig(config)) 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/integrate/frameworks/aws-lambda](https://www.evlog.dev/integrate/frameworks/aws-lambda)
Adapters: [https://www.evlog.dev/integrate/adapters/overview](https://www.evlog.dev/integrate/adapters/overview)

</prompt>

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

<code-group>

```bash [pnpm]
pnpm add evlog
```

```bash [bun]
bun add evlog
```

```bash [yarn]
yarn add evlog
```

```bash [npm]
npm install evlog
```

</code-group>

### 2. Create evlog.config.ts

```typescript [evlog.config.ts]
import { defineEvlog } from 'evlog'

export default defineEvlog({
  service: 'sqs-consumer',
  environment: process.env.NODE_ENV,
})
```

Your bundler includes the file with the handler, so the drain and `process.env` reads in it work at cold start. `npx evlog init` writes it for you.

### 3. Initialize once, log per invocation

```typescript [src/handler.ts]
import type { SQSEvent } from 'aws-lambda'
import { initLogger, createLogger, toLoggerConfig } from 'evlog'
import config from '../evlog.config'

initLogger(toLoggerConfig(config))

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](https://www.evlog.dev/reference/configuration#options).

```typescript [evlog.config.ts]
import { defineEvlog } from 'evlog'
import { createAxiomDrain } from 'evlog/axiom'

export default defineEvlog({
  service: 'sqs-consumer',
  silent: process.env.NODE_ENV === 'production',
  drain: createAxiomDrain(),
})
```

<callout color="warning" icon="i-lucide-alert-triangle">

If `silent` is enabled without a `drain`, events are built but never written anywhere. See the `silent` option in [Configuration](https://www.evlog.dev/reference/configuration#options).

</callout>

## What the file covers

`toLoggerConfig` keeps every option `initLogger` takes, such as the service, sampling, `minLevel`, `redact`, the drain and the plugins. The file's `enrich`, `keep`, `include`, `exclude` and `routes` are framework middleware options, so they do not apply to a handler.

## 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](https://www.evlog.dev/integrate/frameworks/standalone): same `initLogger` + `createLogger` + `emit()` model
- [Configuration](https://www.evlog.dev/reference/configuration): `silent`, `env.region` (`AWS_REGION`), drains
- [Wide Events](https://www.evlog.dev/learn/wide-events): designing one comprehensive event per unit of work


## Sitemap

See the full [sitemap](https://www.evlog.dev/sitemap.md) for all pages.
