npm package: @omdxp/jslog
Description: Structured logging for Node.js that makes Go's log/slog look basic. Features file rotation, buffering, sampling, filtering, async handlers, middleware, and 20+ features Go slog doesn't have!
Author: omdxp
Latest version: 1.10.4

# @omdxp/jslog

A structured logging library for Node.js that makes Go's `log/slog` look basic.

## Why jslog?

- Everything Go's slog has **+ variadic parameters (NEW in v1.7.0!)**
- Plus 20+ features Go `slog` doesn't have
- File logging with auto-rotation
- Buffering, sampling, filtering
- Async handlers
- Middleware pattern
- Metrics collection
- Rate limiting & deduplication
- Colorful output
- And way more...

See full comparison in [SUPERIORITY.md](./SUPERIORITY.md)

## Features

### Core Features (Like Go slog)
- **Structured Logging**: Key-value pairs for easy parsing and analysis
- **Multiple Handlers**: JSON, Text, Multi, and Discard handlers
- **Log Levels**: DEBUG, INFO, WARN, ERROR with dynamic level control
- **Contextual Logging**: Add persistent attributes and groups to loggers
- **TypeScript First**: Full type safety and IntelliSense support
- **Modern Build System**: Built with tsup and tsx
- **Custom Handlers**: Easy to implement custom log handlers
- **Attribute Transformation**: ReplaceAttr function for redacting or transforming values
- **Rich Attribute Types**: String, Int, Float, Bool, Time, Duration, Group, Error, and more
- **Nested Groups**: Organize related attributes hierarchically
- **Dynamic Levels**: Change log levels at runtime with LevelVar

### Advanced Features (Go slog can't do this!)
- **Source Location Tracking**: Automatic file/line tracking with zero dependencies
- **ColorHandler**: Beautiful, colorful console output
- **PrettyHandler**: Format nested objects with proper indentation
- **FileHandler**: Write to files with automatic rotation
- **BufferedHandler**: Batch logs for performance
- **SamplingHandler**: Log only a percentage of messages
- **FilterHandler**: Conditional logging based on custom logic
- **AsyncHandler**: Non-blocking log operations
- **Middleware Pattern**: Compose handlers with middleware
- **Metrics**: Built-in logging statistics
- **Deduplication**: Prevent log spam automatically
- **Rate Limiting**: Automatic rate limiting
- **Fluent API**: Chain attributes with AttrBuilder
- **Performance Timers**: Built-in timing utilities
- **Correlation IDs**: Global request/trace tracking
- **HTTP Helpers**: Easy request/response logging
- **System Info**: Environment and memory helpers
- **Data Masking**: Built-in PII redaction
- **Stack Traces**: Automatic stack trace capture
- **Error Boundaries**: Catch handler errors safely

## Installation

```bash
npm install @omdxp/jslog
```

## Quick Start

```typescript
import { info, warn, error, String, Int } from '@omdxp/jslog';

// Go slog-style variadic parameters (NEW in v1.7.0!)
info('Application started', 'env', 'production', 'port', 3000);
warn('High memory usage', 'percentage', 85);
error('Failed to connect', 'host', 'localhost');

// Traditional style (still works)
info('Application started', String('env', 'production'), Int('port', 3000));

// Mix both styles!
info('Mixed', String('typed', 'value'), 'key', 'value');
```

## Usage

### Basic Logging

```typescript
import { Logger, TextHandler, Level, String, Int } from '@omdxp/jslog';

const logger = new Logger(new TextHandler({ level: Level.DEBUG }));

// Go slog-style (NEW in v1.7.0!)
logger.info('User logged in', 'user', 'alice', 'ip', '192.168.1.1');
logger.warn('Rate limit exceeded', 'requests', 1000);
logger.error('Database connection failed', 'error', 'timeout');

// Traditional style
logger.info('User logged in', String('user', 'alice'), String('ip', '192.168.1.1'));
logger.warn('Rate limit exceeded', Int('requests', 1000));
logger.error('Database connection failed', String('error', 'timeout'));
```

### All Attribute Types

