Come utilizzare Winston per registrare le applicazioni Node.js su Ubuntu 20.04

Introduzione

Una soluzione di logging efficace è cruciale per il successo di qualsiasi applicazione. Winston è una libreria di logging versatile e una soluzione di logging popolare disponibile per le applicazioni Node.js. Le caratteristiche di Winston includono il supporto per diverse opzioni di archiviazione, livelli di log, interrogazioni di log e un profiler integrato.

In questo tutorial, utilizzerai Winston per registrare un’applicazione Node/Express che creerai come parte di questo processo. Vedrai anche come combinare Winston con Morgan, un altro popolare middleware di logging delle richieste HTTP per Node.js, per consolidare i log dei dati delle richieste HTTP con altre informazioni. Dopo aver completato questo tutorial, il tuo server Ubuntu eseguirà una piccola applicazione Node/Express, e Winston sarà implementato per registrare errori e messaggi su un file e sulla console.

Prerequisiti

Per seguire questo tutorial, avrai bisogno di:

Passo 1 — Creare un’app Node/Express di Base

Winston viene spesso utilizzato per registrare eventi dalle applicazioni web costruite con Node.js. In questo passaggio, creerai una semplice applicazione web Node.js utilizzando il framework Express. Utilizzerai express-generator, un tool da riga di comando, per avviare rapidamente un’applicazione web Node/Express.

Dato che hai installato il Gestore Pacchetti Node durante i prerequisiti, puoi utilizzare il comando npm per installare express-generator:

  1. sudo npm install express-generator -g

Il flag -g installa il pacchetto globalmente, il che significa che può essere utilizzato come strumento da riga di comando al di fuori di un progetto/modulo Node esistente.

Con express-generator installato, puoi creare la tua app utilizzando il comando express, seguito dal nome della directory che desideri utilizzare per il progetto:

  1. express myApp

Per questo tutorial, il progetto sarà chiamato myApp.

Nota: È anche possibile eseguire direttamente lo strumento express-generator senza installarlo globalmente come comando a livello di sistema prima. Per farlo, esegui questo comando:

  1. npx express-generator myApp

Il comando npx è un eseguibile di comandi fornito con il Node Package Manager che facilita l’esecuzione di strumenti da riga di comando dal registro npm.

Durante la prima esecuzione, ti chiederà se accetti di scaricare il pacchetto:

Output
Need to install the following packages:
  express-generator
Ok to proceed? (y)

Rispondi con y e premi INVIO. Ora puoi utilizzare npx express-generator al posto di express.

Successivamente, installa Nodemon, che ricaricherà automaticamente l’applicazione ogni volta che apporti modifiche. Un’applicazione Node.js deve essere riavviata ogni volta che vengono apportate modifiche al codice sorgente affinché tali modifiche abbiano effetto, quindi Nodemon controllerà automaticamente le modifiche e riavvierà l’applicazione. Poiché desideri essere in grado di utilizzare nodemon come strumento da riga di comando, installalo con il flag -g:

  1. sudo npm install nodemon -g

Per completare la configurazione dell’applicazione, spostati nella directory dell’applicazione e installa le dipendenze come segue:

  1. cd myApp
  2. npm install

Per impostazione predefinita, le applicazioni create con express-generator girano sulla porta 3000, quindi è necessario assicurarsi che il firewall non blocchi la porta.

Per aprire la porta 3000, eseguire il seguente comando:

  1. sudo ufw allow 3000

Ora hai tutto il necessario per avviare la tua applicazione web. Per farlo, esegui il seguente comando:

  1. nodemon bin/www

Questo comando avvia l’applicazione sulla porta 3000. Puoi verificare se funziona puntando il tuo browser su http://l'indirizzo_ip_del_tuo_server:3000. Dovresti vedere qualcosa di simile a questo:

A questo punto, puoi avviare una seconda sessione SSH verso il tuo server per il resto di questo tutorial, lasciando l’applicazione web appena avviata in esecuzione nella sessione originale. Per il resto di questo articolo, la sessione SSH iniziale che attualmente esegue l’applicazione sarà chiamata Sessione A. Qualsiasi comando in Sessione A apparirà su uno sfondo blu scuro come questo:

  1. nodemon bin/www

Userai la nuova sessione SSH per eseguire comandi e modificare file. Questa sessione sarà chiamata Sessione B. Qualsiasi comando in Sessione B apparirà su uno sfondo blu chiaro come questo:

  1. cd ~/myApp

