@kyrontha/lens
v0.1.3
Published
Official Kyrontha Lens SDK — buffered async log shipping to lens.kyrontha.io
Maintainers
Readme
@kyrontha/lens
Official Node.js SDK for Kyrontha Lens — buffered, async, fire-and-forget log shipping.
- Non-blocking. Logging never waits for the network. Batches flush every 5s or every 50 events.
- Doesn't crash your app. A Lens outage drops events after retry; your code keeps running.
- Drains on shutdown. Hooks
beforeExit,SIGTERM,SIGINTso the last few seconds of logs aren't lost on container restart. - TypeScript-first. Ships
.d.tsdeclarations.
Install
# Once published to npm:
npm install @kyrontha/lens
# Until then (early access from our git repo):
npm install git+https://dev.azure.com/jblamba/Kyrontha%20Lens/_git/Kyrontha%20Lens#main:sdk/nodeNode 18+ required (uses the global
fetchAPI).
Get an API key
In Kyrontha Lens, go to Connections → Connect via SDK, name your connection, and copy the API key shown on screen. The first event you ship will auto-promote the connection from "awaiting first event" to "connected".
Quick start
import { KyronthaLogger } from '@kyrontha/lens';
const log = new KyronthaLogger({
apiKey: process.env.KYRONTHA_KEY!,
source: 'checkout-api',
});
log.info('order placed', { orderId: 42, userId: 'u-123' });
log.error('payment failed', { orderId: 42, reason: 'declined' });In the Lens UI, the connection card's View logs button takes you to a pre-filtered view of just these events.
Winston
import winston from 'winston';
import { createKyronthaWinstonTransport } from '@kyrontha/lens';
const logger = winston.createLogger({
transports: [
new winston.transports.Console(),
createKyronthaWinstonTransport({ apiKey: process.env.KYRONTHA_KEY!, source: 'checkout-api' }),
],
});
logger.info('order placed', { orderId: 42 });winston-transport is declared as an optional peer dependency — install it (and winston) yourself: npm install winston winston-transport.
Pino
import pino from 'pino';
import { createKyronthaPinoStream } from '@kyrontha/lens';
const lens = createKyronthaPinoStream({
apiKey: process.env.KYRONTHA_KEY!,
source: 'checkout-api',
});
// Important: use the TWO-arg form pino(opts, dest). The single-arg form
// pino(dest) makes pino misdetect the destination and silently fall back
// to its default stdout — your events never reach Lens.
const logger = pino({}, lens);
logger.info({ orderId: 42 }, 'order placed');
// Drain on shutdown so in-flight POSTs aren't aborted by Node teardown.
process.on('SIGINT', async () => { await lens.flush(); process.exit(0); });
process.on('SIGTERM', async () => { await lens.flush(); process.exit(0); });To also log to stdout, wrap both in pino.multistream (still using the two-arg form):
const logger = pino({}, pino.multistream([
{ stream: process.stdout },
{ stream: lens },
]));The returned object exposes flush() and close() (both Promise<void>) — call before short-lived scripts exit so the buffered batch actually leaves the process. The library's own beforeExit handler does its best, but it can't keep the event loop alive long enough for an in-flight HTTP POST to finish.
No peer dependency on pino — we just return a plain object with write() that consumes Pino's newline-JSON output.
Bunyan
import bunyan from 'bunyan';
import { createKyronthaBunyanStream } from '@kyrontha/lens';
const log = bunyan.createLogger({
name: 'checkout-api',
streams: [
{ level: 'info', stream: process.stdout },
{
level: 'info',
stream: createKyronthaBunyanStream({ apiKey: process.env.KYRONTHA_KEY!, source: 'checkout-api' }),
type: 'raw', // important — keeps Bunyan from JSON-stringifying first
},
],
});
log.info({ orderId: 42 }, 'order placed');
// Drain on shutdown — the returned object exposes the same flush()/close()
// helpers as the Pino adapter and the standalone KyronthaLogger.
process.on('SIGTERM', async () => {
await log.streams.find(s => s.type === 'raw')?.stream.flush();
process.exit(0);
});The Bunyan stream object exposes flush() and close() — call before short-lived scripts exit so the buffered batch actually leaves the process.
No peer dependency on bunyan. The type: 'raw' is required so Bunyan calls stream.write(record) with a JS object instead of a serialized line.
API
new KyronthaLogger(options)
| Option | Type | Default | Notes |
| ----------------- | -------------------------- | ---------------------------------------- | ---------------------------------------------------------- |
| apiKey | string | (required) | From a Lens SDK connection |
| endpoint | string | https://lens.kyrontha.io/api/ingest | Override for self-hosted Lens |
| source | string | KYRONTHA_SOURCE env, or 'app' | Shows in the Source column of the Lens UI |
| flushIntervalMs | number | 5000 | Background flush cadence |
| flushBatchSize | number | 50 | Buffer size that forces an immediate flush |
| maxRetries | number | 3 | Per-batch, exponential backoff (250ms → 500ms → 1s → ...) |
| onError | (err: Error) => void | console.warn | Pass () => {} to silence |
Methods
| Method | Notes |
| ------------------------------- | -------------------------------------------------------------------- |
| log(level, message, meta?) | Generic — level is any string, lower-cased before send |
| fatal/error/warn/info/debug | Convenience for the standard levels |
| flush(): Promise<void> | Force-flush — await log.flush() before short-lived scripts exit |
| close(): Promise<void> | Stop the background timer and drain the buffer |
How it works
- Every
log()call appends to an in-memory buffer — synchronous, no I/O. - A background timer flushes the buffer to
POST /api/ingesteveryflushIntervalMs. The buffer also flushes immediately when it hitsflushBatchSizeevents. POST /api/ingestaccepts an array of{ level, message, source, raw, ... }objects with the API key inx-api-key.- The Lens backend tags every event with the
connection_idlinked to your API key, so the View logs button on your connection card filters precisely. - On graceful shutdown (
beforeExit,SIGTERM,SIGINT) the SDK drains the buffer one last time.
Failure modes
- Lens unreachable / 5xx: retried up to
maxRetrieswith exponential backoff, then dropped. YouronErrorcallback is invoked. The host app continues. - API key revoked / 4xx: dropped immediately (retry won't help). Your
onErrorcallback is invoked. - Process killed with SIGKILL or
process.exit(0)synchronously: events still in the buffer are lost. Useawait log.flush()before exiting if you can't tolerate that.
License
UNLICENSED. © Kyrontha. Reach out for commercial use.
