Type-safe telemetry library for Igniter.js with session-based events, typed event registry, transport adapters, and sampling/redaction
npm install @igniter-js/telemetry



Type-safe telemetry for Node.js apps
Session-aware events, typed registries, redaction, sampling, and pluggable transports.
Quick Start • Core Concepts • Examples • Adapters • API Reference
---
Telemetry without structure becomes noise. This package delivers typed, correlated, privacy-safe telemetry with minimal configuration and maximum correctness.
- ✅ Type-safe events with autocompletion via IgniterTelemetryEvents
- ✅ Session correlation using AsyncLocalStorage (no manual context passing)
- ✅ PII protection with redaction + hashing
- ✅ Volume control with sampling policies
- ✅ Pluggable transports for logs, OTLP, Slack, Discord, Sentry, HTTP, and Redis Streams
- ✅ Framework-friendly patterns for Next.js, Express, Fastify, and more
---
``bash`npm
npm install @igniter-js/telemetry
`bash`pnpm
pnpm add @igniter-js/telemetry
`bash`yarn
yarn add @igniter-js/telemetry
`bash`bun
bun add @igniter-js/telemetry
`typescript
import { IgniterTelemetry } from '@igniter-js/telemetry'
import { LoggerTransportAdapter } from '@igniter-js/telemetry/adapters'
const telemetry = IgniterTelemetry.create()
.withService('billing-api')
.withEnvironment(process.env.NODE_ENV ?? 'development')
.addTransport(LoggerTransportAdapter.create({ logger: console }))
.build()
telemetry.emit('service.booted', {
attributes: { 'ctx.service.uptime_ms': 42 },
})
`
✅ Success! You now have structured telemetry with a console transport.
---
``
┌──────────────────────────────────────────────────────────┐
│ Your App │
├──────────────────────────────────────────────────────────┤
│ telemetry.emit('payment.succeeded', { attributes: ... }) │
└───────────────┬──────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────┐
│ IgniterTelemetryManager (runtime) │
│ Sampling → Envelope → Async Session → Redaction │
└───────────────┬──────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────┐
│ Transport Fan-out │
│ Logger | HTTP | OTLP | Sentry | Slack | Discord | Store │
└──────────────────────────────────────────────────────────┘
- Builder: IgniterTelemetry.create() builds immutable configuration.IgniterTelemetryManager
- Manager: emits events and manages sessions.IgniterTelemetryEvents
- Events Registry: defines typed event schemas.telemetry.session()
- Session: binds actor/scope/context across async calls.IgniterTelemetryTransportAdapter
- Transport Adapter: Implement to route events.
1. Direct emit (no explicit session)
2. Manual session handle (telemetry.session())session.run()
3. Scoped execution ()
---
> The examples below are fully aligned with the current implementation.
`typescript
import { IgniterTelemetry } from '@igniter-js/telemetry'
import { LoggerTransportAdapter } from '@igniter-js/telemetry/adapters'
const telemetry = IgniterTelemetry.create()
.withService('api')
.withEnvironment('development')
.addTransport(LoggerTransportAdapter.create({ logger: console }))
.build()
telemetry.emit('app.started')
`
`typescript`
const telemetry = IgniterTelemetry.create()
.withService('api')
.withEnvironment('production')
.withVersion('1.4.2')
.addTransport(LoggerTransportAdapter.create({ logger: console }))
.build()
`typescript`
const telemetry = IgniterTelemetry.create()
.withService('checkout')
.addActor('user', { description: 'Signed-in user' })
.addActor('system')
.addScope('organization', { required: true })
.addScope('workspace')
.addTransport(LoggerTransportAdapter.create({ logger: console }))
.build()
`typescript`
telemetry.emit('cart.item_added', {
attributes: {
'ctx.cart.id': 'cart_123',
'ctx.product.sku': 'sku_987',
'ctx.product.quantity': 2,
},
})
`typescript`
telemetry.emit('payment.failed', {
level: 'error',
error: {
name: 'PaymentError',
message: 'Card declined',
code: 'CARD_DECLINED',
},
attributes: {
'ctx.payment.provider': 'stripe',
},
})
`typescript
const session = telemetry.session()
.actor('user', 'usr_123', { role: 'admin' })
.scope('organization', 'org_789')
.attributes({ 'ctx.request.id': 'req_456' })
session.emit('user.login', {
attributes: { 'ctx.login.method': 'oauth' },
})
await session.end()
`
`typescript
await telemetry.session()
.actor('user', 'usr_123')
.scope('organization', 'org_789')
.run(async () => {
telemetry.emit('request.started', {
attributes: { 'ctx.request.path': '/api/orders' },
})
// ... your logic
telemetry.emit('request.completed', {
attributes: { 'ctx.request.status': 200 },
})
})
`
`typescript
import { z } from 'zod'
import { IgniterTelemetryEvents } from '@igniter-js/telemetry'
const JobsEvents = IgniterTelemetryEvents
.namespace('igniter.jobs')
.event('worker.started', z.object({ 'ctx.worker.id': z.string() }))
.event('worker.stopped', z.object({ 'ctx.worker.id': z.string() }))
.build()
`
`typescript
import { z } from 'zod'
import { IgniterTelemetryEvents } from '@igniter-js/telemetry'
const BillingEvents = IgniterTelemetryEvents
.namespace('igniter.billing')
.group('invoice', (g) =>
g
.event('created', z.object({ 'ctx.invoice.id': z.string() }))
.event('paid', z.object({ 'ctx.invoice.id': z.string(), 'ctx.invoice.amount': z.number() }))
.event('voided', z.object({ 'ctx.invoice.id': z.string() }))
)
.build()
`
`typescript
const telemetry = IgniterTelemetry.create()
.withService('billing')
.addEvents(BillingEvents)
.addTransport(LoggerTransportAdapter.create({ logger: console }))
.build()
telemetry.emit('igniter.billing.invoice.paid', {
attributes: { 'ctx.invoice.id': 'inv_123', 'ctx.invoice.amount': 42 },
})
`
`typescript`
const telemetry = IgniterTelemetry.create()
.withService('api')
.addEvents(BillingEvents, { mode: 'always', strict: true })
.withValidation({ mode: 'always', strict: true })
.addTransport(LoggerTransportAdapter.create({ logger: console }))
.build()
`typescript`
const telemetry = IgniterTelemetry.create()
.withService('api')
.withSampling({
debugRate: 0.01,
infoRate: 0.1,
warnRate: 1.0,
errorRate: 1.0,
always: ['.failed', '.error'],
never: ['health.check', 'metrics.heartbeat'],
})
.addTransport(LoggerTransportAdapter.create({ logger: console }))
.build()
`typescript`
const telemetry = IgniterTelemetry.create()
.withService('api')
.withRedaction({
denylistKeys: ['password', 'token', 'authorization'],
hashKeys: ['email', 'ip', 'userId'],
maxStringLength: 500,
})
.addTransport(LoggerTransportAdapter.create({ logger: console }))
.build()
`typescript`
telemetry.emit('feature.flag.loaded', {
source: { causer: '@myapp/flags', file: 'flags.ts', line: 88 },
attributes: { 'ctx.flag.key': 'new-dashboard' },
})
`typescript
import type { IgniterTelemetryTransportAdapter } from '@igniter-js/telemetry'
import type { IgniterTelemetryEnvelope } from '@igniter-js/telemetry'
class DatadogTransport implements IgniterTelemetryTransportAdapter {
readonly type = 'datadog' as const
async handle(envelope: IgniterTelemetryEnvelope): Promise
await fetch('https://example.com/telemetry', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(envelope),
})
}
}
const telemetry = IgniterTelemetry.create()
.withService('api')
.addTransport(new DatadogTransport())
.build()
`
`typescript
import { InMemoryTransportAdapter } from '@igniter-js/telemetry/adapters'
const memory = InMemoryTransportAdapter.create()
const telemetry = IgniterTelemetry.create()
.withService('api')
.addTransport(memory)
.build()
telemetry.emit('test.event')
const events = memory.getEvents()
console.log(events.length)
`
`typescript
import { MockTelemetryAdapter } from '@igniter-js/telemetry/adapters'
const mock = MockTelemetryAdapter.create()
const telemetry = IgniterTelemetry.create()
.withService('api')
.addTransport(mock)
.build()
telemetry.emit('user.created')
expect(mock.getLastEvent()?.name).toBe('user.created')
`
`typescript
const logger = LoggerTransportAdapter.create({
logger: console,
format: 'json',
minLevel: 'info',
includeTimestamp: true,
})
const telemetry = IgniterTelemetry.create()
.withService('api')
.addTransport(logger)
.build()
`
`typescript
import { HttpTransportAdapter } from '@igniter-js/telemetry/adapters'
const http = HttpTransportAdapter.create({
url: 'https://webhook.example.com/telemetry',
headers: { Authorization: Bearer ${process.env.TELEMETRY_TOKEN} },
timeout: 4000,
retries: 0,
})
const telemetry = IgniterTelemetry.create()
.withService('api')
.addTransport(http)
.build()
`
`typescript
import { OtlpTransportAdapter } from '@igniter-js/telemetry/adapters'
const otlp = OtlpTransportAdapter.create({
url: 'http://localhost:4318/v1/logs',
headers: { 'x-api-key': process.env.OTEL_API_KEY ?? '' },
})
const telemetry = IgniterTelemetry.create()
.withService('api')
.addTransport(otlp)
.build()
`
`typescript
import * as Sentry from '@sentry/node'
import { SentryTransportAdapter } from '@igniter-js/telemetry/adapters'
Sentry.init({ dsn: process.env.SENTRY_DSN })
const sentry = SentryTransportAdapter.create({ sentry: Sentry })
const telemetry = IgniterTelemetry.create()
.withService('api')
.addTransport(sentry)
.build()
`
`typescript
import { SlackTransportAdapter } from '@igniter-js/telemetry/adapters'
const slack = SlackTransportAdapter.create({
webhookUrl: process.env.SLACK_WEBHOOK_URL ?? '',
minLevel: 'error',
username: 'Igniter Bot',
iconEmoji: ':rotating_light:',
})
const telemetry = IgniterTelemetry.create()
.withService('api')
.addTransport(slack)
.build()
`
`typescript
import { DiscordTransportAdapter } from '@igniter-js/telemetry/adapters'
const discord = DiscordTransportAdapter.create({
webhookUrl: process.env.DISCORD_WEBHOOK_URL ?? '',
minLevel: 'warn',
username: 'Igniter Bot',
avatarUrl: 'https://example.com/avatar.png',
})
const telemetry = IgniterTelemetry.create()
.withService('api')
.addTransport(discord)
.build()
`
`typescript
import { TelegramTransportAdapter } from '@igniter-js/telemetry/adapters'
const telegram = TelegramTransportAdapter.create({
botToken: process.env.TELEGRAM_BOT_TOKEN ?? '',
chatId: process.env.TELEGRAM_CHAT_ID ?? '',
minLevel: 'error',
})
const telemetry = IgniterTelemetry.create()
.withService('api')
.addTransport(telegram)
.build()
`
`typescript
import Redis from 'ioredis'
import { StoreStreamTransportAdapter } from '@igniter-js/telemetry/adapters'
const redis = new Redis(process.env.REDIS_URL)
const storeStream = StoreStreamTransportAdapter.create({
redis,
stream: 'telemetry:events',
maxLen: 10000,
approximate: true,
})
const telemetry = IgniterTelemetry.create()
.withService('api')
.addTransport(storeStream)
.build()
`
`typescripttelemetry:${envelope.service}:${envelope.level}
const storeStream = StoreStreamTransportAdapter.create({
redis,
streamBuilder: (envelope) => ,
})
const telemetry = IgniterTelemetry.create()
.withService('api')
.addTransport(storeStream)
.build()
`
`typescript`
const telemetry = IgniterTelemetry.create()
.withService('api')
.addTransport(LoggerTransportAdapter.create({ logger: console }))
.addTransport(OtlpTransportAdapter.create({ url: 'http://localhost:4318/v1/logs' }))
.addTransport(SlackTransportAdapter.create({ webhookUrl: process.env.SLACK_WEBHOOK_URL ?? '' }))
.build()
`typescript
import type { IIgniterTelemetryManager } from '@igniter-js/telemetry'
export function trackCheckoutStart(telemetry: IIgniterTelemetryManager) {
telemetry.emit('checkout.started', {
attributes: { 'ctx.checkout.step': 'payment' },
})
}
`
`typescript
import type { Request, Response, NextFunction } from 'express'
import type { IIgniterTelemetryManager } from '@igniter-js/telemetry'
export function telemetryMiddleware(telemetry: IIgniterTelemetryManager) {
return async (req: Request, _res: Response, next: NextFunction) => {
await telemetry.session()
.actor('user', req.headers['x-user-id'] as string | undefined)
.scope('organization', req.headers['x-org-id'] as string)
.attributes({ 'ctx.request.id': req.headers['x-request-id'] as string })
.run(async () => {
telemetry.emit('request.received', { attributes: { 'ctx.request.path': req.path } })
await next()
})
}
}
`
`typescript
// app/api/health/route.ts
import { IgniterTelemetry } from '@igniter-js/telemetry'
import { LoggerTransportAdapter } from '@igniter-js/telemetry/adapters'
const telemetry = IgniterTelemetry.create()
.withService('next-api')
.withEnvironment(process.env.NODE_ENV ?? 'development')
.addTransport(LoggerTransportAdapter.create({ logger: console }))
.build()
export async function GET() {
telemetry.emit('health.check')
return new Response('ok')
}
`
`typescript
import type { FastifyInstance } from 'fastify'
import type { IIgniterTelemetryManager } from '@igniter-js/telemetry'
export async function telemetryPlugin(app: FastifyInstance, telemetry: IIgniterTelemetryManager) {
app.addHook('onRequest', async (req) => {
await telemetry.session()
.actor('user', (req.headers['x-user-id'] as string) ?? undefined)
.run(async () => {
telemetry.emit('request.received', { attributes: { 'ctx.request.path': req.url } })
})
})
}
`
`typescript
import { IgniterTelemetrySampling } from '@igniter-js/telemetry'
const sampler = IgniterTelemetrySampling.createSampler({
debugRate: 0.0,
infoRate: 0.5,
warnRate: 1.0,
errorRate: 1.0,
})
const shouldLog = sampler('order.created', 'info')
console.log('sampled?', shouldLog)
`
`typescript
import { IgniterTelemetryRedaction } from '@igniter-js/telemetry'
const redact = IgniterTelemetryRedaction.createRedactor({
denylistKeys: ['password'],
hashKeys: ['email'],
maxStringLength: 100,
})
const result = await redact({
password: 'secret',
email: 'user@example.com',
message: 'hello world',
})
console.log(result)
`
`typescript
import { IgniterTelemetryId } from '@igniter-js/telemetry'
const sessionId = IgniterTelemetryId.generateSessionId()
const spanId = IgniterTelemetryId.generateSpanId()
const traceId = IgniterTelemetryId.generateTraceId()
console.log({ sessionId, spanId, traceId })
`
`typescript
import { IgniterTelemetryValidator } from '@igniter-js/telemetry'
IgniterTelemetryValidator.validate('billing.invoice.paid', 'Event')
IgniterTelemetryValidator.validate('igniter.billing', 'Namespace')
`
`typescript`
const session = telemetry.session().attributes({ 'ctx.request.id': 'req_1' })
session.emit('request.started', { attributes: { 'ctx.request.method': 'GET' } })
`typescript`
const session = telemetry.session().actor('user', 'usr_1')
session.emit('admin.action', {
actor: { type: 'system', id: 'scheduler' },
})
`typescript`
const result = await telemetry.session().run(async () => {
telemetry.emit('task.started')
return 42
})
`typescript`
const session = telemetry.session().id('custom-session-id')
session.emit('session.bound')
`typescript`
telemetry.emit('cache.miss', {
source: { causer: '@myapp/cache', file: 'cache.ts', line: 132 },
attributes: { 'ctx.cache.key': 'user:1' },
})
`typescript`
telemetry.emit('audit.event', {
actor: { type: 'user', id: 'usr_1' },
scope: { type: 'organization', id: 'org_1' },
attributes: { 'ctx.audit.action': 'delete' },
})
`typescript`
await telemetry.flush()
await telemetry.shutdown()
`typescript`
const AuthEvents = IgniterTelemetryEvents
.namespace('igniter.auth')
.event('login.succeeded', z.object({ 'ctx.user.id': z.string() }))
.event('login.failed', z.object({ 'ctx.auth.reason': z.string() }))
.build()
`typescript`
const JobEvents = IgniterTelemetryEvents
.namespace('igniter.jobs')
.group('job', (g) =>
g
.event('started', z.object({ 'ctx.job.id': z.string() }))
.event('completed', z.object({ 'ctx.job.id': z.string(), 'ctx.job.duration_ms': z.number() }))
.event('failed', z.object({ 'ctx.job.id': z.string(), 'ctx.job.error': z.string() }))
)
.build()
`typescript`
const StorageEvents = IgniterTelemetryEvents
.namespace('igniter.storage')
.event('file.uploaded', z.object({ 'ctx.file.id': z.string(), 'ctx.file.size': z.number() }))
.event('file.deleted', z.object({ 'ctx.file.id': z.string() }))
.build()
`typescript`
const HttpEvents = IgniterTelemetryEvents
.namespace('igniter.http')
.event('request.started', z.object({ 'ctx.request.path': z.string() }))
.event('request.completed', z.object({ 'ctx.request.status': z.number() }))
.build()
`typescript`
const BillingEvents = IgniterTelemetryEvents
.namespace('igniter.billing')
.group('invoice', (g) =>
g
.event('created', z.object({ 'ctx.invoice.id': z.string() }))
.event('paid', z.object({ 'ctx.invoice.id': z.string(), 'ctx.invoice.amount': z.number() }))
)
.build()
`typescript`
const AnalyticsEvents = IgniterTelemetryEvents
.namespace('igniter.analytics')
.event('event.tracked', z.object({ 'ctx.event.name': z.string() }))
.build()
`typescript`
const telemetry = IgniterTelemetry.create()
.withService('api')
.withSampling({ always: ['*.error'], never: ['health.check'] })
.addTransport(LoggerTransportAdapter.create({ logger: console }))
.build()
`typescript`
const telemetry = IgniterTelemetry.create()
.withService('api')
.withRedaction({ denylistKeys: ['user.password', 'credentials.token'] })
.addTransport(LoggerTransportAdapter.create({ logger: console }))
.build()
---
All adapters are available from the subpath:
`typescript`
import {
LoggerTransportAdapter,
HttpTransportAdapter,
OtlpTransportAdapter,
SlackTransportAdapter,
DiscordTransportAdapter,
TelegramTransportAdapter,
SentryTransportAdapter,
InMemoryTransportAdapter,
MockTelemetryAdapter,
StoreStreamTransportAdapter,
} from '@igniter-js/telemetry/adapters'
| Adapter | Type | Purpose |
| --- | --- | --- |
| LoggerTransportAdapter | logger | Console or structured logs |HttpTransportAdapter
| | http | Send events to any HTTP endpoint |OtlpTransportAdapter
| | otlp | OTLP Logs over HTTP |SentryTransportAdapter
| | sentry | Errors + breadcrumbs to Sentry |SlackTransportAdapter
| | slack | Slack webhook alerts |DiscordTransportAdapter
| | discord | Discord webhook alerts |TelegramTransportAdapter
| | telegram | Telegram Bot alerts |InMemoryTransportAdapter
| | memory | In-memory capture (tests/dev) |MockTelemetryAdapter
| | mock | High-fidelity test adapter |StoreStreamTransportAdapter
| | store | Redis Streams via @igniter-js/store |
#### LoggerTransportAdapter
`typescript`
const adapter = LoggerTransportAdapter.create({
logger: console,
format: 'json',
includeTimestamp: true,
minLevel: 'debug',
})
#### HttpTransportAdapter
`typescript`
const adapter = HttpTransportAdapter.create({
url: 'https://example.com/telemetry',
headers: { Authorization: 'Bearer token' },
timeout: 5000,
retries: 0,
})
#### OtlpTransportAdapter
`typescript`
const adapter = OtlpTransportAdapter.create({
url: 'http://localhost:4318/v1/logs',
headers: { 'x-api-key': 'key' },
})
#### SentryTransportAdapter
`typescript`
const adapter = SentryTransportAdapter.create({ sentry: Sentry })
#### SlackTransportAdapter
`typescript`
const adapter = SlackTransportAdapter.create({
webhookUrl: process.env.SLACK_WEBHOOK_URL ?? '',
minLevel: 'error',
username: 'Igniter Telemetry',
iconEmoji: ':fire:',
})
#### DiscordTransportAdapter
`typescript`
const adapter = DiscordTransportAdapter.create({
webhookUrl: process.env.DISCORD_WEBHOOK_URL ?? '',
minLevel: 'warn',
username: 'Igniter Telemetry',
avatarUrl: 'https://example.com/avatar.png',
})
#### TelegramTransportAdapter
`typescript`
const adapter = TelegramTransportAdapter.create({
botToken: process.env.TELEGRAM_BOT_TOKEN ?? '',
chatId: process.env.TELEGRAM_CHAT_ID ?? '',
minLevel: 'error',
})
#### StoreStreamTransportAdapter
`typescripttelemetry:${envelope.service}
const adapter = StoreStreamTransportAdapter.create({
redis,
stream: 'telemetry:events',
maxLen: 10000,
approximate: true,
streamBuilder: (envelope) => ,`
})
---
`typescript
await telemetry.session()
.scope('order', 'ord_123')
.actor('user', 'usr_99')
.run(async () => {
telemetry.emit('order.payment_started', {
attributes: { 'ctx.payment.provider': 'stripe' },
})
const result = await processPayment('ord_123')
if (result.success) {
telemetry.emit('order.payment_succeeded', {
attributes: { 'ctx.payment.transaction_id': result.id },
})
} else {
telemetry.emit('order.payment_failed', {
level: 'error',
error: { name: 'PaymentError', message: result.error },
})
}
})
`
`typescript`
telemetry.emit('workspace.created', {
scope: { type: 'organization', id: 'org_9' },
attributes: {
'ctx.workspace.id': 'ws_77',
'ctx.workspace.plan': 'pro',
},
})
`typescript
const telemetry = IgniterTelemetry.create()
.withService('bank-api')
.withRedaction({ hashKeys: ['ctx.request.ip'] })
.addTransport(LoggerTransportAdapter.create({ logger: console }))
.build()
telemetry.emit('account.balance_viewed', {
attributes: {
'ctx.account.id': 'acct_45',
'ctx.request.ip': '203.0.113.1',
},
})
`
`typescript
const session = telemetry.session().scope('pipeline', 'build_123')
session.emit('pipeline.stage_started', {
attributes: { 'ctx.stage.name': 'test' },
})
// ... run tests
session.emit('pipeline.stage_completed', {
attributes: { 'ctx.stage.name': 'test', 'ctx.stage.status': 'success' },
})
`
`typescript
await telemetry.session()
.actor('agent', 'planner')
.attributes({ 'ctx.agent.version': 'v3' })
.run(async () => {
telemetry.emit('agent.plan.started', {
attributes: { 'ctx.plan.id': 'plan_42' },
})
// ... plan logic
telemetry.emit('agent.plan.completed', {
attributes: { 'ctx.plan.tokens_used': 812 },
})
})
`
`typescript`
telemetry.emit('fraud.signal.detected', {
level: 'warn',
attributes: {
'ctx.user.id': 'usr_9',
'ctx.fraud.score': 0.93,
'ctx.fraud.rule': 'velocity-check',
},
})
`typescript`
telemetry.emit('patient.record.accessed', {
actor: { type: 'user', id: 'dr_1' },
attributes: {
'ctx.patient.id': 'pat_9',
'ctx.record.type': 'lab',
},
})
`typescript`
telemetry.emit('stream.segment.buffered', {
attributes: {
'ctx.stream.id': 'stream_1',
'ctx.segment.ms': 4000,
},
})
`typescript`
telemetry.emit('sensor.heartbeat', {
attributes: { 'ctx.sensor.id': 'sensor_1', 'ctx.sensor.temp_c': 21.2 },
})
`typescript`
telemetry.emit('search.query.executed', {
attributes: {
'ctx.search.query': 'wireless earbuds',
'ctx.search.results': 128,
},
})
---
`typescript`
const telemetry = IgniterTelemetry.create()
.withService('api')
.withEnvironment('production')
.withVersion('1.2.3')
.addActor('user')
.addScope('organization')
.addEvents(MyEvents)
.withSampling({ infoRate: 0.1 })
.withRedaction({ denylistKeys: ['password'] })
.withValidation({ mode: 'development', strict: false })
.addTransport(LoggerTransportAdapter.create({ logger: console }))
.build()
`typescript`
export interface IgniterTelemetryRedactionPolicy {
denylistKeys?: string[]
hashKeys?: string[]
maxStringLength?: number
}
`typescript`
export interface IgniterTelemetrySamplingPolicy {
debugRate?: number
infoRate?: number
warnRate?: number
errorRate?: number
always?: string[]
never?: string[]
}
`typescript`
export interface IgniterTelemetryEventsValidationOptions {
mode?: 'development' | 'always' | 'none'
strict?: boolean
}
---
`typescript
import { describe, it, expect } from 'vitest'
import { IgniterTelemetry } from '@igniter-js/telemetry'
import { MockTelemetryAdapter } from '@igniter-js/telemetry/adapters'
describe('telemetry', () => {
it('captures events', () => {
const mock = MockTelemetryAdapter.create()
const telemetry = IgniterTelemetry.create()
.withService('test')
.addTransport(mock)
.build()
telemetry.emit('test.event', { attributes: { 'ctx.test': true } })
expect(mock.getEvents()).toHaveLength(1)
expect(mock.getLastEvent()?.name).toBe('test.event')
})
})
`
`typescript
import { InMemoryTransportAdapter } from '@igniter-js/telemetry/adapters'
const memory = InMemoryTransportAdapter.create()
const telemetry = IgniterTelemetry.create()
.withService('test')
.addTransport(memory)
.build()
telemetry.emit('snap.event', { attributes: { 'ctx.a': 1 } })
expect(memory.getEvents()[0].name).toBe('snap.event')
`
---
| ✅ Do | Why | Example |
| --- | --- | --- |
| Use ctx. prefixes | Prevent collisions with system fields | 'ctx.user.id' |session.run(() => ...)
| Use sessions for request scope | Guarantees correlation | |withRedaction({ hashKeys: ['email'] })
| Redact PII | Avoid data leaks | |withSampling({ debugRate: 0.01 })
| Sample debug noise | Reduce telemetry cost | |IgniterTelemetryEvents
| Prefer typed registries | Enforces schema consistency | |
| ❌ Don’t | Why | Example |
| --- | --- | --- |
| Log raw tokens | Security risk | { token: '...' } |scope
| Skip sessions in multi-tenant systems | Context loss | No |'user:login'
| Use spaces or colons in names | Invalid event names | |handle
| Throw inside transport | Disrupts pipeline | throw new Error() |
---
Cause: addTransport() received undefined or null.
Fix: Always pass a concrete adapter instance.
`typescript`
telemetry.addTransport(LoggerTransportAdapter.create({ logger: console }))
Cause: Two IgniterTelemetryEvents descriptors with the same namespace.
Fix: Ensure namespaces are unique.
`typescript`
IgniterTelemetryEvents.namespace('igniter.billing')
Cause: addScope() or addActor() was called with the same key twice.
Fix: Define each actor or scope once.
`typescript`
telemetry.addScope('organization')
Cause: Reserved prefixes, spaces, or colon characters in names.
Fix: Use dot notation with lowercase words.
`typescript`
IgniterTelemetryValidator.validate('billing.invoice.paid', 'Event')
Cause: Emitting on a session after await session.end().
Fix: Create a new session for subsequent events.
`typescript`
const session = telemetry.session()
// ... use
await session.end()
Cause: Adapter init() threw during build().
Fix: Validate adapter config and environment variables.
Cause: Every transport failed to handle the event.
Fix: Inspect adapter logs and ensure network connectivity.
---
`typescript
// app/lib/telemetry.ts
import { IgniterTelemetry } from '@igniter-js/telemetry'
import { LoggerTransportAdapter } from '@igniter-js/telemetry/adapters'
export const telemetry = IgniterTelemetry.create()
.withService('next-app')
.withEnvironment(process.env.NODE_ENV ?? 'development')
.addTransport(LoggerTransportAdapter.create({ logger: console }))
.build()
`
`typescript
// app/api/orders/route.ts
import { telemetry } from '@/app/lib/telemetry'
export async function POST() {
telemetry.emit('orders.created', { attributes: { 'ctx.order.id': 'ord_1' } })
return new Response('ok')
}
`
`typescript
import express from 'express'
import { IgniterTelemetry } from '@igniter-js/telemetry'
import { LoggerTransportAdapter } from '@igniter-js/telemetry/adapters'
const telemetry = IgniterTelemetry.create()
.withService('express-api')
.addTransport(LoggerTransportAdapter.create({ logger: console }))
.build()
const app = express()
app.get('/health', (_req, res) => {
telemetry.emit('health.check')
res.send('ok')
})
`
`typescript
import Fastify from 'fastify'
import { IgniterTelemetry } from '@igniter-js/telemetry'
import { LoggerTransportAdapter } from '@igniter-js/telemetry/adapters'
const telemetry = IgniterTelemetry.create()
.withService('fastify-api')
.addTransport(LoggerTransportAdapter.create({ logger: console }))
.build()
const app = Fastify()
app.get('/ping', async () => {
telemetry.emit('ping')
return { ok: true }
})
`
`typescript
import { IgniterTelemetry } from '@igniter-js/telemetry'
import { LoggerTransportAdapter } from '@igniter-js/telemetry/adapters'
const telemetry = IgniterTelemetry.create()
.withService('bun-app')
.addTransport(LoggerTransportAdapter.create({ logger: console }))
.build()
Bun.serve({
fetch() {
telemetry.emit('http.request')
return new Response('ok')
},
})
`
---
`typescript
import { IgniterTelemetry } from '@igniter-js/telemetry'
const builder = IgniterTelemetry.create()
`
#### Methods
| Method | Description |
| --- | --- |
| create() | Create a new builder instance |
---
`typescript`
import { IgniterTelemetryBuilder } from '@igniter-js/telemetry'
#### Methods
| Method | Description |
| --- | --- |
| withService(name) | Set service name |withEnvironment(name)
| | Set environment name |withVersion(version)
| | Set version |addActor(key, options?)
| | Register actor type |addScope(key, options?)
| | Register scope type |addEvents(descriptor, options?)
| | Register typed events |addTransport(adapter)
| | Add a transport adapter |withSampling(policy)
| | Configure sampling |withRedaction(policy)
| | Configure redaction |withValidation(options)
| | Configure validation options |withLogger(logger)
| | Set internal logger |build()
| | Build manager |buildConfig()
| | Build config without runtime |
#### Example
`typescript`
const telemetry = IgniterTelemetryBuilder.create()
.withService('api')
.withEnvironment('production')
.addTransport(LoggerTransportAdapter.create({ logger: console }))
.build()
---
`typescript`
import { IgniterTelemetryEvents } from '@igniter-js/telemetry'
#### Methods
| Method | Description |
| --- | --- |
| namespace(name) | Start a new namespace |event(name, schema)
| | Add an event |group(name, builder)
| | Add a nested event group |build()
| | Build descriptor |
#### Example
`typescript`
const events = IgniterTelemetryEvents
.namespace('igniter.auth')
.event('login.succeeded', z.object({ 'ctx.user.id': z.string() }))
.build()
---
`typescript`
import { IgniterTelemetryEventsGroup } from '@igniter-js/telemetry'
#### Methods
| Method | Description |
| --- | --- |
| event(name, schema) | Add event to group |group(name, builder)
| | Add nested group |build()
| | Build group events map |
---
`typescript`
import { IgniterTelemetryManager } from '@igniter-js/telemetry'
#### Methods
| Method | Description |
| --- | --- |
| emit(name, input?) | Emit telemetry event |session()
| | Create a session |flush()
| | Flush transports |shutdown()
| | Shutdown transports |service
| | Service name |environment
| | Environment name |version
| | Version name |
---
`typescript`
import { IgniterTelemetrySession } from '@igniter-js/telemetry'
#### Methods
| Method | Description |
| --- | --- |
| id(sessionId) | Set session ID |actor(type, id?, tags?)
| | Set actor |scope(type, id, tags?)
| | Set scope |attributes(attrs)
| | Set attributes |emit(name, input?)
| | Emit with session |run(fn)
| | Run in session context |end()
| | End session |getState()
| | Get session state |
---
#### IgniterTelemetryEnvelope
`typescript`
export interface IgniterTelemetryEnvelope
name: TName
time: string
level: 'debug' | 'info' | 'warn' | 'error'
service: string
environment: string
version?: string
sessionId: string
traceId?: string
spanId?: string
parentSpanId?: string
actor?: { type: string; id?: string; tags?: Record
scope?: { type: string; id: string; tags?: Record
attributes?: Record
error?: { name: string; message: string; code?: string; stack?: string; cause?: string }
source?: { causer?: string; file?: string; line?: number }
}
#### IgniterTelemetryEmitInput
`typescript`
export interface IgniterTelemetryEmitInput
level?: 'debug' | 'info' | 'warn' | 'error'
sessionId?: string
actor?: { type: string; id?: string; tags?: Record
scope?: { type: string; id: string; tags?: Record
attributes?: Record
error?: { name: string; message: string; code?: string; stack?: string; cause?: string }
source?: { causer?: string; file?: string; line?: number }
time?: string
}
#### IgniterTelemetryTransportAdapter
`typescript`
export interface IgniterTelemetryTransportAdapter {
readonly type: string
init?(meta: { service: string; environment: string; version?: string }): Promise
handle(envelope: IgniterTelemetryEnvelope): Promise
flush?(): Promise
shutdown?(): Promise
}
---
`typescript
import { IgniterTelemetryId } from '@igniter-js/telemetry'
const id = IgniterTelemetryId.generateSessionId()
`
`typescript
import { IgniterTelemetrySampling } from '@igniter-js/telemetry'
const shouldSample = IgniterTelemetrySampling.shouldSample(
{ infoRate: 0.1, always: ['*.error'] },
'user.login',
'info',
)
`
`typescript
import { IgniterTelemetryRedaction } from '@igniter-js/telemetry'
const redact = IgniterTelemetryRedaction.createSyncRedactor({
denylistKeys: ['password'],
})
`
`typescript
import { IgniterTelemetryValidator } from '@igniter-js/telemetry'
IgniterTelemetryValidator.validate('igniter.jobs', 'Namespace')
`
---
`typescript
import { IgniterTelemetryError } from '@igniter-js/telemetry'
try {
telemetry.emit('unknown.event')
} catch (error) {
if (IgniterTelemetryError.is(error)) {
console.error(error.code)
}
}
`
Common error codes:
- TELEMETRY_INVALID_TRANSPORTTELEMETRY_DUPLICATE_NAMESPACE
- TELEMETRY_DUPLICATE_SCOPE
- TELEMETRY_DUPLICATE_ACTOR
- TELEMETRY_INVALID_EVENT_NAME
- TELEMETRY_INVALID_NAMESPACE
- TELEMETRY_TRANSPORT_INIT_FAILED
- TELEMETRY_TRANSPORT_FAILED
- TELEMETRY_SESSION_ENDED
-
---
This package uses Node-only APIs (AsyncLocalStorage, crypto, etc.) and ships a server-only shim. It is not intended for browser environments.
---
- @igniter-js/store — Used by StoreStreamTransportAdapter@igniter-js/common` — Shared types and error base classes
-
---
See CONTRIBUTING.md for setup and contribution guidelines.
---
MIT