serverless-event-logger
v1.0.1
Published
🚀 Lightweight, zero-dependency structured JSON logger for AWS Lambda. Optimized for observability with automatic context extraction from serverless events.
Maintainers
Readme
serverless-event-logger
🚀 Lightweight, zero-dependency structured JSON logger for AWS Lambda. Optimized for observability with automatic context extraction from serverless events.
Features
- Zero dependencies - Minimal bundle size (< 5KB minified)
- Structured JSON output - Perfect for CloudWatch Insights queries
- Automatic context extraction - Captures requestId, userId, segment from events
- Log level filtering - debug, info, warn, error
- Sensitive data redaction - Automatically redact passwords, tokens, etc.
- Timer utility - Measure operation duration
- Child loggers - Add module-specific context
- TypeScript first - Full type definitions included
Installation
npm install serverless-event-loggerQuick Start
import { createLogger } from 'serverless-event-logger';
// Create a logger instance
const logger = createLogger({
service: 'my-lambda',
});
// Basic logging
logger.info('User created', { userId: '123' });
// Output: {"timestamp":"2024-01-15T10:30:00.000Z","level":"info","message":"User created","service":"my-lambda","userId":"123"}
logger.error('Database connection failed', { error: err.message });Usage with Lambda Events
import { createLogger } from 'serverless-event-logger';
import { NormalizedEvent } from 'serverless-event-orchestrator';
const logger = createLogger({
service: 'ml-properties-lambda',
});
async function handler(event: NormalizedEvent) {
// Create logger with request context
const log = logger.withContext(event);
log.info('Handler started');
// Output includes: requestId, userId, segment, path, method
try {
const result = await processRequest(event);
log.info('Request processed', { resultId: result.id });
return result;
} catch (error) {
log.error('Request failed', { error: error.message, stack: error.stack });
throw error;
}
}API Reference
createLogger(config)
Creates a new logger instance.
interface LoggerConfig {
service: string; // REQUIRED: Name of your lambda/service
defaultLevel?: LogLevel; // Default: 'info'
silent?: boolean; // Default: false (set true for tests)
redactPaths?: string[]; // Fields to redact (e.g., ['password', 'token'])
timestampFormat?: 'iso' | 'epoch'; // Default: 'iso'
pretty?: boolean; // Default: false (formatted output for local dev)
}
type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'silent';Logging Methods
logger.debug(message: string, data?: object): void
logger.info(message: string, data?: object): void
logger.warn(message: string, data?: object): void
logger.error(message: string, data?: object): voidlogger.withContext(event)
Creates a new logger with context extracted from a NormalizedEvent:
const log = logger.withContext(event);
// Extracts: requestId, userId, segment, path, method, userAgentlogger.child(fields)
Creates a child logger with additional fixed fields:
const dbLogger = logger.child({ module: 'dynamodb' });
dbLogger.info('Query executed', { table: 'Users' });
// Output includes module: 'dynamodb'logger.startTimer(label)
Measures operation duration:
const timer = log.startTimer('database-query');
await db.query(...);
timer.done({ recordsFound: 150 });
// Output: {"message":"database-query completed","duration":234,"recordsFound":150,...}Configuration
Environment Variables
| Variable | Description | Default |
|----------|-------------|---------|
| LOG_LEVEL | Minimum log level | info |
| LOG_SILENT | Disable all logs | false |
| LOG_PRETTY | Formatted output | false |
Sensitive Data Redaction
const logger = createLogger({
service: 'auth-lambda',
redactPaths: ['password', 'token', 'authorization', 'apiKey'],
});
logger.info('Login attempt', {
username: 'john',
password: 'secret123'
});
// Output: {..., "username": "john", "password": "[REDACTED]"}Log Output Structure
Base Fields (always present)
{
"timestamp": "2024-01-15T10:30:00.000Z",
"level": "info",
"message": "Your log message",
"service": "your-service-name"
}Context Fields (with withContext)
{
"requestId": "abc-123-def-456",
"userId": "user_abc123",
"segment": "private",
"path": "/api/users",
"method": "POST",
"userAgent": "Mozilla/5.0..."
}CloudWatch Insights Queries
Find errors by endpoint
fields @timestamp, message, path, method, error, userId
| filter level = "error"
| filter service = "my-lambda"
| sort @timestamp desc
| limit 100Latency analysis
fields @timestamp, path, method, duration
| filter message like /completed/
| stats avg(duration) as avg_ms, max(duration) as max_ms by path, method
| sort avg_ms descUser activity trace
fields @timestamp, level, message, path
| filter userId = "user_abc123"
| sort @timestamp desc
| limit 50TypeScript Support
Full TypeScript definitions are included:
import {
createLogger,
Logger,
LoggerConfig,
LogLevel,
Timer,
NormalizedEventLike
} from 'serverless-event-logger';Best Practices
- Create one logger per service - Initialize at module level
- Use
withContextin handlers - Captures request-specific info - Use
childfor modules - Adds component context - Redact sensitive data - Configure
redactPaths - Use timers for slow operations - Helps identify bottlenecks
// src/logging.ts
export const logger = createLogger({
service: process.env.LAMBDA_NAME || 'unknown-lambda',
redactPaths: ['password', 'token', 'authorization'],
});
// src/handlers/user.ts
import { logger } from '../logging';
export async function createUser(event: NormalizedEvent) {
const log = logger.withContext(event);
log.info('Creating user', { email: event.payload.body.email });
// ...
}
// src/repositories/user-repository.ts
import { logger } from '../logging';
const log = logger.child({ module: 'UserRepository' });
export async function save(user: User) {
const timer = log.startTimer('dynamodb-put');
await db.put(user);
timer.done({ userId: user.id });
}License
MIT © MLHolding
