nugi-logs-ts-sdk
v0.1.0
Published
Framework-agnostic TypeScript observability SDK (logs, metrics, traces, distributed tracing) with batching, retries and buffering. Works with Express, Fastify, NestJS and plain Node.
Maintainers
Readme
nugi-logs-ts-sdk
Framework-agnostic TypeScript observability SDK. One client, three signals — logs, metrics, and traces — with distributed tracing, request & error tracking, and a delivery pipeline that does batching, retries and bounded buffering. Ships adapters for Express, Fastify, and NestJS, and works with plain Node too.
Designed to never crash or block the host app: every collection and delivery path is fault-tolerant and non-blocking.
Install
npm install nugi-logs-ts-sdkRequires Node 16+ (uses the built-in fetch on Node 18+; on Node 16 provide a
global fetch polyfill). No mandatory runtime dependencies. @nestjs/common
and rxjs are optional peers, only needed if you use the NestJS adapter.
Quick start
import { createClient } from 'nugi-logs-ts-sdk';
export const nugi = createClient({
apiKey: process.env.NUGI_API_KEY!,
appId: 'checkout-service',
environment: process.env.NODE_ENV, // defaults to NODE_ENV || 'production'
endpoint: 'https://logs.example.com', // base URL of your ingest backend
// batching / reliability (all optional, sensible defaults shown)
batchSize: 100,
flushIntervalMs: 5000,
maxBufferSize: 10_000,
maxRetries: 3,
sampleRate: 1, // head-based trace sampling 0..1
});Logs
nugi.logger.info('order placed', { orderId, amount });
nugi.logger.error('payment failed', { orderId, reason });
const scoped = nugi.logger.child({ component: 'billing' }); // bound attributes
scoped.warn('retrying charge');Logs are automatically correlated with the active trace/span.
Metrics
nugi.metrics.counter('orders.placed').add(1, { channel: 'web' });
nugi.metrics.gauge('queue.depth').set(42);
nugi.metrics.histogram('db.query.duration', 'ms').record(12.5);Traces & distributed tracing
await nugi.tracer.startActiveSpan('charge-card', { kind: 'client' }, async (span) => {
span.setAttribute('gateway', 'stripe');
// propagate context to a downstream service
const headers = {};
span.inject(headers); // adds W3C `traceparent`
await fetch(url, { headers });
// logs/metrics in here inherit this span automatically
nugi.logger.info('charging');
});Continue an inbound trace from headers:
import { extractContext } from 'nugi-logs-ts-sdk';
const parent = extractContext(req.headers);
nugi.tracer.startActiveSpan('handle', { kind: 'server', parent }, () => { /* ... */ });Error tracking
try { /* ... */ } catch (err) {
nugi.trackError(err, { handled: true, attributes: { orderId } });
}
// or capture uncaught errors globally:
createClient({ /* ... */, captureUncaught: true });Framework adapters (request instrumentation)
Each adapter auto-creates a server span per request, continues inbound
distributed traces, records an access log, and emits RED metrics
(http.server.requests, http.server.duration). Sensitive headers and bodies
are redacted (password, token, secret, authorization, cookies, …).
Express
import { nugiExpress, nugiExpressErrorHandler } from 'nugi-logs-ts-sdk';
app.use(nugiExpress(nugi)); // mount early
// ... your routes ...
app.use(nugiExpressErrorHandler(nugi)); // mount last, before your error handlerFastify
import { nugiFastify } from 'nugi-logs-ts-sdk';
fastify.register(nugiFastify(nugi));NestJS
import { NugiModule, NUGI_CLIENT, NugiErrorInterceptor } from 'nugi-logs-ts-sdk';
@Module({
imports: [
NugiModule.forRoot({
apiKey: process.env.NUGI_API_KEY!,
appId: 'checkout-service',
endpoint: 'https://logs.example.com',
http: { captureRequestBody: false },
}),
],
})
export class AppModule {}Inject the client anywhere to emit custom telemetry:
constructor(@Inject(NUGI_CLIENT) private readonly nugi: NugiClient) {}Register NugiErrorInterceptor as a global interceptor (via APP_INTERCEPTOR)
to auto-capture thrown exceptions. The module flushes on app shutdown.
Adapter options (HttpAdapterOptions): captureHeaders (default true),
captureRequestBody/captureResponseBody (default false), ignore(info),
spanName(info).
Delivery guarantees
- Batching — events flush when
batchSizeis reached or everyflushIntervalMs. - Buffering — bounded at
maxBufferSize; oldest events are dropped on overload. - Retries — failed batches retry with exponential backoff + jitter. Transient failures (network, 408, 429, 5xx) are retried and re-buffered; fatal ones (4xx like 401/400) are dropped.
- Graceful shutdown —
await nugi.shutdown()flushes and tears down (also wired toSIGTERM/SIGINT/beforeExitwhenflushOnExitis on).
Wire format
Each flush POSTs an EventEnvelope to endpoint + ingestPath
(default /api/v1/telemetry/batch) with an X-API-Key header:
{
"resource": { "appId": "...", "serviceName": "...", "environment": "...",
"host": { "hostname": "...", "serverIp": "...", "sdkVersion": "...",
"nodeVersion": "...", "os": "...", "pid": 123 } },
"events": [ /* LogEvent | MetricEvent | SpanEvent | ErrorEvent, each with
timestamp + traceId/spanId when correlated */ ],
"sentAt": "2026-07-05T20:00:00.000Z"
}API surface
createClient(config) / new NugiClient(config) → { logger, metrics, tracer,
trackError, flush, shutdown, resource, bufferSize }. Also exported: the
telemetry types, W3C propagation helpers (extractContext, injectContext,
parseTraceparent, formatTraceparent), context helpers (runWithContext,
getActiveSpanContext), and sanitizeBody/sanitizeHeaders.
