Destino Dev

Blog

Interceptor global de logs e tempo no NestJS: implementar e testar

Por John Pires · 19 de março de 2026

Introducao ao problema

O que é um interceptor no NestJS

Interceptors no NestJS são uma camada entre a entrada da requisição e o processamento do handler. Eles permitem inspecionar, transformar e agir sobre a requisição e a resposta. Pense no interceptor como um middleware com superpoderes: ele enxerga o contexto do Nest (ExecutionContext) e o fluxo reativo (Observable) que devolve a resposta.

Para quem está começando, imagine uma esteira em uma fábrica: o request chega, passa por sensores (interceptors), que anotam tempo, status e fazem ajustes antes que o produto saia. O NestJS oferece essa capacidade com uma API tipada e integrada ao ciclo do framework.

Por que logs e medição de tempo importam

Logar e medir tempo de execução é essencial para depuração, monitoramento e SLAs. Um interceptor global garante que esse comportamento seja aplicado de forma consistente em todos os controllers e providers HTTP, sem precisar repetir código em cada rota.

Sem um interceptor global você vai acabar espalhando console.log por todo lugar ou duplicando código. Um interceptor centraliza a lógica, melhora a consistência dos logs e facilita testes.

Pre-requisitos e setup

Ambiente mínimo

Antes de começar tenha instalado: Node.js (>=14), npm ou yarn, e o Nest CLI (opcional, mas útil). Conhecimento básico de TypeScript e do modelo de controllers/providers do NestJS ajuda a absorver este conteúdo.

Vamos trabalhar com o template padrão do Nest. Não precisamos de bibliotecas externas para o interceptor básico — usaremos apenas @nestjs/common, rxjs e jest (já presentes no scaffold).

Instalação e estrutura do projeto

Comandos para criar o projeto e instalar dependências. Este código cria uma base pronta para receber o interceptor. Execute no terminal.

npm i -g @nestjs/cli
nest new logging-app
cd logging-app
# se preferir yarn
# yarn global add @nestjs/cli
# nest new logging-app --package-manager yarn

Depois de criar o projeto você terá uma estrutura padrão com src/app.module.ts, src/main.ts e um controller AppController. Iremos criar uma pasta common/interceptors para organizar o interceptor de logs e tempo.

Tutorial guiado passo a passo

Criando o interceptor

Crie o arquivo src/common/interceptors/logging.interceptor.ts. O interceptor abaixo:

  • Registra o início da requisição (método e URL).
  • Calcula o tempo gasto quando a resposta é emitida.
  • Usa o Logger do Nest para manter formato consistente.
// src/common/interceptors/logging.interceptor.ts
import { Injectable, NestInterceptor, ExecutionContext, CallHandler, Logger } from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';

@Injectable()
export class LoggingInterceptor implements NestInterceptor {
  private readonly logger = new Logger('HTTP');

  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const req = context.switchToHttp().getRequest();
    const method = req.method;
    const url = req.url;
    const handler = context.getHandler().name || 'anonymous';
    const start = Date.now();

    this.logger.log(`Incoming ${method} ${url} -> ${handler}`);

    return next.handle().pipe(
      tap(() => {
        const ms = Date.now() - start;
        this.logger.log(`Handled ${method} ${url} -> ${handler} - ${ms}ms`);
      }),
    );
  }
}

O que esse código faz: ele extrai o request do ExecutionContext, pega método e URL, registra uma primeira linha e depois usa rxjs.tap para calcular o tempo quando a stream do handler emitir o resultado.

O que acontece depois: toda vez que uma rota for chamada, o Logger escreverá duas linhas no console - entrada e saída com a latência.

Registrando globalmente

Para aplicar o interceptor em todas as rotas, registre-o globalmente no bootstrap do app (src/main.ts). Isso evita decorar cada controller manualmente.

// src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { LoggingInterceptor } from './common/interceptors/logging.interceptor';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalInterceptors(new LoggingInterceptor());
  await app.listen(3000);
}
bootstrap();

O que esse trecho faz: cria a aplicação e adiciona o interceptor como global. Depois disso todas as requisições HTTP passam pelo interceptor.

O que acontece depois: você terá logs padronizados para cada requisição sem mudar controllers.

Testando o interceptor (unit)

Para garantir que o interceptor não quebre o fluxo, escreva testes unitários que simulem o ExecutionContext e um CallHandler. O exemplo abaixo usa jest e rxjs.of para devolver um observable síncrono.

// src/common/interceptors/logging.interceptor.spec.ts
import { of } from 'rxjs';
import { LoggingInterceptor } from './logging.interceptor';

describe('LoggingInterceptor', () => {
  it('should pass the value through and not throw', (done) => {
    const interceptor = new LoggingInterceptor();

    const mockContext: any = {
      switchToHttp: () => ({ getRequest: () => ({ method: 'GET', url: '/test' }) }),
      getHandler: () => ({ name: 'testHandler' }),
    };

    const next: any = { handle: () => of('ok') };

    interceptor.intercept(mockContext as any, next as any).subscribe((res) => {
      expect(res).toBe('ok');
      done();
    });
  });
});

