O objeto Console no Node.js é uma das ferramentas mais usadas durante desenvolvimento, mas também participa de logs, diagnósticos, scripts de linha de comando e observabilidade em produção. Métodos como console.log(), console.error(), console.time() e console.table() parecem simples, porém seu comportamento depende dos streams de saída, da formatação dos valores e do ambiente onde o processo executa.
Em aplicações pequenas, imprimir mensagens pode ser suficiente. Em serviços com alto volume, múltiplos workers ou requisitos de auditoria, logs sem estrutura, sem níveis e sem contexto dificultam investigações. Além disso, serializar objetos grandes ou escrever milhares de linhas por segundo pode aumentar latência e consumo de CPU.
Neste guia, você aprenderá a usar o console global, criar instâncias personalizadas, separar stdout e stderr, formatar valores, medir duração, agrupar mensagens, respeitar backpressure, evitar segredos e evoluir de logs simples para registros estruturados.
O que é o Console no Node.js?
O console global é uma instância da classe Console. Seus métodos escrevem principalmente em process.stdout e process.stderr. A documentação oficial de Console descreve métodos, opções e diferenças de comportamento.
Para entender os streams usados na saída, consulte Objeto Process no Node.js e Streams no Node.js.
console.log e console.info
console.log('Servidor iniciado');
console.info('Porta:', 3000);Os dois métodos normalmente escrevem em stdout. Eles aceitam vários argumentos e utilizam regras de formatação semelhantes a util.format().
console.log('Usuário %s possui %d itens', 'Ana', 4);Em produção, prefira mensagens estáveis e campos separados. Textos que mudam constantemente dificultam busca e agregação.
console.error e console.warn
console.error('Falha ao conectar ao banco');
console.warn('Cache indisponível');Esses métodos escrevem em stderr. Separar saída normal de erros é importante em scripts Unix, containers e plataformas que coletam os dois streams de forma diferente.
node script.js >resultado.log 2>erros.logUma mensagem em stderr não define automaticamente um código de saída. Use process.exitCode quando o comando deve terminar com falha.
Formatação com placeholders
O console reconhece marcadores como:
%spara string;%dou%ipara número inteiro;%fpara ponto flutuante;%jpara JSON;%oe%Opara objetos;%%para o caractere de porcentagem.
console.log('Duração: %d ms', 42);
console.log('Configuração: %o', config);A documentação de util.format detalha as regras. O artigo sobre Módulo Util no Node.js mostra outras funções de inspeção.
Inspecionando objetos
console.dir(object, {
depth: 4,
colors: process.stdout.isTTY
});console.dir() permite controlar profundidade, cores e outras opções de inspeção. Evite imprimir objetos completos de requisição, conexão ou usuário. Eles podem conter tokens, cookies, senhas e referências circulares.
console.table
console.table([
{ route: '/users', requests: 120 },
{ route: '/orders', requests: 85 }
]);O formato tabular é útil para scripts administrativos e depuração local. Em logs de produção, tabelas ocupam várias linhas e são difíceis de processar automaticamente.
Contadores
console.count('cache-miss');
console.count('cache-miss');
console.countReset('cache-miss');Contadores são mantidos na memória da instância atual. Eles não substituem métricas persistentes e são reiniciados com o processo.
Medição de tempo
console.time('database-query');
await repository.findUsers();
console.timeEnd('database-query');console.timeLog() registra valores intermediários:
console.time('import');
await readFile();
console.timeLog('import', 'arquivo lido');
await transform();
console.timeEnd('import');Os labels precisam ser consistentes. Em código concorrente, usar o mesmo label para várias requisições pode gerar conflitos. Para instrumentação real, prefira Performance Hooks no Node.js ou um sistema de métricas.
Agrupando mensagens
console.group('Inicialização');
console.log('Configuração carregada');
console.log('Banco conectado');
console.groupEnd();console.groupCollapsed() possui utilidade maior em navegadores; em terminais, o comportamento pode ser semelhante ao grupo comum. Grupos são úteis em ferramentas interativas, mas pouco adequados para logs estruturados.
Assertions com console.assert
console.assert(port > 0, 'Porta inválida:', port);Quando a condição é falsa, uma mensagem é escrita. Esse método não substitui validação nem lança necessariamente uma exceção. Para testes, use Assert no Node.js.
Criando uma instância personalizada
const { Console } = require('node:console');
const fs = require('node:fs');
const output = fs.createWriteStream('./application.log');
const errorOutput = fs.createWriteStream('./errors.log');
const logger = new Console({
stdout: output,
stderr: errorOutput
});
logger.log('Aplicação iniciada');
logger.error('Falha simulada');A classe permite direcionar saída para arquivos, sockets ou streams personalizados. Ainda é necessário tratar erros dos streams e realizar fechamento no shutdown.
Opções de inspeção
const logger = new Console({
stdout: process.stdout,
stderr: process.stderr,
inspectOptions: {
depth: 3,
colors: process.stdout.isTTY,
maxArrayLength: 50
}
});Limitar profundidade e tamanho de arrays reduz volume acidental. Essas opções não substituem sanitização.
stdout, stderr e sincronismo
O comportamento de escrita pode variar conforme o destino e a plataforma. Um terminal, arquivo e pipe não são equivalentes. Não presuma que todas as mensagens foram persistidas imediatamente.
Chamar process.exit() logo depois de escrever pode truncar saída:
console.error('Erro fatal');
process.exit(1);Prefira definir process.exitCode = 1 e permitir que o processo conclua o fluxo, ou aguarde o fechamento dos streams quando necessário.
Backpressure
A API de console não oferece controle explícito de retorno para cada chamada. Em alto volume, escrever diretamente no stream permite observar backpressure:
async function writeLine(stream, value) {
if (stream.write(`${value}\n`)) return;
await new Promise(resolve => {
stream.once('drain', resolve);
});
}Para logs de produção, bibliotecas especializadas implementam buffering, serialização e transporte de forma mais eficiente.
Logs estruturados
Em vez de concatenar texto:
console.log(
`Usuário ${userId} acessou ${route} em ${duration} ms`
);Use um objeto com campos previsíveis:
console.log(JSON.stringify({
level: 'info',
message: 'request_completed',
userId,
route,
durationMs: duration,
timestamp: new Date().toISOString()
}));JSON por linha facilita ingestão por plataformas de logs. Evite valores dinâmicos como nomes de campo e mantenha um schema estável.
Níveis de log
O console não filtra níveis automaticamente. Crie uma camada simples:
const levels = {
debug: 10,
info: 20,
warn: 30,
error: 40
};
const configuredLevel = levels[
process.env.LOG_LEVEL || 'info'
];
function log(level, message, fields = {}) {
if (levels[level] < configuredLevel) return;
const output = JSON.stringify({
level,
message,
...fields,
timestamp: new Date().toISOString()
});
const stream = level === 'error'
? process.stderr
: process.stdout;
stream.write(`${output}\n`);
}Para sistemas maiores, use uma biblioteca madura com redaction, serializers e transportes.
Contexto por requisição
Logs são mais úteis quando incluem request ID, trace ID e operação. AsyncLocalStorage pode preservar contexto:
const store = requestStorage.getStore();
log('info', 'database_query_finished', {
requestId: store?.requestId,
operation: 'find-user',
durationMs
});Veja AsyncLocalStorage no Node.js para propagação segura.
Redação de segredos
Nunca registre:
- senhas;
- tokens de acesso;
- cookies de sessão;
- chaves de API;
- dados completos de cartão;
- variáveis de ambiente inteiras;
- corpos completos sem filtragem.
function sanitizeHeaders(headers) {
const copy = { ...headers };
for (const key of ['authorization', 'cookie', 'x-api-key']) {
if (copy[key]) copy[key] = '[REDACTED]';
}
return copy;
}A sanitização deve ocorrer antes da serialização. Teste campos proibidos para evitar regressões.
Erros e stack traces
try {
await operation();
} catch (error) {
console.error({
name: error.name,
message: error.message,
stack: error.stack
});
}Nem todo erro é uma instância padrão. Normalize valores desconhecidos. Em produção, considere se a stack expõe caminhos internos ou detalhes sensíveis.
Console em múltiplos processos
Cluster e containers podem escrever simultaneamente. Linhas muito grandes podem se misturar dependendo do transporte. Use uma linha JSON por evento e inclua instance ID, PID e worker ID.
O guia de Cluster no Node.js mostra como vários processos atendem a mesma aplicação.
Custo de serialização
Mesmo quando o nível está desativado, construir objetos ou converter JSON antes da checagem consome CPU:
if (isDebugEnabled()) {
log('debug', 'large_payload', {
payload: buildExpensiveSummary(data)
});
}Evite serializar estruturas enormes em caminhos críticos. Use amostragem e resumos.
Testando logs
Injete streams de memória:
const { Writable } = require('node:stream');
function createCollector() {
const lines = [];
const stream = new Writable({
write(chunk, encoding, callback) {
lines.push(chunk.toString());
callback();
}
});
return { stream, lines };
}Crie uma instância Console com esse stream e verifique mensagens, campos e ausência de segredos.
Logs não substituem métricas e traces
Logs explicam eventos individuais. Métricas mostram tendências e traces conectam operações distribuídas. Use cada ferramenta para sua finalidade. O conteúdo de OpenTelemetry no Node.js apresenta traces e métricas.
Erros comuns
- Registrar objetos completos: segredos e volume excessivo aparecem.
- Usar console.log para tudo: níveis e destinos ficam misturados.
- Chamar process.exit imediatamente: mensagens podem ser truncadas.
- Construir logs caros desativados: CPU é consumida sem benefício.
- Usar tabelas em produção: processamento automático fica difícil.
- Não incluir contexto: eventos não podem ser correlacionados.
- Tratar logs como métricas: agregações ficam lentas e caras.
Boas práticas para produção
- Use JSON por linha.
- Separe stdout e stderr.
- Defina níveis configuráveis.
- Inclua request ID e trace ID.
- Remova segredos antes de escrever.
- Limite profundidade e tamanho de objetos.
- Evite serialização cara em caminhos críticos.
- Respeite backpressure em alto volume.
- Teste o schema e a redaction.
- Combine logs, métricas e traces.
Conclusão
O Console no Node.js oferece ferramentas úteis para desenvolvimento, scripts e diagnósticos, incluindo formatação, inspeção, contadores, grupos e medição de tempo. A classe Console também permite direcionar mensagens para streams personalizados.
Em produção, qualidade depende mais da disciplina que do método utilizado. Estrutura estável, níveis, contexto, sanitização e controle de volume tornam logs pesquisáveis e seguros. Quando a aplicação cresce, uma camada de logging especializada deve complementar ou substituir chamadas diretas ao console.



