Skip to content

Best NestJS Logger: nestjs-pino vs nest-winston vs the built-in Logger vs logixia ​

TL;DR: Start with Nest's built-in ConsoleLogger for small apps (it can emit JSON with json: true). For production services, nestjs-pino is the default community choice: pino speed, pino-http request logs and the largest user base. nest-winston fits teams already invested in winston transports and formats. logixia is an integrated alternative with a request trace ID, redaction, an exception filter and many transports in one package, but it is young and small. I maintain logixia, so read that part with that in mind; I have only claimed behavior I checked in the code, and I say where logixia does not do something.

What does Nest's built-in Logger give you? ​

Nest ships a ConsoleLogger, and Logger is its injectable facade. You can pass logger: ['error', 'warn'] to limit levels, set json: true for JSON output, set timestamp, and replace it with your own LoggerService. For structured output and simple setups this may be all you need.

What it does not do is documented by Nest itself: for transports and redaction the docs point you to libraries such as pino or winston. There is no request-scoped correlation ID, no field redaction and no way to ship logs to a destination.

Choose it when: the service is small, stdout is collected by your platform, and you do not need correlation IDs.

Why is nestjs-pino the default choice? ​

nestjs-pino wraps pino and pino-http. You get one request-completion log per request, a request-scoped child logger, and pino's redact, pino-pretty for development and the whole pino transport ecosystem. It has about 4.3M weekly downloads (npm, 2026-11-14), which means most NestJS problems have already been reported and answered.

Strengths:

  • pino's low per-call overhead and JSON output;
  • pino-http request logging with customizable serializers and redact;
  • AsyncLocalStorage-based PinoLogger injection;
  • a very large community.

What are the known pain points? ​

None of these make it a bad choice, but they are worth knowing, and each is linked so you can judge it yourself:

  • Major-version lag. Fixes were needed for pino 10 and for pino-http 11. With a wrapper around two libraries you depend on three release schedules.
  • Context outside HTTP. Request context is created by the HTTP middleware, so logs from queues, cron jobs and microservice handlers do not get a request-scoped context automatically. The long-running thread is #803 (38 reactions at the time of writing); the usual workaround is to set up context around those handlers yourself.
  • Fastify adapter context. There is an open report about request context with FastifyAdapter.

If you hit these, you can usually work around them, which is part of why it stays the default.

Choose it when: you want pino's performance and ecosystem and are fine assembling correlation outside HTTP.

What about nest-winston? ​

nest-winston wires a winston logger into Nest as the app logger and an injectable service. You inherit winston's flexibility (the format pipeline, a huge list of community transports) and its costs (more configuration, lower throughput than pino in most benchmarks, see our benchmark page). It does not create a per-request context for you; you add that with your own middleware.

Choose it when: you already run winston transports in other services, or you need one that only exists for winston.

What does logixia's NestJS module provide? ​

LogixiaLoggerModule from logixia/nest is a global module. The details below come from the NestJS guide and the source.

ts
import { Module } from '@nestjs/common';
import { LogixiaLoggerModule } from 'logixia/nest';

@Module({
  imports: [
    LogixiaLoggerModule.forRoot({
      appName: 'orders-api',
      environment: 'production',
      traceId: true,
      redact: { autoDetect: true },
      transports: { console: { format: 'json', colorize: false } },
    }),
  ],
})
export class AppModule {}

What you get:

  • LogixiaLoggerService, injectable and implementing Nest's LoggerService, so app.useLogger(app.get(LogixiaLoggerService)) routes Nest's own logs through it.
  • A trace middleware applied to routes that reads an incoming traceparent, x-request-id and similar headers (validated), or generates an ID, echoes it as X-Trace-Id and runs the request in an async scope. Every log line inside the request carries the ID without passing a logger around.
  • LogixiaExceptionFilter, which returns a consistent JSON error body and logs with the trace ID, with sensitive query-string values redacted in logged URLs.
  • @LogMethod() to log entry, exit, duration and errors of a method (arguments are not logged unless you opt in).
  • KafkaTraceInterceptor and WebSocketTraceInterceptor to continue a trace in message handlers.
  • Redaction, sampling and the transport set from core logixia: redaction, sampling, transports.

