AWS Lambda
AWS Lambda has no HTTP middleware lifecycle like Nuxt or Express, so evlog behaves like standalone TypeScript: call initLogger() once, create a logger per invocation (or per SQS message) with createLogger(), then call log.emit() when work finishes.
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
bun add evlog
2. Initialize once, log per invocation
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.
import { createAxiomDrain } from 'evlog/axiom'
import { initLogger } from 'evlog'
initLogger({
env: { service: 'sqs-consumer' },
silent: process.env.NODE_ENV === 'production',
drain: createAxiomDrain(),
})
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.
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: same
initLogger+createLogger+emit()model - Configuration:
silent,env.region(AWS_REGION), drains - Wide Events: designing one comprehensive event per unit of work
Custom Integration
Build your own evlog framework integration using the toolkit API with createMiddlewareLogger, header extraction, AsyncLocalStorage, and the full drain/enrich/keep pipeline.
Overview
Send your logs to external services with evlog adapters. Built-in support for popular observability platforms and custom destinations.