La prima volta che l'osservabilità ti serve davvero non è quando stai guardando una dashboard con calma. È quando un utente scrive "il checkout è lento", il grafico degli errori sembra normale e nei log trovi solo una fila di messaggi scollegati.
OpenTelemetry nasce per evitare quel momento lì: non per avere più grafici, ma per collegare i pezzi. Una richiesta entra nell'API, chiama un database, passa da un provider esterno, pubblica un job in coda e magari fallisce tre servizi dopo. Senza tracing distribuito, quella storia la ricostruisci a mano. Con OpenTelemetry almeno hai una mappa.
Il punto non sono le trace, è la storia
Una trace è una sequenza di span. Detto così suona freddo. In pratica, ogni span è un pezzo della storia: POST /checkout, SELECT inventory, call payment provider, publish order.created.
Il valore arriva quando inizi a rispondere a domande vere:
- quale servizio esterno sta rallentando?
- gli errori arrivano da una versione specifica?
- il problema riguarda tutti o solo un tenant?
- un retry sta nascondendo un timeout?
- il job asincrono parte ma poi muore da un'altra parte?
Queste domande non le risolve un console.log messo di fretta. Anzi, spesso il log aggiunto in emergenza ti aiuta oggi e diventa rumore domani.
Come lo metterei in una app Node.js
La configurazione più sana è semplice: l'app produce telemetry, il Collector decide dove mandarla.
Node.js app -> OpenTelemetry Collector -> backend di observability
Perché non esportare direttamente al vendor? Perché all'inizio sembra più veloce, poi ti accorgi che ogni servizio ha configurazioni diverse, retry diversi, filtri diversi e nessun punto centrale dove togliere dati sensibili o cambiare destinazione.
Il Collector è noioso nel modo giusto. Riceve OTLP, fa batching, può filtrare, può fare sampling, può aggiungere attributi comuni e può esportare verso più sistemi.
Auto-instrumentation: bene, ma non basta
In Node.js partirei con l'auto-instrumentation. Ti dà subito visibilità su HTTP, framework supportati, database e librerie comuni.
npm install @opentelemetry/sdk-node \ @opentelemetry/auto-instrumentations-node \ @opentelemetry/exporter-trace-otlp-http
Poi inizializzi l'SDK prima del resto dell'app:
import { NodeSDK } from '@opentelemetry/sdk-node'; import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'; import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'; const sdk = new NodeSDK({ traceExporter: new OTLPTraceExporter({ url: process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, }), instrumentations: [getNodeAutoInstrumentations()], }); sdk.start();
Questo però vede il framework, non il tuo prodotto. Sa che hai fatto una query, ma non sa che quella query era dentro "crea ordine" o "rinnova abbonamento". Per quello servono span manuali nei punti dove il dominio conta.
const span = tracer.startSpan('checkout.create_order'); try { span.setAttribute('cart.items_count', input.items.length); const order = await createOrder(input); span.setAttribute('order.id', order.id); return order; } catch (error) { span.recordException(error as Error); throw error; } finally { span.end(); }
Non metterei span manuali ovunque. Li metterei dove, alle tre di notte, vorrei capire cosa è successo senza leggere mezzo codicebase.
Tre regole che evitano molto caos
Prima regola: ogni servizio deve avere service.name, ambiente e versione. Sembra banale, ma senza questi attributi una trace è molto meno utile. Quando un deploy rompe qualcosa, vuoi filtrare per versione in due secondi.
Seconda regola: non mettere dati sensibili negli attributi. Email, token, payload interi e indirizzi non devono finire in un backend di observability per sbaglio. Se ti serve identificare un utente, valuta ID interni, hashing o campi meno delicati.
Terza regola: attenzione alla cardinalità. user.id come attributo di trace può avere senso. Come label di metrica può distruggerti costi e performance.
Metriche: poche, ma buone
Io partirei da metriche molto pratiche:
- rate, errori e durata delle request;
- latenza delle dipendenze esterne;
- numero di timeout e retry;
- profondità delle code;
- durata dei job;
- percentuale di errori per versione.
Il resto si aggiunge quando serve. Le dashboard piene di grafici che nessuno guarda sono arredamento, non observability.
Log: ancora utili, ma collegati
I log non spariscono. Semplicemente diventano molto più utili quando portano trace_id e span_id. Così puoi partire da un log di errore e aprire la trace, oppure partire da una trace lenta e leggere solo i log prodotti in quel percorso.
Senza correlazione, stai cercando aghi. Con correlazione, almeno sai in quale cassetto guardare.
La checklist che userei prima di dire "siamo coperti"
- Le trace attraversano davvero più servizi.
- I log includono
trace_idespan_id. - Il Collector è configurato con batching e limiti di memoria.
- Gli errori vengono registrati negli span.
- Esiste una policy di sampling.
- Le metriche hanno cardinalità controllata.
- I dati sensibili vengono filtrati.
- Gli alert partono da sintomi utente, non da grafici casuali.
Conclusione
OpenTelemetry non risolve da solo i problemi di produzione. Però cambia il modo in cui li affronti. Invece di aggiungere log alla cieca, inizi a seguire il percorso reale di una richiesta.
Per me il segnale che sta funzionando è semplice: quando succede qualcosa, il team smette di chiedere "dove guardiamo?" e inizia a chiedere "perché quel pezzo è lento?". È lì che l'osservabilità diventa uno strumento, non una collezione di dashboard.