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
- Estenda o interceptor para incluir um requestId gerado por UUID e envie esse id no cabeçalho de resposta (X-Request-Id).
- Adicione uma opção para habilitar/desabilitar logs por rota usando um decorator customizado @NoLog().
- 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
Limitador de requisições no NestJS com Throttler + Redis: passo a passo para iniciantes
Guia prático para iniciantes: configure rate limiting no NestJS usando @nestjs/throttler e Redis. Passo a passo com exemplos funcionais, Docker, dicas de…
NestJS: criar e testar microservice TCP e gateway HTTP passo a passo
Passo a passo prático para criar e testar um microservice TCP em NestJS e um gateway HTTP. Mostro o jeito errado, o jeito certo, exemplos funcionais e como…
Propagando contexto de requisição e trace IDs entre microservices com NestJS
Guia prático para propagar trace IDs em NestJS: middleware, AsyncLocalStorage, interceptors e wrappers de ClientProxy. Padrões, armadilhas e exemplos reais…
Testando Guards e Interceptors no NestJS com Jest e SuperTest — do jeito errado ao certo
Guards controlam acesso e Interceptors transformam respostas. Aprenda a testar ambos no NestJS: mostro o jeito errado, corrijo e explico testes unitários e…
Rate limiting por usuário no NestJS com Guard + Redis (sliding window)
Aprenda a implementar rate limiting por usuário no NestJS usando Guard + Redis com sliding window. Guia passo a passo, exemplos práticos (Lua + ioredis)…
Autorização por permissões no NestJS usando decorators e guards
Aprenda a controlar autorização por permissões no NestJS usando decorators e guards. Tutorial prático, passo a passo, com exemplos funcionais, integração JWT…