A structured logger for Fluentd (Node.js implementation)
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Comprehensive error handling with specific error types for different failure scenarios and event-based error reporting.
Handle errors through event listeners on FluentSender instances.
/**
* Error event - emitted when logging or connection errors occur
* @param error - Error object with details about the failure
*/
sender.on('error', (error) => { });
/**
* Connect event - emitted when connection to Fluentd is established
*/
sender.on('connect', () => { });Usage Examples:
const logger = require('fluent-logger');
// Instance-based error handling
const sender = logger.createFluentSender('app', {
host: 'log-server.example.com',
port: 24224,
enableReconnect: true,
reconnectInterval: 30000
});
// Handle connection errors
sender.on('error', (error) => {
console.error('Fluent Logger error:', error.name, error.message);
// Log to alternative destination
console.error('Failed log data:', error.options);
// Implement fallback logging
if (error.name === 'ResponseTimeout') {
console.log('Timeout occurred, data may have been sent');
} else if (error.name === 'DataTypeError') {
console.error('Invalid data format:', error.options.record);
}
});
// Handle successful connections
sender.on('connect', () => {
console.log('Successfully connected to Fluentd');
});
// Use sender with error handling
sender.emit('user_login', { user_id: 12345, method: 'oauth' });
// Singleton error handling
logger.configure('app', {
host: 'localhost',
port: 24224
});
// Note: For singleton, you need to access the internal sender
logger.on('error', (error) => {
console.error('Singleton logger error:', error);
});Specific error classes for different failure scenarios.
// Base error class
class BaseError extends Error {
name: string;
message: string;
options?: object; // Additional error context
}
// Configuration errors
class ConfigError extends BaseError {
// Thrown for invalid configuration options
}
// Missing tag errors
class MissingTag extends BaseError {
// Thrown when required tag is missing
}
// Response validation errors
class ResponseError extends BaseError {
// Thrown when server response is invalid
}
// Response timeout errors
class ResponseTimeout extends BaseError {
// Thrown when acknowledgment timeout occurs
}
// Data type errors
class DataTypeError extends BaseError {
// Thrown when log data is not an object
}
// Authentication handshake errors
class HandshakeError extends BaseError {
// Thrown during security handshake failures
}Usage Examples:
const logger = require('fluent-logger');
try {
// This will throw ConfigError
const sender = logger.createFluentSender('test', {
eventMode: 'InvalidMode'
});
} catch (error) {
if (error.name === 'ConfigError') {
console.error('Configuration error:', error.message);
}
}
// Handle different error types in event handler
const sender = logger.createFluentSender('app', {
host: 'localhost',
port: 24224,
requireAckResponse: true,
ackResponseTimeout: 5000
});
sender.on('error', (error) => {
switch (error.name) {
case 'ConfigError':
console.error('Configuration problem:', error.message);
break;
case 'MissingTag':
console.error('Missing tag:', error.message);
console.error('Context:', error.options);
break;
case 'DataTypeError':
console.error('Invalid data type:', error.message);
console.error('Received:', typeof error.options.record);
break;
case 'ResponseTimeout':
console.error('Response timeout:', error.message);
console.log('Log may have been sent despite timeout');
break;
case 'ResponseError':
console.error('Server response error:', error.message);
console.error('Details:', error.options);
break;
case 'HandshakeError':
console.error('Authentication failed:', error.message);
console.log('Check shared key and hostname configuration');
break;
default:
console.error('Unknown error:', error.name, error.message);
}
});
// Examples that trigger different errors
// DataTypeError - data must be object
sender.emit('test', 'not an object'); // Triggers DataTypeError
// MissingTag - no tag when sender has no tag_prefix
const noTagSender = logger.createFluentSender(null);
noTagSender.emit(null, { data: 'test' }); // Triggers MissingTag errorHandle errors through callbacks on individual emit operations.
/**
* Emit with error callback
* @param label - Event label
* @param data - Log data object
* @param timestamp - Optional timestamp
* @param callback - Callback function (error) => void
*/
sender.emit(label, data, timestamp?, callback);
sender.emit(label, data, callback);
sender.emit(data, callback);Usage Examples:
const logger = require('fluent-logger');
const sender = logger.createFluentSender('app');
// Handle individual emit errors
sender.emit('user_action', {
action: 'login',
user_id: 12345
}, (error) => {
if (error) {
console.error('Failed to send log:', error.message);
// Implement retry logic
setTimeout(() => {
sender.emit('user_action', {
action: 'login',
user_id: 12345,
retry: true
}, (retryError) => {
if (retryError) {
console.error('Retry also failed:', retryError.message);
} else {
console.log('Retry successful');
}
});
}, 1000);
} else {
console.log('Log sent successfully');
}
});
// Callback with timestamp
sender.emit('timed_event', {
event: 'scheduled_task'
}, new Date(), (error) => {
if (error) {
console.error('Scheduled log failed:', error);
}
});
// Promise wrapper for callback-based API
function emitAsync(sender, label, data, timestamp) {
return new Promise((resolve, reject) => {
const args = [label, data];
if (timestamp) args.push(timestamp);
args.push((error) => {
if (error) reject(error);
else resolve();
});
sender.emit.apply(sender, args);
});
}
// Use async/await with error handling
async function logWithRetry(sender, label, data, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
await emitAsync(sender, label, data);
console.log(`Log sent successfully on attempt ${attempt}`);
return;
} catch (error) {
console.error(`Attempt ${attempt} failed:`, error.message);
if (attempt === maxRetries) {
console.error('All retry attempts failed');
throw error;
}
// Wait before retry
await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
}
}
}
// Usage
logWithRetry(sender, 'critical_event', { alert: 'system_overload' })
.catch(error => console.error('Failed to send critical log:', error));Handle automatic reconnection events and failures.
// Reconnection is handled automatically when enableReconnect: true
// Monitor reconnection through error and connect events
interface ReconnectionOptions {
enableReconnect?: boolean; // Enable automatic reconnection (default: true)
reconnectInterval?: number; // Reconnect interval in ms (default: 600000)
}Usage Examples:
const logger = require('fluent-logger');
const sender = logger.createFluentSender('app', {
host: 'unreliable-server.example.com',
port: 24224,
enableReconnect: true,
reconnectInterval: 10000 // Reconnect every 10 seconds
});
let isConnected = false;
let connectionAttempts = 0;
sender.on('connect', () => {
isConnected = true;
connectionAttempts = 0;
console.log('Connected to Fluentd');
});
sender.on('error', (error) => {
isConnected = false;
connectionAttempts++;
console.error(`Connection error (attempt ${connectionAttempts}):`, error.message);
// Implement circuit breaker pattern
if (connectionAttempts > 5) {
console.warn('Too many connection failures, disabling reconnect temporarily');
sender.enableReconnect = false;
// Re-enable reconnection after 5 minutes
setTimeout(() => {
console.log('Re-enabling reconnection');
sender.enableReconnect = true;
connectionAttempts = 0;
}, 300000);
}
});
// Queue logs when disconnected
const logQueue = [];
function safeEmit(label, data) {
if (isConnected) {
sender.emit(label, data, (error) => {
if (error) {
// Re-queue on error
logQueue.push({ label, data });
}
});
} else {
// Queue for later
logQueue.push({ label, data });
}
}
// Flush queue when connection is restored
sender.on('connect', () => {
console.log(`Flushing ${logQueue.length} queued logs`);
while (logQueue.length > 0) {
const { label, data } = logQueue.shift();
sender.emit(label, data);
}
});
// Usage
safeEmit('user_event', { action: 'click', element: 'button' });Handle graceful shutdown with error handling.
/**
* End connection with optional final emit
* @param label - Final event label (optional)
* @param data - Final event data (optional)
* @param callback - Completion callback (optional)
*/
sender.end(label?, data?, callback?);Usage Examples:
const logger = require('fluent-logger');
const sender = logger.createFluentSender('app');
// Graceful shutdown handler
process.on('SIGINT', () => {
console.log('Shutting down gracefully...');
sender.end('shutdown', {
reason: 'SIGINT',
uptime: process.uptime(),
timestamp: Date.now()
}, (error) => {
if (error) {
console.error('Error during shutdown:', error.message);
process.exit(1);
} else {
console.log('Shutdown log sent successfully');
process.exit(0);
}
});
// Force exit after timeout
setTimeout(() => {
console.error('Shutdown timeout, forcing exit');
process.exit(1);
}, 5000);
});
// Multiple senders shutdown
const senders = [
logger.createFluentSender('app'),
logger.createFluentSender('metrics'),
logger.createFluentSender('audit')
];
async function gracefulShutdown() {
console.log('Shutting down all senders...');
const shutdownPromises = senders.map(sender => {
return new Promise((resolve, reject) => {
sender.end('multi_shutdown', { sender: sender.tag_prefix }, (error) => {
if (error) reject(error);
else resolve();
});
});
});
try {
await Promise.all(shutdownPromises);
console.log('All senders shut down successfully');
} catch (error) {
console.error('Error during multi-sender shutdown:', error);
}
}
process.on('SIGTERM', gracefulShutdown);Error objects include contextual information to help with debugging.
interface ErrorOptions {
tag_prefix?: string; // Sender tag prefix
label?: string; // Event label
record?: any; // Log data that caused error
ack?: string; // Expected acknowledgment ID
chunk?: string; // Received acknowledgment ID
}Usage Examples:
const sender = logger.createFluentSender('debug');
sender.on('error', (error) => {
console.error('Error Details:');
console.error(' Type:', error.name);
console.error(' Message:', error.message);
if (error.options) {
console.error(' Context:', JSON.stringify(error.options, null, 2));
// Access specific context fields
if (error.options.tag_prefix) {
console.error(' Tag Prefix:', error.options.tag_prefix);
}
if (error.options.record) {
console.error(' Failed Record:', error.options.record);
}
if (error.options.ack && error.options.chunk) {
console.error(' ACK Mismatch - Expected:', error.options.ack);
console.error(' ACK Mismatch - Received:', error.options.chunk);
}
}
console.error(' Stack:', error.stack);
});