@mobi4tech/nest-observability
v0.1.0
Published
Observability defaults for Mobi4Tech NestJS services
Maintainers
Readme
@mobi4tech/nest-observability
Defaults de observabilidade para aplicações NestJS: logs JSON com Pino, request ID, métricas Prometheus, liveness, readiness e eventos de negócio.
Compatibilidade
- Node.js 20 ou 22;
- NestJS 10 ou 11;
- adapter HTTP Express;
rxjs7.8 ou superior.
O pacote é estável. Fixe a versão durante a adoção para evitar upgrades acidentais.
Instalação
npm install @mobi4tech/[email protected]Com Yarn:
yarn add @mobi4tech/[email protected]Configuração mínima
Importe o módulo uma vez no módulo raiz:
import { Module } from '@nestjs/common';
import { ObservabilityModule } from '@mobi4tech/nest-observability';
@Module({
imports: [
ObservabilityModule.forRoot({
serviceName: process.env.SERVICE_NAME ?? 'billing-api',
environment: process.env.NODE_ENV ?? 'development',
version: process.env.APP_VERSION ?? 'unknown',
logLevel: process.env.LOG_LEVEL ?? 'info',
ignoredHttpPathSuffixes: ['/refresh', '/notificacoes'],
ignoredHttpPathPatterns: ['/portal-vendas/api/auth/*/silent-check'],
ignoredHttpStatusCodeClasses: [3],
}),
],
})
export class AppModule {}No bootstrap, habilite o logger antes de configurar o prefixo global:
import { NestFactory } from '@nestjs/core';
import { configureObservability } from '@mobi4tech/nest-observability';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule, { bufferLogs: true });
configureObservability(app);
app.setGlobalPrefix('nome-do-servico/api');
await app.listen(process.env.PORT ?? 3000);
}
void bootstrap();O pacote passa a expor os endpoints abaixo do prefixo global:
| Endpoint | Comportamento |
|---|---|
| GET /nome-do-servico/api/metrics | Métricas Prometheus do processo e das requisições HTTP |
| GET /nome-do-servico/api/health/live | HTTP 200 enquanto o processo estiver vivo |
| GET /nome-do-servico/api/health/ready | HTTP 200 ou 503 conforme as dependências registradas |
Essas rotas devem ficar disponíveis apenas na rede interna. Não encaminhe
/metrics ou /health/* pelo proxy público.
Configuração assíncrona
Use forRootAsync quando as opções vierem de outro provider:
import { ConfigModule, ConfigService } from '@nestjs/config';
ObservabilityModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (config: ConfigService) => ({
serviceName: config.getOrThrow<string>('SERVICE_NAME'),
environment: config.get<string>('NODE_ENV', 'development'),
version: config.get<string>('APP_VERSION', 'unknown'),
logLevel: config.get<string>('LOG_LEVEL', 'info'),
readinessTimeoutMs: 2_000,
}),
});Opções
| Opção | Obrigatória | Descrição |
|---|---:|---|
| serviceName | Sim | Nome estável do serviço, preferencialmente em kebab-case |
| environment | Sim | Ambiente, como development, staging ou production |
| version | Não | Versão/build exibido em logs e métricas |
| logLevel | Não | Nível do Pino; padrão info |
| redactPaths | Não | Caminhos adicionais que serão substituídos por [REDACTED] |
| ignoredHttpPathSuffixes | Não | Sufixos cujos logs HTTP automáticos serão omitidos; métricas continuam ativas |
| ignoredHttpPathPatterns | Não | Padrões glob (ex.: /auth/*/refresh) ou RegExp para omitir logs HTTP; métricas continuam ativas |
| ignoredHttpStatusCodeClasses | Não | Classes HTTP a omitir dos logs automáticos (ex.: [3] para redirects); métricas continuam ativas |
| readinessChecks | Não | Checks registrados durante a criação do módulo |
| readinessTimeoutMs | Não | Timeout individual dos checks; padrão 2 segundos |
Logs JSON e request ID
O pacote usa stdout. PM2 ou o runtime do container deve capturar e rotacionar os logs; a aplicação não deve gravar arquivos próprios.
Uma requisição com X-Request-ID válido preserva o valor. Quando o header está
ausente ou inválido, o pacote gera um UUID e devolve o identificador no header
da resposta. Authorization, cookies, senhas, tokens, CPF/CNPJ e e-mail são
redigidos por padrão.
Rotas muito frequentes e de baixo valor podem ser removidas apenas do log HTTP automático:
ignoredHttpPathSuffixes: ['/refresh', '/notificacoes'],
ignoredHttpPathPatterns: ['/portal-vendas/api/auth/*/silent-check'],
ignoredHttpStatusCodeClasses: [3],A correspondência de sufixos e padrões ignora a query string; nos padrões,
* representa qualquer trecho do caminho e também é possível passar um
RegExp. ignoredHttpStatusCodeClasses: [3] remove redirects (3xx) apenas dos
logs automáticos. As métricas HTTP continuam sendo coletadas. Não ignore login, alteração de senha
ou outra rota de segurança sem manter eventos explícitos de sucesso e falha.
Para logs com atributos explícitos, injete StructuredLogger:
import { Injectable } from '@nestjs/common';
import { StructuredLogger } from '@mobi4tech/nest-observability';
@Injectable()
export class ImportService {
constructor(private readonly logger: StructuredLogger) {}
completed(records: number) {
this.logger.info('import_completed', {
file_type: 'proposal',
records,
});
}
failed(error: unknown) {
this.logger.error(error, 'import_failed', {
integration: 'carrier',
});
}
}Não envie bodies, headers, DTOs, entidades ou mensagens de drivers inteiras
para o logger. O serializer de erros remove error.message porque ela pode
conter SQL, credenciais ou dados pessoais.
Cada log HTTP inclui req.path (o caminho real recebido) e req.route (a
rota estável, como /clientes/:id). O segundo campo é o elo entre os painéis
de métricas por rota e os logs no Grafana; não use o caminho real como rótulo
de métrica, pois isso criaria uma série para cada identificador.
Eventos de negócio
Eventos devem usar nomes minúsculos separados por ponto e atributos de baixa cardinalidade:
this.logger.businessEvent({
event: 'proposal.created',
outcome: 'success',
attributes: {
carrier: 'example',
proposal_type: 'individual',
},
});Também é possível registrar um evento de sucesso de forma abreviada:
this.logger.businessEvent('import.completed', { records: 10 });Nunca use CPF/CNPJ, e-mail, token, request ID, IDs de banco ou URLs concretas como atributos de agregação ou labels.
Readiness
Checks podem ser informados nas opções ou registrados posteriormente:
import { Injectable, OnModuleInit } from '@nestjs/common';
import { ReadinessService } from '@mobi4tech/nest-observability';
@Injectable()
export class DatabaseReadinessProbe implements OnModuleInit {
constructor(
private readonly readiness: ReadinessService,
private readonly database: DatabaseService,
) {}
onModuleInit() {
this.readiness.register('database', () => this.database.ping());
}
}O check deve ser leve e não deve lançar mensagens contendo credenciais ou dados pessoais, pois a mensagem resumida aparece na resposta interna de readiness.
Quando um serviço usa várias bases ou filas independentes, registre um check
por dependência. Assim, /health/ready e as métricas indicam exatamente qual
conexão falhou:
for (const name of databaseNames) {
readiness.register(`database_${name.toLowerCase()}`, () =>
databases.getDataSource(name).query('SELECT 1'),
);
}Use nomes estáveis, sem dados sensíveis. Cada check gera
service_readiness_check_status (1 saudável, 0 indisponível) e
service_readiness_check_duration_seconds.
Métricas fornecidas
http_server_requests_total;http_server_request_duration_seconds;http_server_requests_in_flight;service_build_info;- métricas padrão do Node.js com prefixo
nodejs_.
As labels HTTP são limitadas a método, rota normalizada e status code. IDs
numéricos e UUIDs são normalizados para :id para evitar alta cardinalidade.
Produção com PM2
- configure
SERVICE_NAME,APP_VERSION,NODE_ENVeLOG_LEVEL; - não configure
log_date_format, pois o prefixo quebra o JSON por linha; - configure rotação para os arquivos do PM2;
- mantenha Prometheus e os endpoints internos acessíveis somente por loopback.
O playbook completo de migração e os problemas já conhecidos ficam no repositório de observabilidade.
Desenvolvimento e publicação
npm install
npm test --workspace=@mobi4tech/nest-observability
npm run publish:public --workspace=@mobi4tech/nest-observabilityO comando de publicação executa build e testes por meio de prepublishOnly e
publica a versão com a dist-tag beta.
