In questo articolo vedremo come implementare il log degli accessi in ExpressJS con MongoDB.

I dati che ci interessano sono:

  1. URL della pagina.
  2. Indirizzo IP.
  3. Il browser usato.
  4. La data dell'accesso.

Possiamo definire uno schema per i documenti da inserire in MongoDB con mongoose.

'use strict';

const mongoose = require('mongoose');

const DocSchema  = mongoose.Schema;

const LogSchema   = new DocSchema({
   url: String,
   ip: String,
   browser: String,
   date: Date
},{collection: 'logs'});

module.exports = mongoose.model('logs', LogSchema);

Poiché reperire l'indirizzo IP remoto di un client è un'operazione che richiede alcuni passaggi a seconda del fatto se il nostro sito usa un reverse proxy (ad esempio nginx) o meno, possiamo racchiudere la logica della nostra implementazione in una apposita classe.

class Logger {

    static isValidUserAgent(str) {
        const ua = (typeof str === 'string' && str.length > 0) ? str.toLowerCase() : 'none';
        const filter = /chrome|firefox|safari|edge|msie|mobile/i;
        return filter.test(ua);
    }

    static isValidMethod(req) {
        const method = req.method.toLowerCase();
        return (method === 'get');
    }

    static log(request, collection) {
        if( this.isValidUserAgent(request.header('User-Agent')) && this.isValidMethod(request)) {
            const realIP = request.get('x-real-ip');
            let ip = '';
            if (realIP) {
                ip = realIP;
            } else {
                ip = request.socket.remoteAddress;
            }
            const data = {
                url: request.originalUrl,
                ip: ip,
                browser: request.header('User-Agent'),
                date: new Date()
            };
            const logEntry = new collection(data);
            logEntry.save();
        }
    }
}

module.exports = Logger;

La classe effettua due tipi di validazione: uno sul metodo HTTP, che deve essere GET, e l'altro sul browser usato dal client la cui stringa dell'header HTTP User-Agent può contenere solo specifiche stringhe. In questo modo evitiamo di includere nei nostri record delle richieste provenienti ad esempio dai bot.

L'individuazione dell'indirizzo IP tiene conto della presenza di un reverse proxy sulla nostra applicazione. Si tenga presente comunque che se si usa un reverse proxy, questo va configurato in modo da inoltrare (forward) l'indirizzo IP originario alla nostra applicazione. In caso contrario si otterrà come indirizzo IP l'indirizzo di loopback del server.

A questo punto possiamo usare la classe nelle nostre route.

'use strict';

const app = require('express')();
const mongoose = require('mongoose');
const Logger = require('./classes/Logger');
const logs = require('./models/logs');

mongoose.connect('mongodb://localhost:27017/blog', {useNewUrlParser: true});

app.get('/blog/:slug', (req, res) => {
        Logger.log(req, logs);
});

app.listen(3000);

Usare una funzione di middleware in questo caso è una soluzione che ci vedrebbe costretti a filtrare tutti quei percorsi che non ci interessa monitorare, come ad esempio le risorse statiche. Inoltre usare un middleware in questo caso penalizzerebbe il sito a livello di performance, in quanto il salvataggio su MongoDB rallenterebbe l'accesso al codice della route.