O que esse teste faz: cria mocks mínimos, invoca o interceptor e confirma que o valor retornado pelo handler chega ao assinante. Não verificamos o Logger explicitamente (poderíamos espiar Logger se necessário).

O que acontece depois: valida que o interceptor não consome a resposta ou causa erro no pipeline.

Testando o interceptor (e2e)

Teste e2e valida integração: interceptor + controller + servidor. Exemplo usa supertest (já presente nos templates do Nest).

// test/app.e2e-spec.ts
import { Test } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import * as request from 'supertest';
import { AppModule } from './../src/app.module';

describe('AppController (e2e)', () => {
  let app: INestApplication;

  beforeAll(async () => {
    const moduleRef = await Test.createTestingModule({ imports: [AppModule] }).compile();
    app = moduleRef.createNestApplication();
    await app.init();
  });

  it('/ (GET)', async () => {
    const res = await request(app.getHttpServer()).get('/');
    expect(res.status).toBe(200);
    // aqui o interceptor já rodou e registrou logs no console
  });

  afterAll(async () => await app.close());
});

O que esse teste faz: levanta a aplicação inteira, executa uma rota e garante que a resposta está correta. O interceptor rodando globalmente não altera o corpo da resposta, apenas registra logs e tempo.

Erros comuns e armadilhas

Existem alguns problemas recorrentes ao implementar interceptors:

  • Colocar lógica pesada dentro do interceptor (ex.: chamadas bloqueantes) — isso impacta todas as rotas.
  • Esquecer de retornar next.handle() — quebra a cadeia e causa timeouts/pendências.
  • Registrar dados sensíveis (tokens, senhas) nos logs — cuidado com GDPR e segurança.
Se tipar seu ExecutionContext ou mock no teste, você evita falsos positivos e torna os testes mais confiáveis.

Outra armadilha: usar console.log no interceptor e não usar o Logger do Nest. O Logger acompanha níveis e integração com transportes de log em produção.

Resultado final funcionando

Exemplo de controller

Controller simples para demonstrar comportamento. Coloque em src/app.controller.ts ou crie um controller novo.

// src/app.controller.ts
import { Controller, Get } from '@nestjs/common';

@Controller()
export class AppController {
  @Get()
  getRoot() {
    return { status: 'ok' };
  }
}

O que esse controller faz: responde uma requisição GET / com um JSON simples. Quando chamado, nosso interceptor vai logar entrada e saída com o tempo gasto.

O que acontece depois: ao executar a rota você verá algo como:

  • HTTP: Incoming GET / -> getRoot
  • HTTP: Handled GET / -> getRoot - 3ms

Saída e análise

Com logs padronizados você pode coletar métricas, enviar para ELK/Datadog ou configurar alertas em latência alta. Um interceptor central torna fácil anexar correlacionamento (request id) e enriquecer logs.

Para produção, troque Logger padrão por um adapter (pino, bunyan) e encaminhe logs estruturados (JSON) para processamento. O interceptor continua o mesmo — apenas o logger muda.

Desafio para o leitor praticar

Tarefas

  1. Estenda o interceptor para incluir um requestId gerado por UUID e envie esse id no cabeçalho de resposta (X-Request-Id).
  2. Adicione uma opção para habilitar/desabilitar logs por rota usando um decorator customizado @NoLog().
  3. Troque o Logger do Nest por pino e faça logs em JSON com nível dinâmico (info/error) com base no status da resposta.

Esses exercícios cobrem enriquecimento de contexto, metadata por rota e integração com uma lib de logging mais performática.

Critérios de aceitação

Para considerar o desafio concluído, garanta:

  • Unit tests cobrindo o requestId e o decorator @NoLog.
  • E2E demonstrando que o header X-Request-Id chega na resposta.
  • Logs em JSON com campos: timestamp, level, requestId, method, url, handler, durationMs.

Conclusao pratica

Resumo

Interceptors globais no NestJS oferecem um ponto único para implementar logs e medição de tempo. A solução apresentada é minimalista e segura para iniciantes: utiliza ExecutionContext, rxjs.tap e o Logger do framework para manter a aplicação observável sem invadir cada controller.

Os testes unitários e e2e demonstram como validar comportamento sem depender do console. Testes garantem que o interceptor não consome ou modifica indevidamente a resposta.

Proximos passos

Implemente requestId, regras de mute por rota e troque o Logger por uma solução estruturada em produção. Considere também integrar com métricas (Prometheus) expondo histogramas de latência a partir dos dados coletados no interceptor.

Reforce a prática: mantenha interceptors enxutos, teste o fluxo reativo e trate dados sensíveis com atenção. Esses passos transformam um simples log em dados úteis para operação e diagnóstico.

Dica final: comece pequeno: um interceptor que apenas mede tempo já traz ganho significativo. Depois, evolua para logs estruturados e tracing distribuído.

Assuntos: NestJS, interceptors, Testes, logs

Leia também