```typescript
import { String, Int, Int64, Float64, Bool, Time, Duration, Any, Group } from '@omdxp/jslog';

// Go slog-style variadic (automatically inferred types)
logger.info('Event',
  'name', 'user.created',
  'userId', 42,
  'score', 98.5,
  'verified', true,
  'metadata', { plan: 'pro' }
);

// Traditional typed style (explicit type control)
logger.info('Event',
  String('name', 'user.created'),
  Int('userId', 42),
  Int64('bigNumber', 9007199254740991),
  Float64('score', 98.5),
  Bool('verified', true),
  Time('createdAt', new Date()),
  Duration('elapsed', 1500), // in milliseconds
  Any('metadata', { plan: 'pro' }),
  Group('address', 
    String('city', 'NYC'),
    String('country', 'USA')
  )
);
```

### JSON Output

```typescript
import { JSONHandler, New } from '@omdxp/jslog';

const logger = New(new JSONHandler());
logger.info('Request processed', String('method', 'GET'), Int('status', 200));
// Output: {"time":"2024-01-01T00:00:00.000Z","level":"INFO","msg":"Request processed","method":"GET","status":200}
```

### Contextual Logging

```typescript
// Add persistent attributes
const requestLogger = logger.with(String('request_id', '123-456'));
requestLogger.info('Processing request');
requestLogger.info('Request completed');

// Group related attributes
const dbLogger = logger.withGroup('database');
dbLogger.info('Connected', String('host', 'localhost'));
// Output: time=... level=INFO msg="Connected" database.host="localhost"
```

### Attribute Helpers

```typescript
import { String, Int, Bool, Any, Error as ErrorAttr } from '@omdxp/jslog';

logger.info('Event',
  String('name', 'user.created'),
  Int('userId', 42),
  Bool('verified', true),
  Any('metadata', { plan: 'pro' })
);

try {
  throw new Error('Something went wrong');
} catch (err) {
  logger.error('Operation failed', ErrorAttr(err as Error));
}
```

### Dynamic Log Levels

```typescript
import { LevelVar, TextHandler } from '@omdxp/jslog';

const levelVar = new LevelVar(Level.INFO);
const logger = new Logger(new TextHandler({ level: levelVar }));

logger.debug('Not visible'); // Won't show
logger.info('Visible');      // Will show

// Change level at runtime
levelVar.set(Level.DEBUG);
logger.debug('Now visible!'); // Will show
```

### Multiple Handlers

```typescript
import { MultiHandler, TextHandler, JSONHandler } from '@omdxp/jslog';

const textHandler = new TextHandler({ level: Level.INFO });
const jsonHandler = new JSONHandler({ level: Level.WARN });
const multiHandler = new MultiHandler([textHandler, jsonHandler]);

const logger = new Logger(multiHandler);
logger.info('Only in text');        // TextHandler only
logger.warn('In both formats');     // Both handlers
logger.error('In both formats');    // Both handlers
```

### Attribute Transformation

```typescript
import { TextHandler } from '@omdxp/jslog';

const logger = new Logger(new TextHandler({
  level: Level.INFO,
  replaceAttr: (groups, attr) => {
    // Redact sensitive fields
    if (attr.key === 'password' || attr.key === 'token') {
      return { key: attr.key, value: '***REDACTED***' };
    }
    // Transform time format
    if (attr.key === 'time' && attr.value instanceof Date) {
      return { key: attr.key, value: attr.value.toLocaleString() };
    }
    return attr;
  }
}));

logger.info('Login', String('user', 'alice'), String('password', 'secret'));
// Output: ... user="alice" password="***REDACTED***"
```

### Discard Handler

```typescript
import { DiscardHandler } from '@omdxp/jslog';

// Useful for benchmarking or disabling logging
const logger = new Logger(new DiscardHandler());
logger.info('This will be discarded'); // No output
```

### Source Location Tracking

Track where log messages originate in your code with zero runtime dependencies:

