@zipteams/flush-logger
v1.0.0
Published
NestJS buffered logger — silently discards debug logs on success, flushes full trace on error
Maintainers
Readme
@zipteams/flush-logger
Buffered NestJS logger that silently discards debug logs on success and flushes the full trace on error. Cut observability costs without losing context.
The Problem
Traditional loggers force a tradeoff:
- Log everything → expensive (Datadog/CloudWatch ingest costs)
- Log only errors → blind when debugging (no context leading up to the error)
How It Works
Every debug() and info() call below the configured LOG_LEVEL is buffered in memory. When an error() or critical() is logged, the buffer is flushed to stdout — giving you the full trace of what led to the error. On success, the buffer is silently discarded.
Request starts
logger.debug('Fetching account') → buffered
logger.info('Account found') → buffered
logger.info('Charging card') → buffered
Success path:
logger.clearBuffer() → discarded silently ✓
Failure path:
logger.error('Gateway timeout') → prints immediately
→ flushes buffer (all 3 prior lines) ✓Installation
npm install @zipteams/flush-loggerPeer dependencies (already installed in a NestJS project):
npm install @nestjs/common @nestjs/coreQuick Start
Standalone
import { BufferedLogger } from '@zipteams/flush-logger';
const logger = BufferedLogger.create('PaymentService');
logger.debug('Fetching user account'); // buffered (below LOG_LEVEL=WARNING)
logger.info('Account found, processing payment'); // buffered
logger.error('Payment gateway timeout'); // prints error + flushes bufferNestJS Module (per-request scoped)
// app.module.ts
import { BufferedLoggerModule } from '@zipteams/flush-logger';
@Module({
imports: [
BufferedLoggerModule.forRoot({
serviceName: 'MyApp',
isGlobal: true,
getTraceId: () => asyncLocalStorage.getStore()?.get('traceId') ?? '-',
}),
],
})
export class AppModule {}// your.service.ts
import { BufferedLoggerService } from '@zipteams/flush-logger';
@Injectable()
export class YourService {
constructor(private readonly logger: BufferedLoggerService) {}
async processOrder(id: string) {
this.logger.debug(`Processing order ${id}`);
this.logger.info('Validating inventory');
// If anything throws, use logException() for automatic flush+rethrow
try {
await this.doWork();
} catch (err) {
this.logger.logException(err as Error, 'processOrder');
}
}
}With trace IDs from AsyncLocalStorage
import { AsyncLocalStorage } from 'async_hooks';
const storage = new AsyncLocalStorage<Map<string, string>>();
BufferedLoggerModule.forRoot({
serviceName: 'MyApp',
isGlobal: true,
getTraceId: () => storage.getStore()?.get('x-request-id') ?? '-',
});Configuration
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| serviceName | string | 'Application' | Label in log output |
| maxBufferSize | number | 1000 | Max buffered entries before evicting oldest |
| getTraceId | () => string | () => '-' | Fn to get current request trace ID |
| isGlobal | boolean | false | Register module globally (NestJS only) |
Environment Variables
| Var | Values | Default | Description |
|-----|--------|---------|-------------|
| LOG_LEVEL | DEBUG\|INFO\|WARNING\|ERROR\|CRITICAL | WARNING | Logs below this level are buffered |
API
BufferedLogger
| Method | Description |
|--------|-------------|
| debug(msg, ctx?) | Buffer if below LOG_LEVEL |
| info(msg, ctx?) | Buffer if below LOG_LEVEL |
| forceInfo(msg, ctx?) | Always print, never buffer |
| warning(msg, ctx?) / warn(msg, ctx?) | Print if at or above LOG_LEVEL |
| error(msg, ctx?) | Always print + flush buffer |
| critical(msg, ctx?) | Always print + flush buffer |
| logException(err, ctx?) | Log error + flush buffer + re-throws |
| logExceptionSafe(err, ctx?) | Log error + flush buffer, does not throw |
| flushBuffer() | Manually flush buffer to stdout |
| clearBuffer() | Discard buffer without printing |
| getBufferSize() | Returns current buffer entry count |
| setContext(ctx) | Set default context label for all messages |
createScopedLogger(serviceName, context, maxBufferSize?, getTraceId?)
Helper that creates a BufferedLogger with a pre-set context label — useful for operation-scoped logging.
import { createScopedLogger } from '@zipteams/flush-logger';
const logger = createScopedLogger('OrderService', 'checkout-flow');
logger.debug('Cart validated'); // output includes [checkout-flow]BufferedLoggerModule.forRoot(options)
NestJS DynamicModule. Registers BufferedLoggerService as a REQUEST-scoped provider so each HTTP request gets its own isolated buffer.
Log Output Format
[ServiceName] <pid> - <ISO timestamp> [<traceId>] LEVEL[Context] MessageExample:
[PaymentService] 12345 - 2024-01-15T10:30:00.000Z [req-abc-123] ERROR[processPayment] Gateway timeout
=== LOG BUFFER (what led to this point) ===
[PaymentService] 12345 - 2024-01-15T10:29:59.800Z [req-abc-123] DEBUG[processPayment] Fetching account id=42
[PaymentService] 12345 - 2024-01-15T10:29:59.900Z [req-abc-123] INFO[processPayment] Account found, charging $99.00
=== END LOG BUFFER ===License
MIT
