npm package: @power.u.1337/unilogger
Description: Universal, customizable logger for Node.js with multiple transports.
Author: https://github.com/PowerUser1337
Latest version: 0.11.3

# 🪵 UniLogger

Universal, fully customizable logger for Node.js.

## Installation

```bash
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

```typescript
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

```typescript
{
  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.

```typescript
{
  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:

```typescript
{
  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:

```typescript
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:

```typescript
{
  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

```typescript
{
  type: 'file',
  enabled: true,
  path: './logs/app.log',
  formatter: 'json',
  rotation: { type: 'daily', maxFiles: 30, compress: true },
}
```

### MongoDB Transport

```typescript
{
  type: 'mongodb',
  enabled: true,
  uri: 'mongodb://localhost:27017',
  database: 'logs',
  collection: 'app_logs',
  batchSize: 100,
  flushInterval: 5000,
}
```

### PostgreSQL Transport

```typescript
{
  type: 'postgres',
  enabled: true,
  connection: 'postgresql://user:password@localhost:5432/logs',
  table: 'app_logs',
  batchSize: 100,
}
```

### MySQL Transport

```typescript
{
  type: 'mysql',
  enabled: true,
  connection: 'mysql://user:password@localhost:3306/logs',
  table: 'app_logs',
  batchSize: 100,
}
```

### SQLite Transport

```typescript
{
  type: 'sqlite',
  enabled: true,
  path: './logs/app.db',
  table: 'logs',
  batchSize: 100,
}
```

### Elasticsearch Transport

```typescript
{
  type: 'elasticsearch',
  enabled: true,
  node: 'http://localhost:9200',
  index: 'logs',
  dailyIndex: true,
  batchSize: 100,
}
```

### Webhook Transport

```typescript
{
  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!

```typescript
// 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:

```typescript
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

1. Go to your Discord server
2. Right-click a channel → **Edit Channel**
3. Go to **Integrations** → **Webhooks**
4. Click **New Webhook** or select existing
5. Click **Copy Webhook URL**

#### Getting Role/User IDs

1. Enable **Developer Mode** in Discord settings
2. Right-click a role/user
3. 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

```typescript
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

```typescript
process.on('SIGTERM', async () => {
  logger.info('Shutting down...');
  await logger.close();
  process.exit(0);
});
```

## License

MIT