Home > Blog > Desenvolvimento Web
Desenvolvimento Web
JavaScript
Programação

Diagnostics Channel no Node.js

Atualizado em: 8 de agosto de 2026

Rack de servidores processando fluxos de dados no Node.js

Bibliotecas de observabilidade precisam acompanhar operações internas sem obrigar cada aplicação a modificar o código de negócio. O módulo Diagnostics Channel no Node.js oferece canais nomeados para publicar eventos de diagnóstico com baixo acoplamento entre quem executa uma operação e quem deseja observá-la.

Um módulo HTTP, cliente de banco, fila ou biblioteca própria pode publicar informações em pontos importantes. Ferramentas de tracing, métricas e logs se inscrevem nesses canais somente quando necessário. Quando não existem assinantes, o custo pode permanecer pequeno.

Neste guia, você aprenderá a criar canais, publicar mensagens, verificar assinantes, usar subscribe e unsubscribe, preservar contexto assíncrono, evitar dados sensíveis e integrar Diagnostics Channel a métricas e traces.

O que é diagnostics_channel?

node:diagnostics_channel é um módulo nativo que implementa um sistema de publicação e assinatura voltado para diagnóstico. Canais são identificados por nomes estáveis. Produtores enviam mensagens e consumidores registram callbacks para recebê-las.

A documentação oficial de Diagnostics Channel descreve canais, tracingChannel e recursos de contexto. Para revisar conceitos relacionados, veja EventEmitter no Node.js e OpenTelemetry no Node.js.

Diagnostics Channel não é EventEmitter

As duas APIs distribuem eventos, mas possuem objetivos diferentes. EventEmitter normalmente faz parte do comportamento funcional de um objeto. Diagnostics Channel cria pontos de observação globais e nomeados para instrumentação.

Uma regra de negócio não deve depender da presença de um assinante de diagnóstico. O produtor precisa funcionar corretamente mesmo quando nenhum consumidor está registrado.

Criando um canal

const diagnosticsChannel = require('node:diagnostics_channel');

const requestChannel = diagnosticsChannel.channel(
  'app:http:request'
);

Chamadas com o mesmo nome retornam uma referência para o mesmo canal lógico. Escolha nomes específicos, documentados e com namespace para evitar colisões.

Publicando uma mensagem

requestChannel.publish({
  method: req.method,
  route: req.route?.path || 'unknown',
  startedAt: Date.now()
});

A mensagem pode ser qualquer valor, mas objetos com campos estáveis facilitam integração. Evite publicar o objeto completo de requisição sem necessidade, pois ele contém headers, tokens, cookies, body e referências grandes.

Inscrevendo um consumidor

function onRequest(message, name) {
  console.log({
    channel: name,
    method: message.method,
    route: message.route
  });
}

requestChannel.subscribe(onRequest);

O callback recebe a mensagem e o nome do canal. Ele é executado de forma síncrona durante publish(). Trabalho lento dentro do assinante aumenta a duração da operação instrumentada.

Removendo a inscrição

requestChannel.unsubscribe(onRequest);

Use a mesma referência de função. Uma função anônima criada novamente não corresponde ao callback original. Remova assinantes em testes, recarregamentos e módulos temporários.

Verificando se existem assinantes

if (requestChannel.hasSubscribers) {
  requestChannel.publish({
    method: req.method,
    route: normalizeRoute(req)
  });
}

A verificação é útil quando construir a mensagem exige trabalho. Se o payload já é barato, publicar diretamente pode deixar o código mais simples.

Evite lógica condicional de negócio

Este padrão é incorreto:

if (channel.hasSubscribers) {
  await saveOrder();
}

A operação passaria a depender da ferramenta de observabilidade. O canal deve observar, não controlar a execução principal.

Projetando o formato da mensagem

Trate o payload como um contrato:

{
  operation: 'database.query',
  databaseSystem: 'postgresql',
  statementName: 'find-user',
  startedAt: 12345,
  requestId: 'req-123'
}

Prefira nomes de operação e identificadores normalizados. SQL completo, URLs com parâmetros e nomes de usuário podem expor dados ou criar cardinalidade alta.

Canais de início e fim

Uma operação pode publicar eventos separados:

const startChannel = channel('app:query:start');
const endChannel = channel('app:query:end');
const errorChannel = channel('app:query:error');

async function executeQuery(query) {
  const context = {
    name: query.name,
    startedAt: performance.now()
  };

  startChannel.publish(context);

  try {
    const result = await database.query(query);

    endChannel.publish({
      ...context,
      duration: performance.now() - context.startedAt,
      rowCount: result.rowCount
    });

    return result;
  } catch (error) {
    errorChannel.publish({
      ...context,
      duration: performance.now() - context.startedAt,
      error
    });

    throw error;
  }
}

O erro deve ser relançado para manter o comportamento original. Publicar diagnóstico não substitui tratamento funcional.

TracingChannel

O recurso tracingChannel() agrupa canais relacionados ao ciclo de uma operação. Ele oferece eventos para início, sucesso, erro e outras etapas, reduzindo convenções manuais.

const {
  tracingChannel
} = require('node:diagnostics_channel');

const tracing = tracingChannel('app:payment');

Verifique a versão mínima do Node.js utilizada no projeto. Recursos mais novos podem não existir em runtimes antigos.

Executando uma operação instrumentada

Uma tracing channel pode envolver uma função e publicar o ciclo automaticamente de acordo com a API disponível:

const result = await tracing.tracePromise(
  async context => {
    context.paymentId = payment.id;
    return paymentProvider.capture(payment);
  },
  { operation: 'capture-payment' }
);

O formato exato deve seguir a documentação da versão instalada. Cubra essa integração com testes para detectar mudanças durante atualizações.