A meno che non sia diversamente specificato, eseguirai tutti i comandi rimanenti in Sessione B.

In questo passaggio, hai creato l’applicazione di base. Successivamente, la personalizzerai.

Passaggio 2 — Personalizzazione delle Variabili di Registro

Mentre l’applicazione predefinita creata da express-generator è un buon punto di partenza, è necessario personalizzare l’applicazione in modo che chiami il logger corretto quando necessario.

express-generator include il middleware di logging HTTP Morgan che verrà utilizzato per registrare i dati su tutte le richieste HTTP. Poiché Morgan supporta gli stream di output, si abbina bene al supporto degli stream integrato in Winston, consentendoti di consolidare i log dei dati delle richieste HTTP con qualsiasi altra cosa tu scelga di registrare con Winston.

Il boilerplate di express-generator utilizza la variabile logger quando si fa riferimento al pacchetto morgan. Poiché userai sia morgan che winston, che sono entrambi pacchetti di logging, può essere confusionario chiamare uno qualsiasi di essi logger. Per specificare quale variabile desideri, puoi modificare le dichiarazioni delle variabili modificando il file app.js.

Per aprire app.js per la modifica, utilizza nano o il tuo editor di testo preferito:

  1. nano ~/myApp/app.js

Trova la seguente riga vicino all’inizio del file:

~/myApp/app.js
...
var logger = require('morgan');
...

Cambia il nome della variabile da logger a morgan:

~/myApp/app.js
...
var morgan = require('morgan');
...

Questa modifica specifica che la variabile dichiarata morgan chiamerà il metodo require() collegato al logger di richiesta di Morgan.

Devi trovare dove altro è stata referenziata la variabile logger nel file e cambiarla in morgan. Dovrai anche cambiare il formato di log usato dal pacchetto morgan in combined, che è il formato di log standard di Apache e includerà informazioni utili nei log, come l’indirizzo IP remoto e l’intestazione della richiesta HTTP dell’agente utente.

Per farlo, trova la seguente linea:

~/myApp/app.js
...
app.use(logger('dev'));
...

Aggiornala con la seguente:

~/myApp/app.js
...
app.use(morgan('combined'));
...

Questi cambiamenti ti aiuteranno a capire quale pacchetto di log viene referenziato in un determinato momento dopo aver integrato la configurazione di Winston.

Quando hai finito, salva e chiudi il file.

Ora che la tua app è configurata, puoi iniziare a lavorare con Winston.

Passaggio 3 — Installazione e Configurazione di Winston

In questo passaggio, installerai e configurerai Winston. Esplorerai anche le opzioni di configurazione disponibili come parte del pacchetto winston e creerai un logger per registrare informazioni su un file e sulla console.

Installa winston con il seguente comando:

  1. cd ~/myApp
  2. npm install winston

È utile conservare tutti i file di configurazione di supporto o utility per le tue applicazioni in una directory speciale. Crea una cartella config che conterrà la configurazione di winston:

  1. mkdir ~/myApp/config

Successivamente, crea una cartella che conterrà i tuoi file di registro:

  1. mkdir ~/myApp/logs

Infine, installa app-root-path:

  1. npm install app-root-path --save

Il pacchetto app-root-path è utile quando si specificano percorsi in Node.js. Anche se questo pacchetto non è direttamente correlato a Winston, è utile per determinare i percorsi dei file in Node.js. Lo utilizzerai per specificare la posizione dei file di registro di Winston dalla radice del progetto e per evitare la brutta sintassi dei percorsi relativi.

Ora che la configurazione per la gestione del registro è in posizione, puoi definire le impostazioni. Crea e apri ~/myApp/config/winston.js per modificarlo:

  1. nano ~/myApp/config/winston.js

Il file winston.js conterrà la tua configurazione winston.

Successivamente, aggiungi il seguente codice per richiedere i pacchetti app-root-path e winston:

~/myApp/config/winston.js
const appRoot = require('app-root-path');
const winston = require('winston');

Con queste variabili in posizione, puoi definire le impostazioni di configurazione per i tuoi trasporti. I trasporti sono un concetto introdotto da Winston che si riferisce ai meccanismi di archiviazione/uscita utilizzati per i registri. Winston è dotato di quattro trasporti principali incorporati: Console, File, HTTP, e Stream.

