Node.js App Logging API: Structured JSON with Pino, Winston, and Request IDs A developer outlined a practical logging pattern for Node.js AI agent workflows that uses Pino or Winston to emit structured JSON completion events, each tagged with request_id, user_id, trace_id, environment, latency_ms, cost_usd, iterations, and status, then ships them to a central ingest API. The approach treats the completed agent loop as the unit of measurement and the request as the unit of investigation, while keeping raw prompts and article text out of routine logs via redaction. The example includes retry handling with exponential backoff on HTTP 429 responses and recommends placing the ingest call behind a durable shipper or queue in latency-sensitive services. The useful trade-off is signal quality versus noise. TL;DR: in a Node.js app, use Pino or Winston to produce structured JSON logs, then send each completed AI agent run to a central logging API with a request ID and user ID. This gives a small media team a practical view of latency and cost without pretending that a log backend provides traces, alerts, replay, or an archive. Do less first. For an agent that researches a story, calls a model several times, and writes a draft, logging every prompt fragment and intermediate object creates volume without answering the operating question. The completion event should carry the total duration and total model cost for the loop, plus request id , user id , trace id , environment , outcome, and iteration count. Step events are useful when they explain a slow or failed run, but they need the same identifiers or they become isolated lines that nobody can join. Treat the request as the unit of investigation and the completed agent loop as the unit of measurement. A request ID separates retries and concurrent browser requests. A user ID supports product-level investigation, provided it is an internal identifier rather than an email address. A trace ID joins related log records. An environment field prevents a staging run from polluting production searches. The distinction around tracing matters: trace id and span id can be stored as correlation fields, but that does not create a distributed tracing UI or a queryable span tree. Logs can show that three events share an execution. They cannot reconstruct parent-child timing on their own. For the media workflow, the compact completion event might contain agent run completed , latency ms , cost usd , iterations , and status . Cost should be accumulated from the per-call metadata returned by the model surface rather than estimated later from prompt text. Latency should be measured around the whole loop and, when diagnosis requires it, around individual model calls too. Keep raw article text and full prompts out of routine logs; they add noise and may contain reader or newsroom data. This example uses Pino for local structured output and sends the same completion event to the verified central ingest route. In a latency-sensitive service, put ingestLog behind a durable shipper or queue so a logging delay does not extend the article request. The direct call below keeps the network behavior visible and the example runnable: the key comes from the environment, the method is explicit, rate limits back off, and non-success responses retain their real error body. python import pino from "pino"; import { randomUUID } from "node:crypto"; const apiKey = process.env.INFRAI API KEY; const ingestUrl = process.env.INFRAI LOG INGEST URL; if apiKey || ingestUrl { throw new Error "INFRAI API KEY and INFRAI LOG INGEST URL are required" ; } const logger = pino { level: process.env.LOG LEVEL ?? "info", base: { service: "article-agent", environment: process.env.NODE ENV ?? "development", }, redact: "prompt", "article text", "authorization" , } ; type ModelCall = { costUsd: number; latencyMs: number; }; type AgentResult = { status: "ok" | "failed"; iterations: number; calls: ModelCall ; }; type LogEvent = Record