```typescript
import { Logger, TextHandler, JSONHandler, Level, String } from '@omdxp/jslog';

// Enable source tracking with addSource option
const logger = new Logger(new TextHandler({ 
  level: Level.INFO, 
  addSource: true 
}));

logger.info('User action', String('action', 'login'));
// Output: time=... level=INFO source=app.ts:8 msg="User action" action="login"

// Works with JSON output too
const jsonLogger = new Logger(new JSONHandler({ addSource: true }));
jsonLogger.warn('High memory', String('usage', '85%'));
// Output: {"time":"...","level":"WARN","source":{"function":"checkMemory","file":"app.ts","line":15},...}

// Source tracking in nested functions
function processOrder(orderId: number) {
  logger.info('Processing order', String('id', orderId));
  // Shows the actual line where this log call is made
}

// Works in class methods too
class UserService {
  private logger = new Logger(new JSONHandler({ addSource: true }));
  
  createUser(name: string) {
    this.logger.info('Creating user', String('name', name));
    // Source shows: UserService.createUser at line X
  }
}
```

**Features:**
- **Zero Dependencies**: No runtime dependencies, pure stack trace parsing
- **TypeScript Support**: Shows `.ts` files during development with tsx/ts-node
- **Production Ready**: Shows compiled `.js` files in production
- **Smart Filtering**: Automatically skips jslog internal frames to show your code
- **Function Names**: Captures function/method names when available
- **Relative Paths**: Shows paths relative to cwd for cleaner output

**When to use:**
- Development and debugging
- Troubleshooting production issues
- Audit logging where source location matters
- Understanding code flow in complex applications

**Note:** Source tracking adds minimal overhead (one stack trace capture per log call). Only enable it when needed using the `addSource` handler option.

## API Reference

### Loggers

- `Default()` - Get the default logger
- `SetDefault(logger)` - Set the default logger
- `New(handler)` - Create a new logger with a handler

### Log Levels

```typescript
enum Level {
  DEBUG = -4,
  INFO = 0,
  WARN = 4,
  ERROR = 8,
}
```

### Handlers

- `TextHandler` - Human-readable text format
- `JSONHandler` - Structured JSON format
- `MultiHandler` - Send to multiple handlers
- `DiscardHandler` - Discard all logs (for testing/benchmarking)

### Handler Options

```typescript
interface HandlerOptions {
  level?: Level | LevelVar;           // Minimum log level
  addSource?: boolean;                // Add source location
  replaceAttr?: (groups, attr) => Attr; // Transform attributes
}
```

### Logger Methods

- `log(level, msg, ...attrs)` - Log at specific level
- `logAttrs(level, msg, ...attrs)` - Efficient logging variant
- `debug(msg, ...attrs)` - Log at DEBUG level
- `debugContext(msg, ...attrs)` - Debug with context (future)
- `info(msg, ...attrs)` - Log at INFO level
- `infoContext(msg, ...attrs)` - Info with context (future)
- `warn(msg, ...attrs)` - Log at WARN level
- `warnContext(msg, ...attrs)` - Warn with context (future)
- `error(msg, ...attrs)` - Log at ERROR level
- `errorContext(msg, ...attrs)` - Error with context (future)
- `with(...attrs)` - Create logger with persistent attributes
- `withGroup(name)` - Create logger with attribute group
- `enabled(level)` - Check if level is enabled

### Attribute Functions

- `String(key, value)` - String attribute
- `Int(key, value)` - Integer attribute
- `Int64(key, value)` - 64-bit integer attribute
- `Uint64(key, value)` - Unsigned 64-bit integer attribute
- `Float64(key, value)` - Float attribute
- `Bool(key, value)` - Boolean attribute
- `Time(key, date)` - Date/time attribute
- `Duration(key, ms)` - Duration in milliseconds
- `Any(key, value)` - Any value attribute
- `Group(key, ...attrs)` - Group attributes
- `Error(err)` - Error attribute with stack trace
- `attr(key, value)` - Generic attribute constructor

### Convenience Functions

- `debug(msg, ...attrs)` - Log at DEBUG level
- `debugContext(msg, ...attrs)` - Debug with context
- `info(msg, ...attrs)` - Log at INFO level
- `infoContext(msg, ...attrs)` - Info with context
- `warn(msg, ...attrs)` - Log at WARN level
- `warnContext(msg, ...attrs)` - Warn with context
- `error(msg, ...attrs)` - Log at ERROR level
- `errorContext(msg, ...attrs)` - Error with context
- `log(level, msg, ...attrs)` - Log at specific level
- `logAttrs(level, msg, ...attrs)` - Efficient variant
- `with_(...attrs)` - Get default logger with attributes
- `withGroup(name)` - Get default logger with group