Ti concentrerai sui trasporti console e file per questo tutorial. Il trasporto console registrerà le informazioni sulla console e il trasporto file registrerà le informazioni su un file specificato. Ogni definizione di trasporto può contenere impostazioni di configurazione, come dimensione del file, livelli di registro e formato del registro.

Ecco un breve riassunto delle impostazioni che utilizzerai per ogni trasporto:

  • livello: livello dei messaggi da registrare.
  • nomefile: il file da utilizzare per scrivere i dati di log.
  • gestisciEccezioni: cattura e registra le eccezioni non gestite.
  • dimensioneMax: dimensione massima del file di log, in byte, prima che venga creato un nuovo file.
  • numMaxFile: limite del numero di file creati quando la dimensione del file di log viene superata.
  • formato: come verrà formattato l’output del log.

I livelli di registrazione indicano la priorità del messaggio e sono rappresentati da un intero. Winston utilizza i livelli di registrazione di npm che sono prioritizzati da 0 a 6 (da più alto a più basso):

  • 0: errore
  • 1: avviso
  • 2: informazioni
  • 3: http
  • 4: dettagliato
  • 5: debug
  • 6: stravagante

Specificando un livello di registrazione per un particolare trasporto, verranno registrati tutti i messaggi a quel livello o superiore. Ad esempio, impostando un livello di informazioni, verranno registrati tutti i messaggi di livello errore, avviso, o informazioni.

I livelli di log vengono specificati durante la chiamata al logger, il che significa che è possibile eseguire il seguente comando per registrare un errore: logger.error('messaggio di errore di prova').

Ancora nel file di configurazione, aggiungi il seguente codice per definire le impostazioni di configurazione dei trasporti file e console nella configurazione di winston:

~/myApp/config/winston.js
...
// definire le impostazioni personalizzate per ogni trasporto (file, console)
const options = {
  file: {
    level: "info",
    filename: `${appRoot}/logs/app.log`,
    handleExceptions: true,
    maxsize: 5242880, // 5MB
    maxFiles: 5,
    format: winston.format.combine(
      winston.format.timestamp(),
      winston.format.json()
    ),
  },
  console: {
    level: "debug",
    handleExceptions: true,
    format: winston.format.combine(
      winston.format.colorize(),
      winston.format.simple()
    ),
  },
};

Successivamente, aggiungi il seguente codice per istanziare un nuovo logger winston con trasporti file e console utilizzando le proprietà definite nella variabile options:

~/myApp/config/winston.js
...
// istanzia un nuovo Winston Logger con le impostazioni definite sopra
const logger = winston.createLogger({
  transports: [
    new winston.transports.File(options.file),
    new winston.transports.Console(options.console),
  ],
  exitOnError: false, // non uscire in caso di eccezioni gestite
});

Per impostazione predefinita, morgan invia l’output solo alla console, quindi definirai una funzione di stream che sarà in grado di inserire l’output generato da morgan nei file di log di winston. Utilizzerai il livello di log info per raccogliere l’output da entrambi i trasporti (file e console). Aggiungi il seguente codice al file di configurazione:

~/myApp/config/winston.js
...
// crea un oggetto stream con una funzione 'write' che verrà utilizzata da `morgan`
logger.stream = {
  write: function(message, encoding) {
    // utilizza il livello di log 'info' in modo che l'output venga raccolto da entrambi
    // i trasporti (file e console)
    logger.info(message);
  },
};

Infine, aggiungi il codice qui sotto per esportare il logger in modo che possa essere utilizzato in altre parti dell’applicazione:

~/myApp/config/winston.js
...
module.exports = logger;

Il file di configurazione completato di winston ora avrà questo aspetto:

~/myApp/config/winston.js
const appRoot = require("app-root-path");
const winston = require("winston");

// definire le impostazioni personalizzate per ogni trasporto (file, console)
const options = {
  file: {
    level: "info",
    filename: `${appRoot}/logs/app.log`,
    handleExceptions: true,
    maxsize: 5242880, // 5MB
    maxFiles: 5,
    format: winston.format.combine(
      winston.format.timestamp(),
      winston.format.json()
    ),
  },
  console: {
    level: "debug",
    handleExceptions: true,
    format: winston.format.combine(
      winston.format.colorize(),
      winston.format.simple()
    ),
  },
};

// istanziare un nuovo registro Winston con le impostazioni definite sopra
const logger = winston.createLogger({
  transports: [
    new winston.transports.File(options.file),
    new winston.transports.Console(options.console),
  ],
  exitOnError: false, // non uscire in caso di eccezioni gestite
});

