@inso_web/els-nest
v0.5.2
Published
NestJS module and LoggerService for @inso_web/els-client. A drop-in replacement for the built-in NestJS Logger, nestjs-pino and pino-nestjs. Supports app.useLogger() and DI injection.
Maintainers
Readme
@inso_web/els-nest
NestJS module and LoggerService for the Inso Error Logs Service (ELS) — a managed SaaS for centralised event logging (debug → fatal) with AI-assisted error triage. Drop-in replacement for the built-in Logger, nestjs-pino, and @sentry/nestjs. Supports app.useLogger(), DI injection, and request-scoped logging via ClsModule.
Table of contents
- What you get
- Install
- Quick Start
- When to use what
- Core concepts
- Configuration
- Migration
- Versioning
- Quick reference
- Why ELS
- API
- FAQ
- Other ELS SDKs
- Pricing
- License
What you get
ELS ships with a built-in admin dashboard. Every event captured by this SDK lands there with full-text search, faceted filtering, AI-assisted diagnosis, and version-aware regression detection.
| | |
|---|---|
|
|
|
| Virtual table with facet sidebar (app, env, version, source, level, browser, IP, category). Live mode auto-refreshes every 5s. | Full event metadata: timestamps, geo, env, app version, fingerprint, session, repetition cards, in-session correlation. |
|
|
|
| Parsed stack trace + AI-assisted diagnosis: what broke, where, how to fix. | Timeline, donuts, top URLs/IPs, hourly heatmap, version-regression widget. |
Install
npm install @inso_web/els-client @inso_web/els-nest@inso_web/els-client is a peer dependency.
Requirements: NestJS 9+, Node.js 18+.
Quick Start
1. Register the module
app.module.ts:
import { Module } from '@nestjs/common';
import { ELSModule } from '@inso_web/els-nest';
@Module({
imports: [
ELSModule.forRoot({
apiKey: process.env.ELS_API_KEY!,
appSlug: 'my-nest-app',
serviceName: 'api',
deploymentEnv: process.env.NODE_ENV === 'production' ? 'PRODUCTION' : 'DEV',
appVersion: process.env.BUILD_VERSION,
minLevel: 'info',
}),
],
})
export class AppModule {}Don't have an API key yet? Sign up at lk.insoweb.ru — takes under a minute.
2. Hook it into app.useLogger()
main.ts:
import { NestFactory } from '@nestjs/core';
import { ELSLoggerService } from '@inso_web/els-nest';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule, { bufferLogs: true });
app.useLogger(app.get(ELSLoggerService));
await app.listen(3000);
}
bootstrap();Now every built-in Logger.log() / error() / warn() call in Nest goes to ELS.
3. Inject through DI
import { Injectable } from '@nestjs/common';
import { ELSLoggerService } from '@inso_web/els-nest';
@Injectable()
export class UserService {
constructor(private readonly logger: ELSLoggerService) {}
async findById(id: string) {
this.logger.log(`Fetching user ${id}`);
// ...
}
}4. Request-scoped with CLS
Install nestjs-cls to propagate requestId automatically:
import { ClsModule } from 'nestjs-cls';
@Module({
imports: [
ClsModule.forRoot({ middleware: { mount: true, generateId: true } }),
ELSModule.forRoot({ /* ... */ }),
],
})
export class AppModule {}5. Async config (for ConfigService)
ELSModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (config: ConfigService) => ({
apiKey: config.getOrThrow('ELS_API_KEY'),
appSlug: 'my-nest-app',
deploymentEnv: config.get('NODE_ENV') === 'production' ? 'PRODUCTION' : 'DEV',
}),
});When to use what
| Scenario | Use |
|---|---|
| Replace Nest's default logger globally | app.useLogger(app.get(ELSLoggerService)) |
| Log inside services / controllers | constructor(private readonly logger: ELSLoggerService) |
| Static config at boot | ELSModule.forRoot({ ... }) |
| Need ConfigService / async deps | ELSModule.forRootAsync({ ... }) |
| HTTP middleware-style request tagging | ClsModule + ELSModule together |
| Capture exceptions from filters / interceptors | Inject ELSLoggerService, call .error(err.stack, context) |
Core concepts
LoggerService shape
ELSLoggerService implements Nest's LoggerService contract — drop it into app.useLogger() without wrappers. Mapping:
| Nest method | ELS level |
|---|---|
| log | info |
| error | error |
| warn | warning |
| debug | debug |
| verbose | debug |
| fatal (Nest 10+) | critical |
Context tracking
setContext('UsersController') attaches a string label to subsequent calls — visible in the dashboard as a meta.context field. Bonus: child controllers automatically get their class name as the context when Nest creates the logger.
Async transport
Calls are fire-and-forget. The HTTP transport batches in the background. app.close() triggers a flush.
Configuration
ELSConfig matches the base client — see @inso_web/els-client. Key fields:
| Option | Description |
|---|---|
| apiKey | API key (required) |
| appSlug | App slug (required) |
| serviceName | Service / module name |
| deploymentEnv | DEV / STAGING / PRODUCTION |
| appVersion | Version (≤128 chars) |
| minLevel | Minimum level to send |
Migration
From built-in Nest Logger
The default Logger writes to stdout. ELS writes to a queryable dashboard. API is compatible — switching is a one-liner in main.ts.
Before:
// main.ts
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// default Logger prints to console
await app.listen(3000);
}// some.service.ts
import { Logger } from '@nestjs/common';
@Injectable()
export class SomeService {
private readonly logger = new Logger(SomeService.name);
doWork() {
this.logger.log('working');
this.logger.error('boom', err.stack);
}
}After:
// main.ts
async function bootstrap() {
const app = await NestFactory.create(AppModule, { bufferLogs: true });
app.useLogger(app.get(ELSLoggerService));
await app.listen(3000);
}// some.service.ts — no changes required
import { Logger } from '@nestjs/common';
@Injectable()
export class SomeService {
private readonly logger = new Logger(SomeService.name);
// identical API, now ships to ELS
}| Nest built-in | ELS | Notes |
|---|---|---|
| new Logger(context) | ELSLoggerService (via DI) | Or keep new Logger() — useLogger reroutes it |
| logger.log() | logger.log() | → info |
| logger.error(msg, trace) | logger.error(msg, trace) | Stack lands in meta.stack |
| Color-coded stdout | Plain stdout + remote events | Keep Console transport for local dev |
Gotchas:
- Pre-bootstrap calls (before
app.useLogger(...)) buffer whenbufferLogs: true. Without buffering they go to stdout-only and won't reach ELS. Logger.overrideLogger(false)disables the default — useful in tests.
From nestjs-pino
Before:
import { LoggerModule } from 'nestjs-pino';
@Module({
imports: [
LoggerModule.forRoot({
pinoHttp: {
autoLogging: true,
genReqId: () => crypto.randomUUID(),
},
}),
],
})
export class AppModule {}// service
import { PinoLogger } from 'nestjs-pino';
@Injectable()
export class SomeService {
constructor(private readonly logger: PinoLogger) {}
doWork() { this.logger.info({ userId: 42 }, 'fetched'); }
}After:
@Module({
imports: [
ELSModule.forRoot({
apiKey: process.env.ELS_API_KEY!,
appSlug: 'my-nest-app',
minLevel: 'info',
}),
],
})
export class AppModule {}import { ELSLoggerService } from '@inso_web/els-nest';
@Injectable()
export class SomeService {
constructor(private readonly logger: ELSLoggerService) {}
doWork() { this.logger.log('fetched', 'SomeService'); }
}| nestjs-pino | ELS | Notes |
|---|---|---|
| LoggerModule.forRoot({ pinoHttp }) | ELSModule.forRoot({ ... }) | Same role |
| PinoLogger (DI) | ELSLoggerService (DI) | Different shape — see method map below |
| logger.info(obj, msg) | logger.log(msg, context) | Pass structured meta via child |
| pinoHttp.autoLogging | Add a request middleware with ClsModule + ELSLoggerService | No built-in request log row |
| Bindings (logger.setBindings) | child({ ...bindings }) on the underlying client | |
Gotchas:
- Nest's
LoggerServicecontract uses(message, context?)— pino's(obj, message)does not match. Re-order arguments at call sites. - nestjs-pino's HTTP auto-logging is opt-in here — register a small middleware via
ClsModuleif you need per-request rows.
From @sentry/nestjs
Before:
import { SentryModule } from '@sentry/nestjs';
@Module({
imports: [
SentryModule.forRoot({
dsn: 'https://[email protected]/1',
environment: 'production',
release: process.env.BUILD_VERSION,
}),
],
})
export class AppModule {}// in a filter
import { SentryService } from '@sentry/nestjs';
@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
constructor(private readonly sentry: SentryService) {}
catch(exception: unknown, host: ArgumentsHost) {
this.sentry.instance().captureException(exception);
}
}After:
@Module({
imports: [
ELSModule.forRoot({
apiKey: process.env.ELS_API_KEY!,
appSlug: 'my-nest-app',
deploymentEnv: 'PRODUCTION',
appVersion: process.env.BUILD_VERSION,
}),
],
})
export class AppModule {}import { ELSLoggerService } from '@inso_web/els-nest';
@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
constructor(private readonly logger: ELSLoggerService) {}
catch(exception: unknown, host: ArgumentsHost) {
const err = exception instanceof Error ? exception : new Error(String(exception));
this.logger.error(err.message, err.stack, 'AllExceptionsFilter');
}
}| Sentry | ELS | Notes |
|---|---|---|
| SentryModule.forRoot({ dsn }) | ELSModule.forRoot({ apiKey, appSlug }) | Three explicit fields instead of DSN |
| sentry.captureException(err) | logger.error(err.message, err.stack) | Stack lands in meta.stack |
| release | appVersion | Any string ≤128 chars |
| environment | deploymentEnv | Fixed enum |
| Performance / tracing | Not provided | Keep Sentry alongside if needed |
| Source maps upload | Not provided | Same — pair with another tool if critical |
Gotchas:
- ELS doesn't trace transactions or spans. Sentry Performance integrations stay — they don't conflict.
- Sentry's automatic scope tags need to be replicated via
ClsModuleorchildbindings.
Versioning
Pass BUILD_VERSION through Dockerfile / CI. ELS accepts any format ≤128 chars: semver, CalVer, date-compact, git SHA, opaque.
ARG BUILD_VERSION=dev
ENV BUILD_VERSION=$BUILD_VERSIONELSModule.forRoot({ ..., appVersion: process.env.BUILD_VERSION });In the dashboard you get a "Regressions" widget: "this error first seen in v20260507120000, not present in v20260506180000."
Quick reference
| Need | Use |
|---|---|
| Global Nest logger | app.useLogger(app.get(ELSLoggerService)) |
| Logger via DI | constructor(private readonly logger: ELSLoggerService) |
| Async config from ConfigService | ELSModule.forRootAsync({ ... }) |
| Request-scoped IDs | ClsModule.forRoot(...) + ELSModule.forRoot(...) |
| Capture in a filter | Inject ELSLoggerService, call .error(msg, stack, ctx) |
| Per-controller context | Default — Nest passes the class name automatically |
| Suppress noisy levels | minLevel: 'warn' |
| Buffer logs before bootstrap | NestFactory.create(AppModule, { bufferLogs: true }) |
Why ELS
ELS for Node.js is a focused logging SaaS, not a full observability suite. It optimises for capture speed, AI-driven triage, and a low integration cost.
- Lower weight. No
pinopeer, no transport packages. - Zero external API calls. Only
POST /errors[/batch]andGET /health. - AI-assisted diagnosis on every stack trace.
- 5-minute integration. Register the module, hand it to
useLogger, done. - Predictable price. Tariffs in the dashboard.
Detailed comparison
| Category | ELS | Sentry | Datadog / New Relic | Grafana Loki | LogRocket / Logtail / BetterStack | |---|---|---|---|---|---| | Hosting model | Managed SaaS | SaaS or self-hosted | SaaS only | Self-hosted / Grafana Cloud | SaaS | | SDK runtime deps | Zero | Medium (sub-SDKs, integrations) | Heavy (agent + tracing) | Promtail / agent | Medium | | Typical integration time | ~5 min | 10–20 min | 30–60 min | Hours to days | 10–20 min | | AI-assisted triage | Built-in | Paid add-on | Paid add-on | None | None | | Error grouping / fingerprint | Yes | Yes | Yes | Manual via LogQL | Partial | | Source-map upload | No | Yes | Yes | n/a | Partial | | Session replay (frontend) | No | Paid | Paid | n/a | Yes (core) | | Distributed tracing / APM | No | Partial | Yes (core) | Yes with Tempo | No | | Infrastructure metrics | No | No | Yes (core) | Yes with Mimir | No | | Free tier log retention | 24 hours | 30 days (limited volume) | Trial only | Self-cost | 3–30 days | | Russian-language support / docs | Native | Community | Limited | Community | None |
When ELS is the wrong choice
- You need a single vendor for APM + logs + metrics under one bill — go Datadog or New Relic.
- Your frontend bug triage relies on DOM session replay — go LogRocket or Sentry Replay.
- You ship a public mobile app and need crash symbolication + ANR detection — Firebase Crashlytics or Sentry Mobile.
For everything else — backend errors, frontend JS errors, request logs, structured app events with version-aware analytics — ELS is built to be the cheapest path to a working dashboard.
→ Sign up at lk.insoweb.ru to grab an API key.
API
class ELSModule {
static forRoot(config: ELSConfig): DynamicModule;
static forRootAsync(opts: ELSAsyncOptions): DynamicModule;
}
class ELSLoggerService implements LoggerService {
log(message: any, context?: string): void;
error(message: any, trace?: string, context?: string): void;
warn(message: any, context?: string): void;
debug(message: any, context?: string): void;
verbose(message: any, context?: string): void;
setContext(context: string): void;
}ELSConfig matches the base client — see @inso_web/els-client.
FAQ
Works with GraphQL / Microservices? Yes. ELSLoggerService is a Nest LoggerService — attach to any application context.
Where do request IDs come from? Pair with nestjs-cls and call cls.getId(); or set context on each call manually.
Can I have multiple ELS apps in one process? Yes — register two ELSModule instances with different appSlug and inject distinct ELSLoggerService providers.
Other ELS SDKs
Same wire format, same dashboard — pick by stack.
Node.js family
@inso_web/els-client— base TS / Node / browser client@inso_web/els-express— Express middleware@inso_web/els-next— Next.js helpers (App + Pages router)@inso_web/els-nest— NestJS module (this repo)@inso_web/els-react— React Provider, hooks, ErrorBoundary@inso_web/els-vue— Vue 3 plugin
Other stacks
Inso.Els— .NET (Core + ASP.NET Core + ILogger)io.github.official-inso:els-core— Java + Spring Boot starter + SLF4Jgithub.com/official-inso/els-go— Go
Pricing
Free tier — 24-hour log retention. See lk.insoweb.ru for the full tariff matrix.
License
MIT © INSOWEB
