@devms/livetail
v0.1.0
Published
Live Tail NestJS module for the Monitoring platform. Real-time WebSocket log streaming and raw stdout/stderr console capture over Socket.IO.
Maintainers
Readme
@devms/livetail
Live Tail module for the Monitoring platform. A NestJS WebSocket gateway
that broadcasts structured logs to connected clients via Socket.IO — like
tail -f for your application logs — plus optional console capture that
streams the raw stdout/stderr of your process, like docker logs -f.
Install
pnpm add @devms/livetail @nestjs/websockets @nestjs/platform-socket.ioQuick Start
// app.module.ts
import { LiveTailModule } from '@devms/livetail';
@Module({
imports: [
LiveTailModule.register({
captureConsole: {
token: process.env.LIVETAIL_CONSOLE_TOKEN, // recommended in prod
},
}),
],
})
export class AppModule {}The module is @Global() — register it once and inject LiveTailService or
ConsoleCaptureService anywhere.
With ConfigService (async)
LiveTailModule.registerAsync({
inject: [ConfigService],
useFactory: (config: ConfigService) => ({
cors: config.get('LIVETAIL_CORS_ORIGIN', '*'),
maxClients: parseInt(config.get('LIVETAIL_MAX_CLIENTS', '0')),
disabled: config.get('LIVETAIL_DISABLED') === 'true',
captureConsole: {
bufferSize: parseInt(config.get('LIVETAIL_CONSOLE_BUFFER', '2000')),
token: config.get('LIVETAIL_CONSOLE_TOKEN'),
},
}),
});Configuration
| Option | Type | Default | Description |
| ---------------- | ---------------------------- | -------------- | --------------------------------------------------------- |
| namespace | string | '/live-tail' | WebSocket namespace path |
| cors | string \| string[] \| bool | '*' | CORS origin for WebSocket connections |
| maxClients | number | 0 | Max simultaneous clients (0 = unlimited) |
| pingInterval | number | 25000 | Socket.IO ping interval in ms |
| pingTimeout | number | 20000 | Socket.IO ping timeout in ms |
| disabled | boolean | false | Disable the gateway (service still injectable but no-ops) |
| captureConsole | boolean \| object | false | Stream raw stdout/stderr — see below |
Broadcast logs from your service
import { LiveTailService, LiveTailEnvContext } from '@devms/livetail';
@Injectable()
export class AppLogService {
constructor(private readonly liveTail: LiveTailService) {}
async ingestLogs(envId: string, logs: LogDto[], ctx: LiveTailEnvContext) {
await this.db.appLog.createMany({ data: logs });
// Broadcast to live tail clients, enriched with org/app/env names
this.liveTail.broadcastMany(
logs.map((log) => ({
environmentId: envId,
level: log.level,
category: log.category,
action: log.action,
message: log.message,
createdAt: new Date().toISOString(),
})),
ctx, // optional LiveTailEnvContext — adds orgName, appName, envName
);
}
}WebSocket protocol
Clients connect to ws://<host>:<port>/live-tail (Socket.IO).
Client → server
| Event | Payload | Description |
| ------------------ | ---------------- | ------------------------------------------------- |
| subscribe | LiveTailFilter | Start receiving logs matching the given filter |
| updateFilter | LiveTailFilter | Update filters without reconnecting |
| pause / resume | — | Pause / resume the stream while staying connected |
Server → client
| Event | Payload | Description |
| --------------- | ----------------------------------------- | ------------------------------------------- |
| connected | { clientId, message, connectedClients } | Connection confirmed |
| subscribed | { filter } | Subscription confirmed |
| filterUpdated | { filter } | Filter update confirmed |
| log | LiveTailLogEvent | A log entry matching your filter |
| error | { message } | Connection error (e.g. max clients reached) |
All LiveTailFilter fields are optional: environmentId, appId, orgId,
level (single or array), category (exact), action (contains),
userId, search (full-text in message/action/category), tags.
import { io } from 'socket.io-client';
const socket = io('http://localhost:5002/live-tail');
socket.on('connected', () => {
socket.emit('subscribe', {
environmentId: 'env-abc',
level: ['error', 'fatal'],
});
});
socket.on('log', (log) =>
console.log(`[${log.level}] ${log.category}/${log.action}`),
);Console Capture (raw stdout/stderr streaming)
Stream the raw terminal output of your application — everything the
process writes to stdout/stderr (NestJS Logger, console.log, crash stack
traces, third-party output, ANSI colors included) — like docker logs -f,
but over the same /live-tail socket. Nothing is persisted; lines are held
in an in-memory ring buffer and replayed to clients on subscribe.
LiveTailModule.register({
captureConsole: {
bufferSize: 2000, // lines kept in memory & replayed on subscribe
maxLineLength: 8192, // longer lines are truncated
batchInterval: 150, // ms between batched pushes to clients
token: process.env.LIVETAIL_CONSOLE_TOKEN, // require a shared secret
stripAnsi: false, // keep colors (dashboard renders them)
},
});Pass captureConsole: true for the defaults. No logger changes are needed —
the module tees process.stdout / process.stderr; your terminal/PM2/Docker
output is untouched.
Client protocol
const socket = io('http://localhost:5002/live-tail');
// Subscribe (replays the buffer, then streams live)
socket.emit('subscribe-console', { token: '...', tail: 1000 });
socket.on('console-subscribed', ({ pid, bufferSize, historyCount }) => {});
socket.on('console-history', ({ lines, done }) => {}); // chunked replay
socket.on('console-lines', ({ lines, dropped }) => {}); // live batches
socket.on('console-error', ({ code, message }) => {}); // disabled / bad token
socket.emit('unsubscribe-console');Each line: { seq: number, stream: 'stdout' | 'stderr', line: string, ts: string }.
Performance & safety
- Zero subscribers → near-zero cost. Lines only go to the ring buffer; nothing is broadcast.
- Batched delivery. Live lines are flushed every
batchIntervalms as a single frame, and the pending queue is hard-capped (oldest dropped, with adroppedcount reported) so a slow consumer can never grow memory. - Crash-safe tee. The original write always executes first; any error in capture is swallowed and can never break the app's own output.
- Security. Raw console output can contain secrets (connection strings,
error dumps). Set
tokenin production and restrictcors.
Using with @devms/applog-client
Live Tail was previously bundled inside @devms/applog-client and
auto-registered by AppLogModule via the livetail config key. It is now a
standalone package — register it alongside the applog module:
import { AppLogModule } from '@devms/applog-client';
import { LiveTailModule } from '@devms/livetail';
@Module({
imports: [
AppLogModule.register({
/* ...credentials */
}),
LiveTailModule.register({
captureConsole: { token: process.env.LIVETAIL_CONSOLE_TOKEN },
}),
],
})
export class AppModule {}License
MIT