// creare un oggetto stream con una funzione 'write' che verrà utilizzata da `morgan`
logger.stream = {
  write: function (message, encoding) {
    // utilizzare il livello di log 'info' in modo che l'output venga raccolto sia dai
    // trasporti (file e console)
    logger.info(message);
  },
};

module.exports = logger;

Salvare e chiudere il file.

Ora hai configurato il registro, ma la tua applicazione non ne è ancora consapevole, né sa come usarlo, quindi è necessario integrare il registro con l’applicazione.

Passaggio 4 — Integrazione di Winston con l’applicazione

Per far funzionare il tuo registro con l’applicazione, è necessario far sì che express ne sia consapevole. Hai visto nel Passaggio 2 che la configurazione di express si trova in app.js, quindi puoi importare il tuo registro in questo file.

Aprire il file per modificarlo:

  1. nano ~/myApp/app.js

Aggiungere una dichiarazione di variabile winston vicino all’inizio del file insieme agli altri statement require:

~/myApp/app.js
...
var winston = require('./config/winston');
...

Il primo posto in cui userai winston è con morgan. Ancora in app.js, trova la seguente riga:

~/myApp/app.js
...
app.use(morgan('combined'));
...

Aggiornala per includere l’opzione stream:

~/myApp/app.js
...
app.use(morgan('combined', { stream: winston.stream }));
...

Qui, imposti l’opzione stream sull’interfaccia di stream che hai creato come parte della configurazione di winston.

Salva e chiudi il file.

In questo passaggio, hai configurato la tua applicazione Express per funzionare con Winston. Successivamente, rivedrai i dati del registro.

Passaggio 5 — Accesso ai dati del registro e registrazione di messaggi di registro personalizzati

Ora che l’applicazione è stata configurata, sei pronto per vedere alcuni dati di registro. In questo passaggio, esaminerai le voci di registro e aggiornerai le impostazioni con un esempio di messaggio di registro personalizzato.

Se ricarichi la pagina nel browser web, dovresti vedere qualcosa di simile all’output seguente nella console della sessione SSH A:

Output
[nodemon] restarting due to changes... [nodemon] starting `node bin/www` info: ::1 - - [25/Apr/2022:18:10:55 +0000] "GET / HTTP/1.1" 200 170 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36" info: ::1 - - [25/Apr/2022:18:10:55 +0000] "GET /stylesheets/style.css HTTP/1.1" 304 - "http://localhost:3000/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36"

Ci sono due voci di registro qui: la prima per la richiesta alla pagina HTML; la seconda per il foglio di stile associato. Poiché ogni trasporto è configurato per gestire i dati di registro di livello info, dovresti vedere informazioni simili anche nel trasporto del file situato in ~/myApp/logs/app.log.

Per visualizzare il contenuto del file di registro, esegui il seguente comando:

  1. tail ~/myApp/logs/app.log

Il comando tail visualizzerà le ultime parti del file nel tuo terminale.

Dovresti vedere qualcosa di simile a quanto segue:

{"level":"info","message":"::1 - - [25/Apr/2022:18:10:55 +0000] \"GET / HTTP/1.1\" 304 - \"-\" \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36\"\n","timestamp":"2022-04-25T18:10:55.573Z"}
{"level":"info","message":"::1 - - [25/Apr/2022:18:10:55 +0000] \"GET /stylesheets/style.css HTTP/1.1\" 304 - \"http://localhost:3000/\" \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36\"\n","timestamp":"2022-04-25T18:10:55.588Z"}

L’output nel file di trasporto sarà scritto come un oggetto JSON poiché hai utilizzato winston.format.json() nell’opzione format per la configurazione del trasporto del file. Puoi saperne di più su JSON in Un’introduzione a JSON.

Fino ad ora, il tuo registro sta registrando solo le richieste HTTP e i dati correlati. Queste informazioni sono essenziali da avere nei tuoi registri.

In futuro, potresti voler registrare messaggi di log personalizzati, ad esempio per registrare errori o profilare le prestazioni delle query al database. Come esempio, chiamerai il registro dalla route del gestore degli errori. Per impostazione predefinita, il pacchetto express-generator include già una route del gestore degli errori 404 e 500, quindi lavorerai con quella.

Apri il file ~/myApp/app.js:

  1. nano ~/myApp/app.js

