Leave a trace. A modern, runtime-agnostic structured logging library with automatic PII sanitization and context propagation.
npm install vestigLeave a trace.
A modern, runtime-agnostic structured logging library with automatic PII sanitization and context propagation.







---
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 | ✅ | ❌ | ❌ |
| Wide Events / Tail Sampling | ✅ | ❌ | ❌ |
| 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
``bashbun
bun add vestig
Quick Start
`typescript
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: 'user@example.com', // → us*@example.com
password: 'secret123', // → [REDACTED]
creditCard: '4111111111111111', // → **1111
})
`Features
$3
Send logs to multiple destinations simultaneously:
`typescript
import { createLogger, HTTPTransport, DatadogTransport, SentryTransport } 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'],
}))
// Add Sentry for error tracking
log.addTransport(new SentryTransport({
name: 'sentry',
dsn: process.env.SENTRY_DSN,
environment: 'production',
release: 'my-app@1.0.0',
minLevel: 'warn', // Only send warn/error to Sentry
}))
// Initialize transports (starts flush timers)
await log.init()
// All logs go to console, HTTP endpoint, Datadog, and Sentry
log.info('Server started', { port: 3000 })
`$3
| 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 |
| SentryTransport | Sentry error tracking | Error monitoring, alerting |$3
Choose from compliance-focused presets:
`typescript
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 |$3
`typescript
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]
})
`$3
`typescript
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 hit
`$3
`typescript
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)
})
}
`$3
Wide Events capture all context about a complete operation in ONE structured event. Instead of scattered logs, you get a single comprehensive record per request.
`typescript
import { createLogger, createWideEvent } from 'vestig'const log = createLogger({
tailSampling: {
enabled: true,
alwaysKeepStatuses: ['error'], // 100% of errors
slowThresholdMs: 2000, // 100% of slow requests
successSampleRate: 0.1, // 10% of successful requests
vipUserIds: ['user-123'], // 100% for VIP users
}
})
// Create and enrich the wide event throughout the request
const event = createWideEvent({ type: 'http.request' })
// Add HTTP context
event.merge('http', {
method: 'POST',
path: '/api/checkout',
status_code: 200,
})
// Add user context
event.merge('user', {
id: 'user-456',
subscription: 'premium',
})
// Add performance metrics
event.merge('performance', {
db_query_ms: 45,
external_api_ms: 230,
})
// End and emit the event
const completedEvent = event.end({ status: 'success' })
log.emitWideEvent(completedEvent)
// Output: ONE event with 50+ fields, easily queryable
`Why Wide Events?
- Debug faster: All context in one place, no log correlation needed
- Reduce costs: Tail sampling keeps 100% of errors, samples success
- Better queries: "Show me slow requests from premium users with payment errors"
- No missing context: You'll never again lose the request that caused an error
Configuration
$3
`bash
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.0
`$3
`typescript
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/next for best experience)`typescript
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 enabledAPI Reference
$3
Create a new logger instance.
$3
Log at the specified level.
$3
Create a namespaced child logger.
$3
Add a transport to the logger.
$3
Remove a transport by name.
$3
Flush all buffered logs.
$3
Cleanup all transports (call on shutdown).
$3
Run a function with the given context.
$3
Generate correlation IDs (requestId, traceId, spanId).
$3
Create a sanitizer from a preset name.
$3
Create a wide event builder for accumulating context throughout a request.
`typescript
const event = createWideEvent({ type: 'http.request' })
event.set('http', 'method', 'POST')
event.merge('user', { id: 'user-123', tier: 'premium' })
const completed = event.end({ status: 'success' })
`$3
Emit a completed wide event through the logger's transports. Applies tail sampling if configured.
$3
Create a tail sampler for wide events. Used internally by
emitWideEvent() but can be used standalone.`typescript
const sampler = createTailSampler({
alwaysKeepStatuses: ['error'],
slowThresholdMs: 2000,
successSampleRate: 0.1,
})
if (sampler.shouldSample(event).sampled) {
log.emitWideEvent(event)
}
``We love contributions! Please read our Contributing Guide to get started.
- 🐛 Report bugs
- 💡 Request features
- 📖 Improve documentation
MIT © Arakiss
See LICENSE for more details.