vestig
v0.8.0
Published
Leave a trace. A modern, runtime-agnostic structured logging library with automatic PII sanitization and context propagation.
Maintainers
Readme
👣 Vestig
Leave a trace.
A modern, runtime-agnostic structured logging library with automatic PII sanitization and context propagation.
Why Vestig?
Vestig — from Latin vestigium (trace, footprint). Leave a trace of what happened.
| Feature | Vestig | Pino | Winston | |---------|:-----:|:----:|:-------:| | Runtime Agnostic | ✅ | ❌ | ❌ | | Auto PII Sanitization | ✅ | ❌ | ❌ | | GDPR/HIPAA/PCI-DSS Presets | ✅ | ❌ | ❌ | | Zero Config | ✅ | ✅ | ❌ | | TypeScript First | ✅ | ✅ | ⚠️ | | Edge Runtime Support | ✅ | ❌ | ❌ | | Browser Support | ✅ | ❌ | ⚠️ | | Context Propagation | ✅ | ❌ | ❌ | | Multiple Transports | ✅ | ✅ | ✅ | | Zero Dependencies | ✅ | ❌ | ❌ |
Vestig is the only logging library that:
- Works everywhere (Node.js, Bun, Deno, Edge, Browser)
- Automatically sanitizes PII with compliance presets
- Propagates context through async operations
- Has zero runtime dependencies
Installation
# bun
bun add vestig
# npm
npm install vestig
# pnpm
pnpm add vestigQuick Start
import { log } from 'vestig'
// Simple logging
log.info('Hello world')
log.error('Something failed', { userId: 123 })
// Sensitive data is automatically redacted
log.info('User login', {
email: '[email protected]', // → us***@example.com
password: 'secret123', // → [REDACTED]
creditCard: '4111111111111111', // → ****1111
})Features
Multi-Transport Support
Send logs to multiple destinations simultaneously:
import { createLogger, ConsoleTransport, HTTPTransport, DatadogTransport } from 'vestig'
const log = createLogger()
// Add HTTP transport for centralized logging
log.addTransport(new HTTPTransport({
name: 'api-logs',
url: 'https://logs.example.com/ingest',
headers: { 'Authorization': 'Bearer token' },
}))
// Add Datadog for observability
log.addTransport(new DatadogTransport({
name: 'datadog',
apiKey: process.env.DD_API_KEY,
service: 'my-app',
tags: ['env:production'],
}))
// Initialize transports (starts flush timers)
await log.init()
// All logs go to console, HTTP endpoint, and Datadog
log.info('Server started', { port: 3000 })Available Transports
| Transport | Description | Use Case |
|-----------|-------------|----------|
| ConsoleTransport | Console output with colors | Development, debugging |
| HTTPTransport | Send to any HTTP endpoint | Custom log aggregation |
| FileTransport | Write to files with rotation | Server-side logging |
| DatadogTransport | Datadog Log Management | Production observability |
PII Sanitization with Presets
Choose from compliance-focused presets:
import { Sanitizer } from 'vestig'
// GDPR compliance (EU data protection)
const gdprSanitizer = Sanitizer.fromPreset('gdpr')
// HIPAA compliance (healthcare)
const hipaaSanitizer = Sanitizer.fromPreset('hipaa')
// PCI-DSS compliance (payment cards)
const pciSanitizer = Sanitizer.fromPreset('pci-dss')
// Apply to logger
const log = createLogger({
sanitize: 'gdpr', // Use preset name directly
})| Preset | Fields Protected | Patterns Applied |
|--------|-----------------|------------------|
| none | None | None |
| minimal | password, secret, token, key | None |
| default | 26 common fields | Email, Credit Card, JWT |
| gdpr | + name, address, phone, IP | + IP addresses, phone |
| hipaa | + patient, medical, SSN | + SSN pattern |
| pci-dss | + card, CVV, PIN | Full card detection |
Custom Sanitization
import { Sanitizer } from 'vestig'
const sanitizer = new Sanitizer({
fields: [
'customSecret',
{ type: 'prefix', value: 'private_' },
{ type: 'contains', value: 'token' },
],
patterns: [{
name: 'internal-id',
pattern: /ID-[A-Z0-9]+/g,
replacement: '[ID_REDACTED]',
}],
})
const safe = sanitizer.sanitize({
private_key: 'abc123', // → [REDACTED]
auth_token: 'xyz789', // → [REDACTED]
internalId: 'ID-ABC123', // → [ID_REDACTED]
})Child Loggers
const log = createLogger({ namespace: 'app' })
const dbLog = log.child('database')
const cacheLog = log.child('cache')
dbLog.info('Query executed') // [app:database] Query executed
cacheLog.info('Cache hit') // [app:cache] Cache hitContext & Correlation IDs
import { withContext, createCorrelationContext } from 'vestig'
// Next.js API Route
export async function GET(req: Request) {
const context = createCorrelationContext({
requestId: req.headers.get('x-request-id') ?? undefined
})
return withContext(context, async () => {
log.info('Request started')
// All logs include: requestId, traceId, spanId
const result = await fetchData()
log.info('Request completed')
return Response.json(result)
})
}Configuration
Environment Variables
VESTIG_LEVEL=debug # trace | debug | info | warn | error
VESTIG_ENABLED=true # Enable/disable logging
VESTIG_STRUCTURED=true # JSON output (auto-enabled in production)
VESTIG_SANITIZE=true # PII sanitization (default: true)
# Add to context
VESTIG_CONTEXT_SERVICE=api
VESTIG_CONTEXT_VERSION=1.0.0Programmatic
const log = createLogger({
level: 'debug',
enabled: true,
structured: false,
sanitize: 'gdpr', // or true, false, or SanitizeConfig
context: { environment: 'development' }
})Log Levels
| Level | Description |
|-------|-------------|
| trace | Very detailed debugging information |
| debug | Development debugging |
| info | General information |
| warn | Warning messages |
| error | Error messages (includes stack traces) |
Runtime Detection
Vestig automatically detects and adapts to:
- Node.js - Full features with AsyncLocalStorage
- Bun - Full features with AsyncLocalStorage
- Deno - Full features with AsyncLocalStorage (via
node:async_hooks) - Edge Runtime - Vercel Edge, Cloudflare Workers
- Browser - Client-side logging (use with
@vestig/nextfor best experience)
import { RUNTIME, IS_SERVER, IS_DENO } from 'vestig'
console.log(RUNTIME) // 'node' | 'bun' | 'deno' | 'edge' | 'browser' | 'worker' | 'unknown'Auto-Production Mode
In production (NODE_ENV=production), Vestig automatically:
- Sets log level to
warn - Enables structured (JSON) output
- Keeps sanitization enabled
API Reference
createLogger(config?)
Create a new logger instance.
log.trace/debug/info/warn/error(message, metadata?)
Log at the specified level.
log.child(namespace, config?)
Create a namespaced child logger.
log.addTransport(transport)
Add a transport to the logger.
log.removeTransport(name)
Remove a transport by name.
log.flush()
Flush all buffered logs.
log.destroy()
Cleanup all transports (call on shutdown).
withContext(context, fn)
Run a function with the given context.
createCorrelationContext(existing?)
Generate correlation IDs (requestId, traceId, spanId).
Sanitizer.fromPreset(preset)
Create a sanitizer from a preset name.
Contributing
We love contributions! Please read our Contributing Guide to get started.
License
MIT © Arakiss
See LICENSE for more details.