Contexto assíncrono

Instrumentação frequentemente precisa associar eventos à requisição atual. AsyncLocalStorage pode armazenar request ID, trace ID e informações normalizadas:

const store = asyncLocalStorage.getStore();

channel.publish({
  operation: 'cache.get',
  requestId: store?.requestId,
  keyNamespace: 'users'
});

Não armazene objetos enormes ou dados pessoais desnecessários. Veja o guia de AsyncLocalStorage no Node.js.

Assinantes devem ser rápidos

Como o callback roda durante a publicação, evite escrita de arquivo síncrona, serialização enorme ou chamadas de rede. Agregue métricas em memória e exporte em lote.

channel.subscribe(message => {
  metrics.increment('operation_total', {
    operation: message.operation
  });
});

Mesmo operações aparentemente pequenas podem gerar alto volume. Meça o overhead da instrumentação com carga real.

Tratando erros no assinante

Um assinante com bug não deve comprometer a aplicação. Proteja transformações próprias:

function safeSubscriber(message) {
  try {
    collectMetric(message);
  } catch (error) {
    diagnosticsLogger.warn('Falha na instrumentação', {
      error: error.message
    });
  }
}

Evite registrar o próprio erro no mesmo canal, criando uma recursão infinita.

Integração com métricas

Um consumidor pode observar duração e status:

endChannel.subscribe(message => {
  histogram.observe(message.duration, {
    operation: message.name,
    status: 'success'
  });
});

errorChannel.subscribe(message => {
  histogram.observe(message.duration, {
    operation: message.name,
    status: 'error'
  });
});

Use conjuntos limitados de labels. Identificadores únicos criam séries demais e aumentam custo do sistema de métricas.

Integração com tracing

Uma biblioteca de tracing pode iniciar um span no canal de início e finalizá-lo em sucesso ou erro. O contexto precisa ser associado de forma segura à operação. Não use um mapa global sem limpeza, porque operações abandonadas podem permanecer na memória.

Prefira identificadores ou objetos de contexto que acompanhem o ciclo e sejam liberados no encerramento.

Instrumentação de bibliotecas

Diagnostics Channel é especialmente útil para autores de bibliotecas. A biblioteca publica eventos sem depender de um fornecedor de observabilidade. A aplicação escolhe se deseja conectar OpenTelemetry, métricas próprias ou nenhum consumidor.

Documente nomes, campos, tipos, momento de publicação e estabilidade do contrato. Alterações silenciosas quebram integrações.

Instrumentação condicional

Quando coletar detalhes custa caro:

if (queryChannel.hasSubscribers) {
  queryChannel.publish({
    operation: query.name,
    parameterCount: query.values.length,
    estimatedSize: estimateSize(query.values)
  });
}

Não calcule estimatedSize se ninguém está ouvindo. Essa otimização mantém baixo o custo normal.

Dados sensíveis

Não publique:

  • senhas, tokens e cookies;
  • números de cartão ou documentos;
  • corpo completo de requisições;
  • SQL com valores interpolados;
  • URLs contendo segredos;
  • objetos de usuário sem filtragem.

Crie uma função de sanitização central e teste campos proibidos.

Testando canais

test('publica evento de consulta', async () => {
  const messages = [];

  function subscriber(message) {
    messages.push(message);
  }

  queryChannel.subscribe(subscriber);

  try {
    await repository.findUser('123');
    assert.equal(messages[0].operation, 'find-user');
  } finally {
    queryChannel.unsubscribe(subscriber);
  }
});

O bloco finally evita que um assinante vaze para outros testes.

Versionando contratos

Quando uma mudança incompatível for necessária, crie outro nome de canal ou campo de versão. Consumidores antigos continuam funcionando enquanto a migração acontece.

channel('app:query:v2:start');

Não adicione versão a cada alteração compatível; use-a para mudanças de contrato realmente relevantes.

Erros comuns

  • Executar trabalho lento no assinante: a operação principal fica mais demorada.
  • Publicar dados completos: segredos e alta cardinalidade chegam à telemetria.
  • Depender do assinante: a regra de negócio muda quando a instrumentação é desligada.
  • Não remover inscrição em testes: callbacks são duplicados.
  • Mudar payload sem documentar: consumidores quebram.
  • Criar recursão de diagnóstico: o próprio logger publica no mesmo canal.
  • Instrumentar tudo: overhead e volume superam o benefício.

Boas práticas para produção

  • Use nomes com namespace e contrato estável.
  • Mantenha payloads pequenos e normalizados.
  • Evite dados sensíveis.
  • Garanta que assinantes sejam rápidos.
  • Use hasSubscribers para detalhes caros.
  • Integre contexto assíncrono com cuidado.
  • Limite cardinalidade de métricas.
  • Teste publicação e remoção de assinantes.
  • Meça o overhead com carga representativa.
  • Documente canais públicos de bibliotecas.

Conclusão

O Diagnostics Channel no Node.js cria uma fronteira limpa entre código instrumentado e ferramentas de observabilidade. Bibliotecas publicam eventos estáveis, enquanto consumidores escolhem como transformar esses eventos em métricas, logs ou traces.

O benefício depende de disciplina: mensagens pequenas, assinantes rápidos, contratos documentados e nenhuma dependência funcional. Com esses cuidados, a aplicação ganha visibilidade sem espalhar código específico de um fornecedor por todas as camadas.

Os 10 Melhores Cursos de Programação de 2026

Descubra os melhores cursos de programação. Aprenda a escolher o curso ideal para iniciar ou avançar na carreira de desenvolvedor

POSTS RELACIONADOS

Ver todos

Seta para a direita