Skip to main content
Version: Next

AsyncHandler

Non-blocking log operations for better application performance.

Overview

AsyncHandler wraps another handler and processes log records asynchronously, preventing log operations from blocking your application's main thread.

Basic Usage

import { New, AsyncHandler, FileHandler } from '@omdxp/jslog';

const logger = New(new AsyncHandler({
handler: new FileHandler({ filepath: './logs/app.log' }),
errorHandler: (err) => console.error('Log error:', err)
}));

// Returns immediately, doesn't block
logger.info('User logged in');

Configuration Options

interface AsyncHandlerOptions {
handler: Handler; // Underlying handler
errorHandler?: (error: Error) => void; // Error callback
}

How It Works

Log records are queued and processed asynchronously:

const logger = New(new AsyncHandler({
handler: new FileHandler({ filepath: './logs/app.log' })
}));

// These return immediately
logger.info('Log 1');
logger.info('Log 2');
logger.info('Log 3');

// Logs are written asynchronously in the background

Error Handling

Always provide an error handler:

import { New, AsyncHandler, FileHandler } from '@omdxp/jslog';

const logger = New(new AsyncHandler({
handler: new FileHandler({ filepath: './logs/app.log' }),
errorHandler: (err) => {
// Handle logging errors
console.error('Failed to write log:', err.message);

// Optionally send to error tracking service
errorTracker.captureException(err);
}
}));

Use Cases

High-Performance APIs

Don't let logging slow down your API:

import { New, AsyncHandler, JSONHandler, HttpReq, HttpRes } from '@omdxp/jslog';

const logger = New(new AsyncHandler({
handler: new JSONHandler()
}));

app.use((req, res, next) => {
const start = Date.now();

res.on('finish', () => {
// Logging happens async, doesn't slow down response
logger.info('Request',
...HttpReq(req),
...HttpRes({
status: res.statusCode,
duration: Date.now() - start
})
);
});

next();
});

Background Job Processing

const logger = New(new AsyncHandler({
handler: new FileHandler({ filepath: './logs/jobs.log' })
}));

async function processJob(job: Job) {
logger.info('Job started', String('jobId', job.id));

try {
await job.execute();
logger.info('Job completed', String('jobId', job.id));
} catch (error) {
logger.error('Job failed', String('jobId', job.id), Err(error));
}
}

Database Operations

const logger = New(new AsyncHandler({
handler: new JSONHandler()
}));

async function queryDatabase(sql: string) {
const start = Date.now();

try {
const result = await db.query(sql);

// Non-blocking log
logger.info('Query executed',
...SqlQuery({ query: sql, duration: Date.now() - start })
);

return result;
} catch (error) {
logger.error('Query failed', String('sql', sql), Err(error));
throw error;
}
}

Combining with BufferedHandler

Maximum performance: async + buffered:

import { 
New,
AsyncHandler,
BufferedHandler,
FileHandler
} from '@omdxp/jslog';

const logger = New(new AsyncHandler({
handler: new BufferedHandler({
handler: new FileHandler({ filepath: './logs/app.log' }),
bufferSize: 100,
flushInterval: 1000
}),
errorHandler: (err) => console.error('Log error:', err)
}));

// Logs are queued async AND buffered
// Maximum throughput, minimum blocking

Graceful Shutdown

Ensure all logs are written before shutdown:

import { New, AsyncHandler, FileHandler } from '@omdxp/jslog';

const asyncHandler = new AsyncHandler({
handler: new FileHandler({ filepath: './logs/app.log' })
});

const logger = New(asyncHandler);

// Shutdown handler
async function shutdown() {
console.log('Shutting down...');

// Wait for async logs to finish
await asyncHandler.close();

process.exit(0);
}

process.on('SIGTERM', shutdown);
process.on('SIGINT', shutdown);

Performance Comparison

import { New, FileHandler, AsyncHandler } from '@omdxp/jslog';

// Synchronous - blocks on each log
const syncLogger = New(new FileHandler({
filepath: './logs/sync.log'
}));

// Asynchronous - returns immediately
const asyncLogger = New(new AsyncHandler({
handler: new FileHandler({ filepath: './logs/async.log' })
}));

// Benchmark
console.time('sync');
for (let i = 0; i < 10000; i++) {
syncLogger.info(`Log ${i}`);
}
console.timeEnd('sync'); // ~500ms

console.time('async');
for (let i = 0; i < 10000; i++) {
asyncLogger.info(`Log ${i}`);
}
console.timeEnd('async'); // ~50ms

Error Recovery

Handle errors gracefully:

const logger = New(new AsyncHandler({
handler: new FileHandler({ filepath: './logs/app.log' }),
errorHandler: (err) => {
// Log to stderr as fallback
console.error('Logging failed:', err.message);

// Try alternate handler
try {
const fallback = new JSONHandler();
fallback.handle(record);
} catch (fallbackErr) {
console.error('Fallback failed:', fallbackErr);
}
}
}));

Best Practices

Do:

  • Always provide an error handler
  • Implement graceful shutdown with close()
  • Use for I/O-bound handlers (file, network)
  • Combine with buffering for maximum performance

Don't:

  • Use for console output (already async in Node.js)
  • Forget to handle errors
  • Expect logs to be written synchronously
  • Skip shutdown handling

Trade-offs

Pros:

  • No blocking on log operations
  • Better application performance
  • Higher throughput
  • Smoother request handling

Cons:

  • Logs written with slight delay
  • Need error handling
  • Harder to debug (logs appear later)
  • Must handle shutdown properly

See Also