@power.u.1337/unilogger
v0.11.3
Published
Universal, customizable logger for Node.js with multiple transports.
Maintainers
Readme
🪵 UniLogger
Universal, fully customizable logger for Node.js.
Installation
npm install @power.u.1337/unilogger
# Optional drivers (install only what you need)
npm install mongodb # For MongoDB
npm install pg # For PostgreSQL
npm install mysql2 # For MySQL
npm install better-sqlite3 # For SQLite
npm install @elastic/elasticsearch # For Elasticsearch
# Webhook and Discord require no additional dependencies!Quick Start
import { Logger, presets } from '@power.u.1337/unilogger';
const builder = new Logger<StandardLogMethods>({
name: 'my-app',
types: presets.standard,
transports: [{ type: 'console', enabled: true }],
});
const logger = builder.build();
logger.info('Application started');
logger.error('Something failed', new Error('Connection timeout'));
logger.trace('Entering function X...');Transports
Console Transport
{
type: 'console',
enabled: true,
formatter: 'pretty',
colorize: true,
}Console Transport with Transform Callback
Transform the payload before formatting - perfect for converting metadata to JSON, custom formatting, etc.
{
type: 'console',
enabled: true,
formatter: 'pretty',
colorize: true,
format: '[{timestamp}] {emoji} {label}: {message} | {meta}',
transformPayload: (payload) => ({
// Convert meta object to JSON string
meta: payload.meta ? JSON.stringify(payload.meta) : undefined,
}),
}Advanced Transform Example
Format nested metadata fields:
{
type: 'console',
enabled: true,
format: '[{timestamp}] {emoji} {label}: {message} [Info: {meta.id}/{meta.name}]',
transformPayload: (payload) => {
if (!payload.meta) return payload;
// Return transformed fields that can be used in format string
return {
meta: {
id: payload.meta.id ?? 'N/A',
name: payload.meta.name ?? 'N/A',
},
};
},
}Usage:
const logger = builder.build();
logger.info('Processing request', {
id: '1337',
name: 'The name',
});
// Output: [12/03/2025 10:45:23 AM] ℹ️ INFO: Processing request [Info: 1337/The name]Transform to Flat Structure
Convert nested meta to flat key=value format:
{
type: 'console',
enabled: true,
format: '[{timestamp}] {emoji} {label}: {message} {meta}',
transformPayload: (payload) => {
if (! payload.meta) return payload;
// Flatten nested object
const flatMeta: Record<string, unknown> = {};
const flatten = (obj: Record<string, unknown>, prefix = '') => {
for (const [key, value] of Object.entries(obj)) {
const fullKey = prefix ? `${prefix}.${key}` : key;
if (value && typeof value === 'object' && ! Array.isArray(value)) {
flatten(value as Record<string, unknown>, fullKey);
} else {
flatMeta[fullKey] = value;
}
}
};
flatten(payload.meta);
return { meta: flatMeta };
},
}File Transport
{
type: 'file',
enabled: true,
path: './logs/app.log',
formatter: 'json',
rotation: { type: 'daily', maxFiles: 30, compress: true },
}MongoDB Transport
{
type: 'mongodb',
enabled: true,
uri: 'mongodb://localhost:27017',
database: 'logs',
collection: 'app_logs',
batchSize: 100,
flushInterval: 5000,
}PostgreSQL Transport
{
type: 'postgres',
enabled: true,
connection: 'postgresql://user:password@localhost:5432/logs',
table: 'app_logs',
batchSize: 100,
}MySQL Transport
{
type: 'mysql',
enabled: true,
connection: 'mysql://user:password@localhost:3306/logs',
table: 'app_logs',
batchSize: 100,
}SQLite Transport
{
type: 'sqlite',
enabled: true,
path: './logs/app.db',
table: 'logs',
batchSize: 100,
}Elasticsearch Transport
{
type: 'elasticsearch',
enabled: true,
node: 'http://localhost:9200',
index: 'logs',
dailyIndex: true,
batchSize: 100,
}Webhook Transport
{
type: 'webhook',
enabled: true,
url: 'https://your-api.com/logs',
headers: { 'Authorization': 'Bearer token' },
batch: true,
batchSize: 50,
retry: { attempts: 3, delay: 1000 },
}Discord Transport
Send beautiful log messages to Discord with rich embeds, mentions, and colors!
// Basic setup
{
type: 'discord',
enabled: true,
webhookUrl: 'https://discord.com/api/webhooks/xxx/yyy',
}
// With custom bot name and avatar
{
type: 'discord',
enabled: true,
webhookUrl: 'https://discordcom/api/webhooks/xxx/yyy',
username: 'App Logger',
avatarUrl: 'https://example.com/bot-avatar.png',
}
// Errors only with mentions
{
type: 'discord',
enabled: true,
webhookUrl: 'https://discord.com/api/webhooks/xxx/yyy',
username: '🚨 Alert Bot',
types: ['error', 'fatal'],
mentionRoles: ['123456789012345678'], // Role ID
mentionUsers: ['987654321098765432'], // User ID
}
// Alerts only (isAlert: true)
{
type: 'discord',
enabled: true,
webhookUrl: 'https://discord.com/api/webhooks/xxx/yyy',
alertsOnly: true,
mentionRoles: ['ops-team-role-id'],
}
// Plain text instead of embeds
{
type: 'discord',
enabled: true,
webhookUrl: 'https://discord.com/api/webhooks/xxx/yyy',
useEmbeds: false,
}
// With rate limiting (default: 30/min)
{
type: 'discord',
enabled: true,
webhookUrl: 'https://discord.com/api/webhooks/xxx/yyy',
rateLimit: 20, // Max 20 messages per minute
}Discord Embed Example
When you log an error:
logger.error('Database connection failed', {
host: 'db.example.com',
error: new Error('ECONNREFUSED'),
});Discord shows a beautiful embed:
┌─────────────────────────────────────────┐
│ ❌ ERROR │
├─────────────────────────────────────────┤
│ Database connection failed │
│ │
│ 📦 Service 🔗 Trace ID 🖥️ Host │
│ my-app lxk3j2-a8d server-01 │
│ │
│ 📋 Details │
│ ┌─────────────────────────────────────┐ │
│ │ { │ │
│ │ "host": "db.example.com", │ │
│ │ "error": {...} │ │
│ │ } │ │
│ └─────────────────────────────────────┘ │
│ │
│ 🔥 Stack Trace │
│ ┌─────────────────────────────────────┐ │
│ │ Error: ECONNREFUSED │ │
│ │ at Connection.connect (...) │ │
│ └─────────────────────────────────────┘ │
│ │
│ Priority: 2 | PID: 12345 │
└─────────────────────────────────────────┘Getting Discord Webhook URL
- Go to your Discord server
- Right-click a channel → Edit Channel
- Go to Integrations → Webhooks
- Click New Webhook or select existing
- Click Copy Webhook URL
Getting Role/User IDs
- Enable Developer Mode in Discord settings
- Right-click a role/user
- Click Copy ID
Formatters
| Formatter | Description |
| --------- | --------------------------------- |
| pretty | Human-readable, colored (default) |
| json | Structured JSON |
| compact | Single line, minimal |
| logfmt | key=value format |
Format String Tokens
Available tokens in format strings:
| Token | Description |
| --------------- | ------------------------------------------- |
| {timestamp} | Log timestamp (respects timestampFormat) |
| {emoji} | Log type emoji |
| {label} | Log type label (INFO, ERROR, etc.) |
| {message} | Log message |
| {args} | Additional arguments |
| {serviceName} | Service name |
| {traceId} | Trace ID |
| {hostname} | Hostname |
| {pid} | Process ID |
| {meta} | Full metadata object (JSON) |
| {meta.KEY} | Specific metadata field (e.g., {meta.id}) |
Transport Comparison
| Transport | Batching | Rate Limit | Dependencies | Best For | | ------------- | -------- | ---------- | ---------------------- | ------------- | | Console | ❌ | ❌ | None | Development | | File | ❌ | ❌ | None | Local storage | | MongoDB | ✅ | ❌ | mongodb | Scale, TTL | | PostgreSQL | ✅ | ❌ | pg | Analytics | | MySQL | ✅ | ❌ | mysql2 | Web apps | | SQLite | ✅ | ❌ | better-sqlite3 | Desktop | | Elasticsearch | ✅ | ❌ | @elastic/elasticsearch | Search | | Webhook | ✅ | ❌ | None | Custom APIs | | Discord | ❌ | ✅ 30/min | None | Alerts, Team |
Multiple Transports Example
import { Logger, presets } from '@power.u.1337/unilogger';
type ProdLogMethods = 'trace' | 'debug' | 'info' | 'success' | 'warn' | 'error' | 'fatal';
const builder = new Logger<ProdLogMethods>({
name: 'production-app',
types: presets.standard,
transports: [
// Console for development
{
type: 'console',
enabled: process.env.NODE_ENV !== 'production',
},
// File for local backup
{
type: 'file',
enabled: true,
path: './logs/app.log',
formatter: 'json',
rotation: { type: 'daily', maxFiles: 7 },
},
// Discord for critical alerts
{
type: 'discord',
enabled: true,
webhookUrl: process.env.DISCORD_WEBHOOK,
username: '🚨 Production Alerts',
types: ['error', 'fatal'],
mentionRoles: [process.env.OPS_ROLE_ID],
},
// Discord for all logs (different channel)
{
type: 'discord',
enabled: true,
webhookUrl: process.env.DISCORD_LOGS_WEBHOOK,
username: '📋 Log Stream',
rateLimit: 20,
},
],
});
// Step 2: Build the final logging instance
const logger = builder.build();Graceful Shutdown
process.on('SIGTERM', async () => {
logger.info('Shutting down...');
await logger.close();
process.exit(0);
});License
MIT
