148 votes

Analyser des fichiers journaux volumineux en Node.js - lire ligne par ligne

Je dois analyser des fichiers journaux volumineux (5 à 10 Go) en Javascript/Node.js (j'utilise Cube).

La ligne de conduite ressemble à quelque chose comme :

10:00:43.343423 I'm a friendly log message. There are 5 cats, and 7 dogs. We are in state "SUCCESS".

Nous devons lire chaque ligne, faire un peu d'analyse syntaxique (par exemple, enlever les caractères 5 , 7 et SUCCESS ), puis de pomper ces données dans Cube ( https://github.com/square/cube ) en utilisant leur client JS.

Tout d'abord, quelle est la manière canonique dans Node de lire un fichier, ligne par ligne ?

Cette question semble être assez courante sur Internet :

Beaucoup de réponses semblent pointer vers un tas de modules tiers :

Cependant, cela semble être une tâche assez basique - il y a sûrement un moyen simple dans la stdlib pour lire un fichier texte, ligne par ligne ?

Ensuite, je dois traiter chaque ligne (par exemple, convertir l'horodatage en un objet Date, et extraire les champs utiles).

Quelle est la meilleure façon de le faire, en maximisant le débit ? Existe-t-il un moyen de ne pas bloquer la lecture de chaque ligne ou l'envoi au Cube ?

Troisièmement, je suppose que l'utilisation des séparations de chaînes de caractères et de l'équivalent JS de contains (IndexOf != -1 ?) sera beaucoup plus rapide que les regex ? Quelqu'un a-t-il une grande expérience de l'analyse de quantités massives de données textuelles dans Node.js ?

A la vôtre, Victor

8voto

Kris Roofe Points 13087

En plus de lire le gros fichier ligne par ligne, vous pouvez également le lire morceau par morceau. Pour en savoir plus, consultez cet article

var offset = 0;
var chunkSize = 2048;
var chunkBuffer = new Buffer(chunkSize);
var fp = fs.openSync('filepath', 'r');
var bytesRead = 0;
while(bytesRead = fs.readSync(fp, chunkBuffer, 0, chunkSize, offset)) {
    offset += bytesRead;
    var str = chunkBuffer.slice(0, bytesRead).toString();
    var arr = str.split('\n');

    if(bytesRead = chunkSize) {
        // the last item of the arr may be not a full line, leave it to the next chunk
        offset -= arr.pop().length;
    }
    lines.push(arr);
}
console.log(lines);

4voto

user1278316 Points 61

J'ai eu le même problème. Après avoir comparé plusieurs modules qui semblent avoir cette fonctionnalité, j'ai décidé de le faire moi-même, c'est plus simple que je ne le pensais.

gist : https://gist.github.com/deemstone/8279565

var fetchBlock = lineByline(filepath, onEnd);
fetchBlock(function(lines, start){ ... });  //lines{array} start{int} lines[0] No.

Il couvre le dossier ouvert dans une fermeture, qui fetchBlock() retourné récupère un bloc du fichier, puis le divise en tableau (il traitera le segment de la dernière récupération).

J'ai défini la taille du bloc à 1024 pour chaque opération de lecture. Cela peut avoir des bogues, mais la logique du code est évidente, essayez-le vous-même.

4voto

SridharKritha Points 346

Lecture / écriture de fichiers l'utilisation de stream avec les modules nodejs natifs ( fs, readline ) :

const fs = require('fs');
const readline = require('readline');

const rl = readline.createInterface({
                                       input:  fs.createReadStream('input.json'),
                                       output: fs.createWriteStream('output.json')
                                    });

rl.on('line', function(line) {
    console.log(line);

    // Do any 'line' processing if you want and then write to the output file
    this.output.write(`${line}\n`);
});

rl.on('close', function() {
    console.log(`Created "${this.output.path}"`);
});

3voto

Benvorth Points 4301

Sur la base de este réponse aux questions J'ai implémenté une classe que vous pouvez utiliser pour lire un fichier de manière synchrone ligne par ligne avec fs.readSync() . Vous pouvez faire cette "pause" et "reprise" en utilisant un Q promesse ( jQuery semble nécessiter un DOM et ne peut donc pas être exécuté avec nodejs ) :

var fs = require('fs');
var Q = require('q');

var lr = new LineReader(filenameToLoad);
lr.open();

var promise;
workOnLine = function () {
    var line = lr.readNextLine();
    promise = complexLineTransformation(line).then(
        function() {console.log('ok');workOnLine();},
        function() {console.log('error');}
    );
}
workOnLine();

complexLineTransformation = function (line) {
    var deferred = Q.defer();
    // ... async call goes here, in callback: deferred.resolve('done ok'); or deferred.reject(new Error(error));
    return deferred.promise;
}

function LineReader (filename) {      
  this.moreLinesAvailable = true;
  this.fd = undefined;
  this.bufferSize = 1024*1024;
  this.buffer = new Buffer(this.bufferSize);
  this.leftOver = '';

  this.read = undefined;
  this.idxStart = undefined;
  this.idx = undefined;

  this.lineNumber = 0;

  this._bundleOfLines = [];

  this.open = function() {
    this.fd = fs.openSync(filename, 'r');
  };

  this.readNextLine = function () {
    if (this._bundleOfLines.length === 0) {
      this._readNextBundleOfLines();
    }
    this.lineNumber++;
    var lineToReturn = this._bundleOfLines[0];
    this._bundleOfLines.splice(0, 1); // remove first element (pos, howmany)
    return lineToReturn;
  };

  this.getLineNumber = function() {
    return this.lineNumber;
  };

  this._readNextBundleOfLines = function() {
    var line = "";
    while ((this.read = fs.readSync(this.fd, this.buffer, 0, this.bufferSize, null)) !== 0) { // read next bytes until end of file
      this.leftOver += this.buffer.toString('utf8', 0, this.read); // append to leftOver
      this.idxStart = 0
      while ((this.idx = this.leftOver.indexOf("\n", this.idxStart)) !== -1) { // as long as there is a newline-char in leftOver
        line = this.leftOver.substring(this.idxStart, this.idx);
        this._bundleOfLines.push(line);        
        this.idxStart = this.idx + 1;
      }
      this.leftOver = this.leftOver.substring(this.idxStart);
      if (line !== "") {
        break;
      }
    }
  }; 
}

2voto

hereandnow78 Points 4914

Node-byline utilise des flux, donc je préfèrerais cette solution pour vos gros fichiers.

pour vos conversions de date, j'utiliserais moment.js .

pour maximiser votre débit, vous pouvez envisager d'utiliser un cluster logiciel. il existe quelques modules sympathiques qui enveloppent assez bien le module cluster natif de node. j'aime bien cluster-master d'isaacs. Par exemple, vous pouvez créer un cluster de x travailleurs qui calculent tous un fichier.

pour évaluer les splits par rapport aux regex, utilisez benchmark.js . je ne l'ai pas testé jusqu'à présent. benchmark.js est disponible en tant que node-module.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X