A fast, flexible logging library built specifically for the Bun runtime.
JellyLogger provides structured logging with multiple transports, automatic redaction, and TypeScript-first design.
- π Bun-Optimized: Built specifically for Bun runtime with native API integration
- π Multiple Transports: Console, File, Discord Webhook, and WebSocket support
- π¨ Flexible Formatters: JSON, Logfmt, NDJSON, and custom formatters
- π Advanced Redaction: Comprehensive data protection with patterns and field-specific rules
- πΆ Child Loggers: Context inheritance with message prefixes and persistent context
- π Bun HTTP Middleware: Dedicated request logger for Bun servers with field control
- π File Rotation: Automatic log rotation with compression and date-based naming
- π Structured Logging: Rich metadata and context support
- π‘οΈ Internal Error Handling: Configurable handlers for transport and formatter errors
- π― TypeScript-First: Full type safety with extensive type definitions
- β‘ High Performance: Optimized for speed and memory efficiency
- π§ Extensible: Plugin architecture for custom transports and formatters
bun add jellyloggerimport { logger } from 'jellylogger';
// Basic logging
logger.info('Hello, JellyLogger!');
logger.error('Something went wrong', { error: 'Connection failed' });
// Structured logging with metadata
logger.info('User login', {
userId: '12345',
ip: '192.168.1.1',
userAgent: 'Mozilla/5.0...',
});
// Using different log levels
logger.trace('Detailed debugging info');
logger.debug('Debug information');
logger.info('General information');
logger.warn('Warning message');
logger.error('Error occurred');
logger.fatal('Critical system error');JellyLogger supports 7 log levels (0-6):
import { LogLevel } from 'jellylogger';
LogLevel.SILENT; // 0 - No logs
LogLevel.FATAL; // 1 - Critical errors
LogLevel.ERROR; // 2 - Errors
LogLevel.WARN; // 3 - Warnings
LogLevel.INFO; // 4 - Information
LogLevel.DEBUG; // 5 - Debug info
LogLevel.TRACE; // 6 - Detailed tracingimport { logger, ConsoleTransport } from 'jellylogger';
logger.addTransport(new ConsoleTransport());import { logger, FileTransport } from 'jellylogger';
logger.addTransport(
new FileTransport('app.log', {
maxSize: '10MB',
maxFiles: 5,
compress: true,
datePattern: 'YYYY-MM-DD',
})
);import { logger, DiscordWebhookTransport } from 'jellylogger';
logger.addTransport(new DiscordWebhookTransport('https://discord.com/api/webhooks/...'));import { logger, WebSocketTransport } from 'jellylogger';
logger.addTransport(new WebSocketTransport('ws://localhost:8080/logs'));JellyLogger provides convenient preset functions:
import { useConsoleAndFile, useConsoleFileAndDiscord, useAllTransports } from 'jellylogger';
// Console + File
useConsoleAndFile('app.log');
// Console + File + Discord
useConsoleFileAndDiscord('app.log', 'https://discord.com/api/webhooks/...');
// All transports
useAllTransports('app.log', 'https://discord.com/api/webhooks/...', 'ws://localhost:8080/logs');import { logger, createFormatter } from 'jellylogger';
// JSON formatter
logger.setOptions({
format: createFormatter('ndjson'),
});
// Logfmt formatter
logger.setOptions({
format: createFormatter('logfmt'),
});
// Default human-readable formatter
logger.setOptions({
format: createFormatter('default'),
});import type { LogFormatter, LogEntry } from 'jellylogger';
class CustomFormatter implements LogFormatter {
format(entry: LogEntry): string {
return `[${entry.levelName}] ${entry.message} ${JSON.stringify(entry.data || {})}`;
}
}
logger.setOptions({ format: new CustomFormatter() });logger.setOptions({
redaction: {
keys: ['password', 'token', 'secret', '*.apiKey'],
replacement: '[REDACTED]',
},
});
logger.info('User data', {
username: 'alice',
password: 'hunter2', // Will be [REDACTED]
});logger.setOptions({
redaction: {
keys: ['password', '*.credentials.*'],
keyPatterns: [/secret/i, /token/i],
valuePatterns: [/\b\d{4}-\d{4}-\d{4}-\d{4}\b/], // Credit cards
redactStrings: true,
stringPatterns: [
/Bearer\s+[\w-]+/gi, // Bearer tokens
/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g, // Emails
],
whitelist: ['user.id'],
redactIn: 'file', // Only redact in file logs
},
});logger.setOptions({
redaction: {
fieldConfigs: {
'user.email': {
replacement: '[EMAIL_REDACTED]',
},
'debug.*': {
disabled: true, // Never redact debug fields
},
'financial.*': {
customRedactor: (value, context) => {
return context.target === 'console' ? value : '[FINANCIAL_DATA]';
},
},
},
},
});logger.setOptions({
redaction: {
customRedactor: (value, context) => {
if (context.path.includes('sensitive')) {
return '[CUSTOM_REDACTED]';
}
return value;
},
auditHook: event => {
console.debug(`Redacted ${event.type} at ${event.context.path}`);
},
},
});Create contextual loggers that inherit parent configuration:
// Create child logger with prefix
const userLogger = logger.child({ messagePrefix: 'USER' });
userLogger.info('Login successful'); // [USER] Login successful
// Child logger with persistent context (v4.1.3+)
const requestLogger = logger.child({
messagePrefix: 'REQUEST',
context: { requestId: 'req-123', userId: 'user-456' },
});
requestLogger.info('Processing request');
// Context automatically included: { requestId: 'req-123', userId: 'user-456', ... }
// Nested child loggers merge context
const moduleLogger = requestLogger.child({
messagePrefix: 'AUTH',
context: { authMethod: 'jwt' },
});
moduleLogger.warn('Invalid token');
// [REQUEST] [AUTH] Invalid token
// Context: { requestId: 'req-123', userId: 'user-456', authMethod: 'jwt', ... }Dedicated middleware for logging Bun HTTP server requests:
import { bunRequestLogger } from 'jellylogger';
const handler = bunRequestLogger(
async (req) => {
// Your handler logic
return new Response('Hello, World!');
},
{
includeHeaders: true,
includeBody: false,
redactHeaders: ['authorization', 'cookie', 'x-api-key'],
logLevel: 'info',
messagePrefix: 'API',
}
);
Bun.serve({
port: 3000,
fetch: handler,
});Features:
- Automatic request/response logging
- Configurable field inclusion (headers, body, metadata)
- Built-in header redaction
- Client IP address capture
- Non-blocking async logging
- Full redaction config support
import { logger, LogLevel } from 'jellylogger';
logger.setOptions({
level: LogLevel.INFO,
format: createFormatter('ndjson'),
colors: {
info: 'blue',
warn: 'yellow',
error: 'red',
},
redaction: {
keys: ['password', 'token'],
redactIn: 'both',
},
});import { FileTransport } from 'jellylogger';
const fileTransport = new FileTransport('app.log', {
maxSize: '50MB',
maxFiles: 10,
compress: true,
datePattern: 'YYYY-MM-DD-HH',
});
logger.addTransport(fileTransport);Automatic log rotation with flexible configuration:
import { FileTransport } from 'jellylogger';
const transport = new FileTransport('logs/app.log', {
maxSize: '100MB', // Rotate when file exceeds 100MB
maxFiles: 30, // Keep 30 old files
compress: true, // Compress rotated files with gzip
datePattern: 'YYYY-MM-DD', // Daily rotation pattern
auditFile: 'logs/.audit.json', // Track rotation events
});Control how JellyLogger handles internal errors (transport failures, formatter issues, etc.):
import { setInternalErrorHandler, setInternalWarningHandler } from 'jellylogger';
// Register custom error handler
setInternalErrorHandler((message, error) => {
// Send to monitoring service instead of console
monitoringService.trackError({
library: 'jellylogger',
message,
error: error instanceof Error ? error.message : String(error),
});
});
// Register custom warning handler
setInternalWarningHandler((message, error) => {
// Handle warnings differently
console.warn(`[JellyLogger Warning] ${message}`, error);
});
// Errors in transports, formatters, redaction are now routed to your handlers
logger.addTransport(new WebSocketTransport('ws://invalid-url'));
logger.info('This continues to work'); // Error handled by your custom handlerBenefits:
- Centralized error monitoring
- Non-blocking failures (other transports continue working)
- Integration with monitoring services
- Consistent error handling across all JellyLogger operations
import type { Transport, LogEntry, TransportOptions } from 'jellylogger';
import { getRedactedEntry, logInternalError } from 'jellylogger';
class DatabaseTransport implements Transport {
async log(entry: LogEntry, options?: TransportOptions): Promise<void> {
try {
// Apply redaction if needed
const redacted = getRedactedEntry(entry, options?.redaction, 'file');
// Store in database
await this.database.insert('logs', {
timestamp: redacted.timestamp,
level: redacted.level,
message: redacted.message,
data: JSON.stringify(redacted.data),
});
} catch (error) {
// Use internal error handler for consistent error reporting
logInternalError('DatabaseTransport.log failed', error);
}
}
async flush(): Promise<void> {
// Flush any pending writes
await this.database.flush();
}
}
logger.addTransport(new DatabaseTransport());JellyLogger includes comprehensive test utilities:
import { MemoryTransport, resetAllMocks } from 'jellylogger/test-utils';
// In your tests
beforeEach(() => {
resetAllMocks();
});
const memoryTransport = new MemoryTransport();
logger.addTransport(memoryTransport);
// Test logging
logger.info('test message');
expect(memoryTransport.logs).toHaveLength(1);
expect(memoryTransport.logs[0].message).toBe('test message');- Usage Guide - Comprehensive usage examples
- API Reference - Complete API documentation
- Extending JellyLogger - Custom transports and formatters
- Migration Guide - Upgrading from other loggers
- Linting & Code Quality - Development workflow and code standards
We welcome contributions! Please see our Contributing Guide for details.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Run tests (
bun test) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
- Built for the Bun runtime
- Inspired by popular logging libraries like Winston, Pino, and Bunyan
- TypeScript-first design for optimal developer experience
Made with β€οΈ for the Bun ecosystem