CloudWatch Logs charges for every ingested GB. Debug spam at INFO on hot Lambdas is a silent bill — until you sample blindly and lose the one correlation ID that would have closed the sev. Buffer, sample by level, and always keep correlation + error paths loud.
⚡ TL;DR: Use Powertools Logger with buffer-on-INFO / flush-on-ERROR; sample successful requests; never drop
correlation_id/cold_start/ error stacks. Pair with Lambda Powertools Node Structured Logs and OpenTelemetry for LLMs.
Buffer INFO, flush on failure
// handler.ts
import { Logger } from "@aws-lambda-powertools/logger";
import { injectLambdaContext } from "@aws-lambda-powertools/logger/middleware";
import middy from "@middy/core";
const logger = new Logger({
serviceName: "checkout",
logLevel: "INFO",
// Powertools buffer: hold debug/info until error or explicit flush
});
export const handler = middy(async (event, ctx) => {
logger.appendKeys({
correlationId: event.headers?.["x-correlation-id"] ?? ctx.awsRequestId,
tenantId: event.requestContext?.authorizer?.tenantId,
});
logger.info("start", { path: event.rawPath });
try {
const result = await checkout(event);
// ✅ Sample successes: 1% INFO after success
if (Math.random() < 0.01) logger.info("success_sample", { resultCode: result.code });
return result;
} catch (err) {
// ✅ Errors always flush full buffer + stack
logger.error("checkout_failed", { err });
throw err;
}
}).use(injectLambdaContext(logger, { clearState: true }));
Sampling policy that on-call accepts
| Level | Success path | Error / timeout / DLQ |
|---|---|---|
| DEBUG | off in prod | flush last N buffered |
| INFO | 1% sample | 100% |
| WARN | 100% | 100% |
| ERROR | 100% | 100% + stack |
// lib/sample.ts
export function shouldLogSuccess(sampleRate = 0.01) {
return Math.random() < sampleRate;
}
❌ Sampling ERROR to “save money.” ✅ Cutting DEBUG/INFO volume while preserving every failed invoke’s correlation trail — same discipline as Node.js Event Loop Lag observability.
EMF metrics beat log-counting
import { Metrics, MetricUnit } from "@aws-lambda-powertools/metrics";
const metrics = new Metrics({ namespace: "Checkout", serviceName: "checkout" });
metrics.addMetric("CheckoutOk", MetricUnit.Count, 1);
metrics.publishStoredMetrics();
Prefer Embedded Metric Format for counts/latency; reserve logs for narrative context. That combination usually cuts ingestion more than aggressive sampling alone.
Closing checklist
✅ Dos
– ✅ Powertools Logger + correlation keys on every line
– ✅ Buffer INFO; flush on ERROR/timeout
– ✅ Sample successful INFO; never sample ERROR
– ✅ EMF for cardinality-safe counters
– ✅ Alarm on ingestion GB/day per log group
❌ Don’ts
– ❌ Don’t console.log unstructured strings in hot paths
– ❌ Don’t strip awsRequestId / tenant IDs when sampling
– ❌ Don’t keep DEBUG on in prod “just in case”
– ❌ Don’t create per-invoke log streams manually
Related reading
- Lambda Powertools Node: Structured Logs
- OpenTelemetry for LLMs
- Node.js Event Loop Lag p99
- Lambda Cold Starts on Node 20
Last updated on September 11, 2026
Discover more from CheatCoders
Subscribe to get the latest posts sent to your email.