## Development

```bash
# Install dependencies
npm install

# Run tests (validates all examples)
npm test

# Run individual examples
npm run test:basic      # Basic logging features
npm run test:advanced   # Advanced features
npm run test:beast      # Beast mode (all 20+ features)

# Build
npm run build

# Type check
npm run typecheck

# Watch mode for development
npm run dev
```

### Running Tests

The test suite validates that all examples work correctly:

```bash
# Run all tests with assertions
npm test

# Expected output:
# Starting test suite
# Running: Basic Tests
# PASSED: All 15 expectations met
# Running: Advanced Tests
# PASSED: All 22 expectations met
# Running: Beast Mode Tests
# PASSED: All 22 expectations met
# All tests passed!
```

Tests validate:
- Basic logging (TextHandler, JSONHandler, attributes, groups, levels)
- Advanced features (LevelVar, all attribute types, nested groups, replaceAttr, etc.)
- Beast mode features (20+ advanced handlers and utilities)

### Graceful Shutdown

When using handlers with resources (files, timers, async operations), always call `close()`:

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

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

// On shutdown
process.on('SIGTERM', () => {
  // FileHandler.close() is synchronous
  fileHandler.close();
  process.exit(0);
});

// For async handlers (BufferedHandler, AsyncHandler, etc.)
const asyncHandler = new AsyncHandler({ handler: new JSONHandler() });
process.on('SIGTERM', async () => {
  // These handlers need await
  await asyncHandler.close();
  process.exit(0);
});
```

Handlers that need closing:
- `FileHandler` - Closes file stream (sync: `fileHandler.close()`)
- `BufferedHandler` - Flushes buffer and clears timer (async: `await handler.close()`)
- `AsyncHandler` - Waits for pending operations (async: `await handler.close()`)
- `MultiHandler` - Cascades close to all wrapped handlers (async: `await handler.close()`)
- `MiddlewareHandler` - Delegates close to wrapped handler (async: `await handler.close()`)

## Comparison with Go's log/slog

This library closely mirrors Go's `log/slog` API:


| Go slog             | jslog                | Status        |
| ------------------- | -------------------- | ------------- |
| `slog.Debug()`      | `debug()`            | Implemented   |
| `slog.Info()`       | `info()`             | Implemented   |
| `slog.Warn()`       | `warn()`             | Implemented   |
| `slog.Error()`      | `error()`            | Implemented   |
| `slog.New()`        | `New()`              | Implemented   |
| `slog.Default()`    | `Default()`          | Implemented   |
| `slog.SetDefault()` | `SetDefault()`       | Implemented   |
| `slog.With()`       | `logger.with()`      | Implemented   |
| `slog.WithGroup()`  | `logger.withGroup()` | Implemented   |
| `slog.String()`     | `String()`           | Implemented   |
| `slog.Int()`        | `Int()`              | Implemented   |
| `slog.Bool()`       | `Bool()`             | Implemented   |
| `slog.Time()`       | `Time()`             | Implemented   |
| `slog.Duration()`   | `Duration()`         | Implemented   |
| `slog.Group()`      | `Group()`            | Implemented   |
| `slog.Any()`        | `Any()`              | Implemented   |
| `slog.TextHandler`  | `TextHandler`        | Implemented   |
| `slog.JSONHandler`  | `JSONHandler`        | Implemented   |
| `slog.Level`        | `Level`              | Implemented   |
| `slog.LevelVar`     | `LevelVar`           | Implemented   |
| `Handler` interface | `Handler`            | Implemented   |
| `ReplaceAttr`       | `replaceAttr` option | Implemented   |

## Inspiration

This library is inspired by Go's [`log/slog`](https://pkg.go.dev/log/slog) package, bringing structured logging patterns to the Node.js ecosystem.

## License

[MIT License](./LICENSE)