Trova il blocco di codice alla fine del file che assomiglia a questo:

~/myApp/app.js
...
// gestore degli errori
app.use(function(err, req, res, next) {
  // impostare le variabili locali, fornendo solo gli errori in sviluppo
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // renderizzare la pagina di errore
  res.status(err.status || 500);
  res.render('error');
});
...

Questa sezione è la route finale per la gestione degli errori che alla fine invierà una risposta di errore al client. Poiché tutti gli errori lato server passeranno attraverso questa route, è un buon posto per includere il registro di winston.

Poiché stai ora gestendo gli errori, desideri utilizzare il livello di registro error. Entrambi i trasporti sono configurati per registrare messaggi di livello error, quindi dovresti vedere l’output nei log della console e del file.

Puoi includere qualsiasi informazione desideri nel registro, inclusi dettagli come:

  • err.status: Il codice di stato di errore HTTP. Se non è già presente, utilizzare il valore predefinito 500.
  • err.message: Dettagli sull’errore.
  • req.originalUrl: L’URL richiesto.
  • req.path: La parte di percorso dell’URL della richiesta.
  • req.method: Metodo HTTP della richiesta (GET, POST, PUT, ecc.).
  • req.ip: Indirizzo IP remoto della richiesta.

Aggiorna il gestore degli errori per includere il registro winston:

~/myApp/app.js
...
// gestore degli errori
app.use(function(err, req, res, next) {
  // impostare le variabili locali, fornendo solo l'errore nello sviluppo
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // includere il registro winston
  winston.error(
    `${err.status || 500} - ${err.message} - ${req.originalUrl} - ${req.method} - ${req.ip}`
  );

  // renderizzare la pagina di errore
  res.status(err.status || 500);
  res.render('error');
});
...

Salva e chiudi il file.

Per testare questo processo, prova ad accedere a una pagina inesistente nel tuo progetto. Accedere a una pagina inesistente genererà un errore 404. Nel tuo browser web, prova a caricare l’URL seguente: http://your_server_ip:3000/foo. Grazie al boilerplate creato da express-generator, l’applicazione è configurata per rispondere a tale errore.

Il tuo browser mostrerà un messaggio di errore come questo:

Quando guardi la console nella sessione SSH A, dovrebbe esserci un’entrata nel registro per l’errore. Grazie al formato colorize applicato, dovrebbe essere facile individuarlo:

Output
[nodemon] starting `node bin/www` error: 404 - Not Found - /foo - GET - ::1 info: ::1 - - [25/Apr/2022:18:08:33 +0000] "GET /foo HTTP/1.1" 404 982 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36" info: ::1 - - [25/Apr/2022:18:08:33 +0000] "GET /stylesheets/style.css HTTP/1.1" 304 - "http://localhost:3000/foo" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36"

Per quanto riguarda il registro di file, eseguire nuovamente il comando tail dovrebbe mostrarti i nuovi record di registro:

  1. tail ~/myApp/logs/app.log

Vedrai un messaggio simile al seguente:

{"level":"error","message":"404 - Not Found - /foo - GET - ::1","timestamp":"2022-04-25T18:08:33.508Z"}

Il messaggio di errore include tutti i dati che hai specificamente istruito winston a registrare come parte del gestore degli errori. Queste informazioni includeranno lo stato dell’errore (404 – Non Trovato), l’URL richiesto (localhost/foo), il metodo di richiesta (GET), l’indirizzo IP che ha effettuato la richiesta e l’orario in cui è stata effettuata la richiesta.

Conclusione

In questo tutorial, hai costruito una semplice applicazione web Node.js e integrato una soluzione di registrazione di Winston che funzionerà come uno strumento efficace per fornire informazioni sulle prestazioni dell’applicazione.

Puoi fare molto di più per creare soluzioni di registrazione robuste per le tue applicazioni, specialmente quando le tue esigenze diventano più complesse. Per saperne di più sui trasporti di Winston, consulta la Documentazione sui Trasporti di Winston. Per creare i tuoi trasporti personalizzati, consulta Aggiunta di Trasporti Personalizzati. Per creare un endpoint HTTP da utilizzare con il trasporto core HTTP, consulta winstond. Per utilizzare Winston come strumento di profilazione, consulta Profilazione.

Source:
https://www.digitalocean.com/community/tutorials/how-to-use-winston-to-log-node-js-applications-on-ubuntu-20-04