Observability

Escopo: JavaScript. Visão transversal: shared/standards/observability.md.

Observability (observabilidade) é a capacidade de entender o estado interno de um sistema a partir do que ele emite para fora. Em JS/Node, isso significa logging estruturado em vez de strings concatenadas, níveis de severidade corretos, proteção de dados sensíveis e um identificador de correlação que liga todos os logs de uma mesma requisição. Os princípios agnósticos estão em shared/standards/observability.md.

Conceitos fundamentais

ConceitoO que é
structured logging (log estruturado)Log emitido como objeto JSON com campos pesquisáveis, não string concatenada
log level (nível de log)Severidade: trace, debug, info, warn, error, fatal
correlation ID (ID de correlação)Identificador único por requisição que aparece em todos os logs do mesmo fluxo
redaction (redação)Remoção ou mascaramento de campos sensíveis (senha, token, CPF) antes de emitir
PII (Personally Identifiable Information, Informação Pessoal Identificável)Dado que identifica um indivíduo; nunca sai cru no log
trace (rastro)Caminho de uma requisição atravessando múltiplos serviços; cada salto é um span
span (trecho)Unidade de trabalho dentro de um trace; tem início, fim e atributos
metric (métrica)Valor numérico agregado (contador, gauge, histograma) emitido periodicamente

Logging estruturado

console.log com strings é ilegível para sistemas de observabilidade. Use Pino (ou Winston) com objetos estruturados: cada campo vira uma propriedade pesquisável.

❌ Ruim: string concatenada, ilegível para ferramentas
logger.info(`Order ${order.id} processed by user ${user.id}, total: $${order.total}`);
logger.error(`Payment failed: ${error.message} for order ${order.id}`);
✅ Bom: objeto estruturado com campos semânticos
const orderContext = { orderId: order.id, userId: user.id, total: order.total };
logger.info(orderContext, "order processed");

const paymentErrorContext = { orderId: order.id, error: error.message };
logger.error(paymentErrorContext, "payment failed");

Níveis de log

❌ Ruim: console.log para tudo, sem distinção de severidade
console.log("Checkout started");
console.log(`Database query took ${durationMs}ms`);

console.log(`User ${userId} not found`);
✅ Bom: nível correto por situação
const checkoutContext = { cartId };
logger.info(checkoutContext, "checkout started");

const slowQueryContext = { cartId, durationMs };
logger.warn(slowQueryContext, "slow database query");

const userNotFoundContext = { cartId, userId };
logger.error(userNotFoundContext, "user not found during checkout");

O que nunca logar

❌ Ruim: PII e credenciais em log
logger.info({ email: user.email, password: user.password }, "login attempt");
logger.info({ cardNumber: payment.card, cvv: payment.cvv }, "payment initiated");

logger.info({ token }, "user authenticated");
✅ Bom: IDs e referências, nunca dados sensíveis
const loginContext = { userId: user.id };
logger.info(loginContext, "login attempt");

const paymentContext = { paymentId: payment.id, last4: payment.lastFour };
logger.info(paymentContext, "payment initiated");

const authContext = { userId: user.id };
logger.info(authContext, "user authenticated");

Correlation ID

Sem um identificador comum, logs de uma mesma requisição são ilhas: impossível rastrear o fluxo. AsyncLocalStorage propaga o correlationId para todos os logs sem passar por parâmetro.

❌ Ruim: logs sem contexto de requisição
async function processOrder(orderId) {
  logger.info("processing order");

  const invoice = await buildInvoice(orderId);

  logger.info("order processed");

  return invoice;
}
// {"msg":"processing order"}: impossível saber qual request originou
✅ Bom: correlationId propagado via AsyncLocalStorage
// middleware/correlation.js
const requestStore = new AsyncLocalStorage();

export function correlationMiddleware(request, response, next) {
  const correlationId = request.headers["x-correlation-id"] ?? crypto.randomUUID();

  response.setHeader("x-correlation-id", correlationId);

  const store = { correlationId };
  requestStore.run(store, next);
}

export const logger = pino({
  mixin() {
    const context = requestStore.getStore();
    return context ?? {};
  },
});

// handler: correlationId incluído automaticamente em todos os logs
async function processOrder(orderId) {
  const startContext = { orderId };
  logger.info(startContext, "processing order");

  const invoice = await buildInvoice(orderId);

  const completedContext = { orderId, invoiceId: invoice.id };
  logger.info(completedContext, "order processed");

  return invoice;
}
// {"correlationId":"abc-123","orderId":"...","msg":"processing order"}

Desenvolvido por @thiagocajadev · Fork baseado no repositório pmndrs/docs · Poimandres.