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

Diagnostics Channel no Node.js

Atualizado em: 29 de setembro de 2026

Rack de servidores processando fluxos de dados no Node.js

Diagnostics Channel é uma API do Node.js para publicar eventos de diagnóstico entre bibliotecas e ferramentas de observabilidade com baixo acoplamento. Um módulo pode emitir informações sobre requisições, consultas ou tarefas, enquanto outro módulo assina esses eventos para gerar métricas, logs ou traces.

A API evita que bibliotecas dependam diretamente de um agente de APM específico. Quando não há assinantes, o custo pode ser pequeno, mas toda instrumentação precisa ser medida sob carga.

Conceito de canal

Um canal é identificado por uma string. Publicadores e assinantes precisam usar exatamente o mesmo nome e concordar sobre o formato da mensagem.

import diagnosticsChannel from 'node:diagnostics_channel';

const channel = diagnosticsChannel.channel('app.orders.created');

O método channel() devolve uma referência ao canal nomeado.

Publicando uma mensagem

const ordersCreated = diagnosticsChannel.channel('app.orders.created');

function createOrder(order) {
  const result = repository.save(order);

  if (ordersCreated.hasSubscribers) {
    ordersCreated.publish({
      orderId: result.id,
      createdAt: result.createdAt,
    });
  }

  return result;
}

Verificar hasSubscribers evita construir payloads caros quando ninguém está ouvindo. Para objetos simples já disponíveis, publicar diretamente pode ser suficiente.

Assinando um canal

function onOrderCreated(message, name) {
  metrics.ordersCreated.inc();
  logger.debug({
    channel: name,
    orderId: message.orderId,
  }, 'Pedido criado');
}

ordersCreated.subscribe(onOrderCreated);

O callback recebe a mensagem e o nome. Ele executa de forma síncrona durante publish, portanto deve ser rápido e não lançar exceções.

Desinscrevendo

ordersCreated.unsubscribe(onOrderCreated);

Guarde a mesma referência da função. Em testes e plugins recarregáveis, remover assinantes evita duplicação e vazamentos.

Publicação é síncrona

O publicador fica bloqueado enquanto os assinantes processam a mensagem. Não faça:

  • requisições HTTP;
  • consultas ao banco;
  • serialização grande;
  • escrita síncrona em disco;
  • cálculos pesados;
  • logs excessivos.

Atualize métricas em memória ou enfileire uma amostra para processamento posterior.

Tratamento de erros

Um assinante defeituoso não deve quebrar a operação instrumentada. Envolva código próprio:

function safeSubscriber(message, name) {
  try {
    collect(message, name);
  } catch (error) {
    diagnosticsLogger.warn({ error, name }, 'Falha no assinante');
  }
}

Não crie um ciclo em que o logger também publica no mesmo canal.

Contrato da mensagem

Documente:

  • nome do canal;
  • momento da publicação;
  • campos e tipos;
  • campos opcionais;
  • unidades;
  • possíveis dados sensíveis;
  • compatibilidade entre versões.

Canal é uma interface pública quando outras bibliotecas dependem dele. Mudanças silenciosas quebram instrumentação.

Canais de início e fim

Uma operação pode publicar eventos distintos:

const startChannel = diagnosticsChannel.channel('app.payment.start');
const endChannel = diagnosticsChannel.channel('app.payment.end');
const errorChannel = diagnosticsChannel.channel('app.payment.error');

async function charge(input) {
  const context = { paymentId: input.id, start: performance.now() };
  startChannel.publish(context);

  try {
    const result = await gateway.charge(input);
    endChannel.publish({
      ...context,
      durationMs: performance.now() - context.start,
      status: result.status,
    });
    return result;
  } catch (error) {
    errorChannel.publish({
      ...context,
      durationMs: performance.now() - context.start,
      error,
    });
    throw error;
  }
}

Não publique detalhes de cartão, token ou payload completo.

Tracing com start e end

Um assinante pode criar span no início e encerrá-lo no fim. O desafio é manter o mesmo contexto entre eventos assíncronos. Use AsyncLocalStorage ou APIs de tracing, não um mapa global sem limpeza.

TracingChannel

A API oferece abstrações para representar etapas de tracing como start, end, asyncStart, asyncEnd e error. Ela reduz convenções manuais e integra contexto assíncrono. Confirme a disponibilidade e estabilidade na versão mínima adotada.

const tracing = diagnosticsChannel.tracingChannel('app.inventory.lookup');

Bibliotecas podem envolver operações e ferramentas podem assinar os canais correspondentes.

Instrumentando uma biblioteca

Uma biblioteca reutilizável não deveria importar o logger da aplicação. Em vez disso:

const queryChannel = diagnosticsChannel.channel('mydb.query');

export async function executeQuery(sql, parameters) {
  const start = performance.now();
  try {
    const result = await driver.query(sql, parameters);
    queryChannel.publish({
      operation: classifySql(sql),
      durationMs: performance.now() - start,
      rowCount: result.rowCount,
      success: true,
    });
    return result;
  } catch (error) {
    queryChannel.publish({
      operation: classifySql(sql),
      durationMs: performance.now() - start,
      success: false,
      error,
    });
    throw error;
  }
}

Não publique SQL bruto por padrão. Normalize operação e proteja parâmetros.

Métricas

queryChannel.subscribe((message) => {
  queryDuration.observe(
    {
      operation: message.operation,
      success: String(message.success),
    },
    message.durationMs / 1000,
  );
});

Use labels de baixa cardinalidade. Nunca use query, user ID ou URL completa como label.

Logs amostrados

queryChannel.subscribe((message) => {
  if (message.durationMs > 500 || Math.random() < 0.01) {
    logger.info({
      operation: message.operation,
      durationMs: message.durationMs,
      success: message.success,
    }, 'Diagnóstico de consulta');
  }
});

Amostragem aleatória simples pode ser suficiente para diagnóstico, mas traces distribuídos costumam aplicar decisões mais consistentes.

Contexto com AsyncLocalStorage

queryChannel.subscribe((message) => {
  const context = requestContext.getStore();
  metrics.record({
    ...message,
    requestId: context?.requestId,
  });
});

Não transforme requestId em label de métrica. Ele pode aparecer em logs ou exemplares controlados.

Descobrindo assinantes

hasSubscribers é útil para evitar trabalho:

if (channel.hasSubscribers) {
  channel.publish(createDiagnosticPayload());
}

O estado pode mudar entre a verificação e a publicação. Isso não é um problema para diagnóstico; não use como sincronização de negócio.

Nomeação de canais

Use namespaces previsíveis:

empresa.produto.modulo.operacao.start
empresa.produto.modulo.operacao.end
empresa.produto.modulo.operacao.error

Evite nomes genéricos como request. Canais globais compartilham o processo.

Versionamento

Se o payload precisar de mudança incompatível, crie canal novo ou adicione campo de versão:

channel.publish({
  schemaVersion: 2,
  operation,
  durationMs,
});

Campos adicionais opcionais costumam ser compatíveis. Remover ou mudar unidade não é.

Testes do publicador

const messages = [];
const subscriber = (message) => messages.push(message);

channel.subscribe(subscriber);
try {
  await executeOperation();
  assert.equal(messages.length, 1);
  assert.equal(messages[0].success, true);
} finally {
  channel.unsubscribe(subscriber);
}

Teste sucesso, erro e ausência de assinantes.

Testes de overhead

Compare:

  • sem canal;
  • canal sem assinante;
  • um assinante leve;
  • vários assinantes;
  • payload simples e complexo.

Meça throughput, p99, CPU, alocações e event loop delay.

Worker Threads

Cada thread possui seu próprio ambiente. Um canal na thread principal não recebe automaticamente publicações internas do worker. Agregue via mensagens se necessário, preservando volume baixo.

Processos cluster

Cada processo possui assinantes e métricas separados. Use um coletor externo ou agregação adequada. Não assuma que o canal atravessa IPC.

Ativação dinâmica

Uma aplicação pode carregar um módulo de diagnóstico somente quando uma variável de ambiente estiver habilitada. Evite adicionar e remover assinantes repetidamente em alta frequência.

Segurança

Mensagens permanecem no processo, mas agentes e assinantes podem exportá-las. Trate o canal como uma fronteira de observabilidade: publique apenas dados aprovados, com redaction e limites.

Quando não usar

Diagnostics Channel não é EventEmitter de negócio, fila durável ou mecanismo de comunicação entre serviços. Eventos podem não ter assinantes e não são armazenados. Use para diagnóstico opcional, não para ações essenciais.

Erros comuns

  • usar canal para lógica de negócio;
  • executar I/O pesado no assinante;
  • publicar payload sensível;
  • não documentar contrato;
  • usar labels de alta cardinalidade;
  • deixar assinantes duplicados em testes;
  • assumir comunicação entre workers;
  • não medir overhead;
  • lançar exceção no assinante.

Fluxo recomendado

Defina canais estáveis, mantenha payload pequeno, use hasSubscribers para construção cara, proteja assinantes e integre com métricas e traces. Combine com AsyncLocalStorage, Async Hooks, perf_hooks e OpenTelemetry.

Consulte a documentação oficial de Diagnostics Channel e a referência oficial de TracingChannel.

10 melhores cursos de programação em 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