Does logixia handle context in queues and cron jobs? ​

Partly, and not automatically. Here is exactly what the code does:

  • The trace middleware covers HTTP routes only.
  • The Kafka and WebSocket interceptors continue an ID that is already in the message (body or headers). They do not generate one: with no ID found, the handler runs untraced. They need traceId set in forRoot(); otherwise they are created disabled.
  • For cron jobs and other queue consumers (Bull, SQS and similar), there is no interceptor. You wrap the handler yourself with runWithTraceId() or LogixiaContext.run() (see tracing); the logixia/correlation helpers cover extracting an ID from a message.
  • Outside any scope, each entry still gets a traceId, but it is a UUID generated once per logger instance, not one per job.

So the gap nestjs-pino users report for non-HTTP contexts exists in a different shape here: logixia gives you primitives and two interceptors rather than a fully automatic answer. I have not tested logixia with Bull or @nestjs/schedule in this article, only checked what the code provides.

ts
import { Injectable } from '@nestjs/common';
import { generateTraceId, runWithTraceId } from 'logixia';
import { LogixiaLoggerService } from 'logixia/nest';

@Injectable()
export class NightlyJob {
  constructor(private readonly logger: LogixiaLoggerService) {}

  async run(): Promise<void> {
    await runWithTraceId(generateTraceId(), async () => {
      await this.logger.info('nightly job started');
    });
  }
}

What are logixia's drawbacks on NestJS? ​

  • Young project, tiny community. About a hundred weekly downloads. You will find fewer blog posts, answers and integrations than for nestjs-pino.
  • Slower on simple log calls than pino, though it is faster on some structured payloads in our benchmark.
  • Methods return promises, unlike Nest's Logger. Await them in handlers, or accept that you are not waiting for transports.
  • @InjectLogger() needs a feature import. forRoot() alone does not register its token; inject LogixiaLoggerService by class, or add LogixiaLoggerModule.forFeature(...). See the guide.

Choose it when: you want tracing, redaction and several destinations in one package and can accept the maturity trade-off. Coming from nestjs-pino? See the migration guide.

Which NestJS logger should you choose? ​

SituationPick
Small service, platform collects stdoutBuilt-in ConsoleLogger
Production API, want the community default and pino speednestjs-pino
Already standardized on winston transportsnest-winston
Want trace IDs, redaction and transports with one dependencylogixia
Heavy non-HTTP work (queues, cron) and a zero-config answerNone is fully automatic; budget for wrapping handlers

For the tracing setup in detail, see NestJS logging with request tracing. For the wider field, see the best Node.js logging libraries.

Key takeaways ​

  • The built-in logger has json: true but defers transports and redaction to pino or winston.
  • nestjs-pino is the default; know its pino 10 and pino-http 11 upgrade issues and its non-HTTP context gap.
  • nest-winston is for winston loyalists.
  • logixia bundles tracing, redaction and transports; its non-HTTP context support is primitives plus Kafka and WebSocket interceptors, not automatic.

FAQ ​

What is the best logger for NestJS? For most production apps nestjs-pino, because of pino's speed and its large community. Use the built-in logger for small apps, and consider logixia if you want built-in tracing, redaction and transports and accept a young project.

Does the built-in NestJS logger support JSON? Yes, ConsoleLogger accepts json: true. For transports and redaction, Nest's docs point to pino or winston.

Does nestjs-pino support pino 10? Fixes were needed for it (see the issue); check the nestjs-pino release notes against your pino version before upgrading.

How do I add a request ID to NestJS logs? With nestjs-pino it comes from pino-http's request ID; with logixia, register LogixiaLoggerModule.forRoot({ traceId: true }). See request tracing.

Does logixia propagate context to cron jobs and queues? Not automatically. You wrap handlers in runWithTraceId() or LogixiaContext.run(); the Kafka and WebSocket interceptors continue an ID already present in the message.

Can I use logixia as the NestJS app logger? Yes: app.useLogger(app.get(LogixiaLoggerService)).

Released under the MIT License.