web-dev-qa-db-fra.com

Analyser d’énormes fichiers journaux dans Node.js - lire ligne par ligne

J'ai besoin d'analyser de gros fichiers journaux (5-10 Go) en Javascript/Node.js (j'utilise un cube).

La logline 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, effectuer une analyse syntaxique (par exemple effacer 5, 7 et SUCCESS), puis pomper ces données dans un cube ( https://github.com/square/cube ) à l'aide de leur client JS.

Premièrement, quelle est la manière canonique dans Node de lire dans un fichier, ligne par ligne?

Cela semble être une question assez courante en ligne:

Un grand nombre de réponses semblent pointer vers des modules tiers:

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

Deuxièmement, je dois ensuite traiter chaque ligne (par exemple, convertir l’horodatage en un objet Date et extraire des champs utiles).

Quelle est la meilleure façon de faire cela, en maximisant le débit? Existe-t-il un moyen qui ne bloque ni la lecture de chaque ligne ni son envoi à Cube?

Troisièmement - je suppose que l’utilisation des divisions de chaîne et l’équivalent JS de conte (IndexOf! = -1?) Seront beaucoup plus rapides que les expressions rationnelles? Quelqu'un a-t-il beaucoup d'expérience dans l'analyse de grandes quantités de données texte dans Node.js?

A bientôt, Victor

94
victorhooi

J'ai cherché une solution pour analyser de très gros fichiers (gbs) ligne par ligne à l'aide d'un flux. Toutes les bibliothèques tierces et les exemples ne répondaient pas à mes besoins car ils traitaient les fichiers pas ligne par ligne (comme 1, 2, 3, 4 ..) ou lisaient le fichier entier en mémoire.

La solution suivante permet d’analyser des fichiers très volumineux, ligne par ligne, à l’aide de stream & pipe. Pour les tests, j'ai utilisé un fichier de 2,1 gb avec 17 000 000 enregistrements. L'utilisation du bélier n'a pas dépassé 60 mb.

var fs = require('fs')
    , es = require('event-stream');

var lineNr = 0;

var s = fs.createReadStream('very-large-file.csv')
    .pipe(es.split())
    .pipe(es.mapSync(function(line){

        // pause the readstream
        s.pause();

        lineNr += 1;

        // process line here and call s.resume() when rdy
        // function below was for logging memory usage
        logMemoryUsage(lineNr);

        // resume the readstream, possibly from a callback
        s.resume();
    })
    .on('error', function(err){
        console.log('Error while reading file.', err);
    })
    .on('end', function(){
        console.log('Read entire file.')
    })
);

enter image description here

S'il vous plaît laissez-moi savoir comment ça se passe!

159
Gerard

Vous pouvez utiliser le package readline intégré, voir docs ici . J'utilise stream pour créer un nouveau flux de sortie.

var fs = require('fs'),
    readline = require('readline'),
    stream = require('stream');

var instream = fs.createReadStream('/path/to/file');
var outstream = new stream;
outstream.readable = true;
outstream.writable = true;

var rl = readline.createInterface({
    input: instream,
    output: outstream,
    terminal: false
});

rl.on('line', function(line) {
    console.log(line);
    //Do your stuff ...
    //Then write to outstream
    rl.write(cubestuff);
});

Les gros fichiers prendront un certain temps à traiter. Dites si cela fonctionne.

62
user568109

J'ai vraiment aimé @gerard answer qui mérite en fait d'être la bonne réponse ici. J'ai fait quelques améliorations:

  • Le code est dans une classe (modulaire)
  • L'analyse est incluse
  • La possibilité de reprendre est donnée à l'extérieur au cas où un travail asynchrone serait chaîné à la lecture du fichier CSV, comme l'insertion dans la base de données ou une requête HTTP.
  • Lecture en morceaux/tailles de lots que Utilisateur peut déclarer. Je me suis également occupé de l'encodage dans le flux, au cas oùvous avez des fichiers en encodage différent.

Voici le code:

'use strict'

const fs = require('fs'),
    util = require('util'),
    stream = require('stream'),
    es = require('event-stream'),
    parse = require("csv-parse"),
    iconv = require('iconv-lite');

class CSVReader {
  constructor(filename, batchSize, columns) {
    this.reader = fs.createReadStream(filename).pipe(iconv.decodeStream('utf8'))
    this.batchSize = batchSize || 1000
    this.lineNumber = 0
    this.data = []
    this.parseOptions = {delimiter: '\t', columns: true, escape: '/', relax: true}
  }

  read(callback) {
    this.reader
      .pipe(es.split())
      .pipe(es.mapSync(line => {
        ++this.lineNumber

        parse(line, this.parseOptions, (err, d) => {
          this.data.Push(d[0])
        })

        if (this.lineNumber % this.batchSize === 0) {
          callback(this.data)
        }
      })
      .on('error', function(){
          console.log('Error while reading file.')
      })
      .on('end', function(){
          console.log('Read entirefile.')
      }))
  }

  continue () {
    this.data = []
    this.reader.resume()
  }
}

module.exports = CSVReader

Donc, fondamentalement, voici comment vous allez l'utiliser:

let reader = CSVReader('path_to_file.csv')
reader.read(() => reader.continue())

J'ai testé cela avec un fichier CSV de 35 Go et cela a fonctionné pour moi. C'est pourquoi j'ai choisi de le construire sur la réponse de @gerard , les retours sont les bienvenus. 

24
ambodi

J'ai utilisé https://www.npmjs.com/package/line-by-line pour lire plus de 1 000 000 lignes d'un fichier texte. Dans ce cas, la capacité occupée de RAM était d'environ 50 à 60 mégaoctets.

    const LineByLineReader = require('line-by-line'),
    lr = new LineByLineReader('big_file.txt');

    lr.on('error', function (err) {
         // 'err' contains error object
    });

    lr.on('line', function (line) {
        // pause emitting of lines...
        lr.pause();

        // ...do your asynchronous line processing..
        setTimeout(function () {
            // ...and continue emitting lines.
            lr.resume();
        }, 100);
    });

    lr.on('end', function () {
         // All lines are read, file is closed now.
    });
10
Eugene Ilyushin

En plus de lire le gros fichier ligne par ligne, vous pouvez également le lire morceau par morceau. Pour plus d'informations, reportez-vous à 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);
4
Kris Roofe

J'ai eu le même problème encore. 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 fichier ouvert dans une fermeture, que fetchBlock() retourné va chercher un bloc dans le fichier, end split en tableau (traitera le segment du dernier extrait) 

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

2
deemstone

J'ai créé un module de nœud pour lire de gros fichiers de manière asynchrone du texte ou JSON .

var fs = require('fs')
, util = require('util')
, stream = require('stream')
, es = require('event-stream');

module.exports = FileReader;

function FileReader(){

}

FileReader.prototype.read = function(pathToFile, callback){
    var returnTxt = '';
    var s = fs.createReadStream(pathToFile)
    .pipe(es.split())
    .pipe(es.mapSync(function(line){

        // pause the readstream
        s.pause();

        //console.log('reading line: '+line);
        returnTxt += line;        

        // resume the readstream, possibly from a callback
        s.resume();
    })
    .on('error', function(){
        console.log('Error while reading file.');
    })
    .on('end', function(){
        console.log('Read entire file.');
        callback(returnTxt);
    })
);
};

FileReader.prototype.readJSON = function(pathToFile, callback){
    try{
        this.read(pathToFile, function(txt){callback(JSON.parse(txt));});
    }
    catch(err){
        throw new Error('json file is not valid! '+err.stack);
    }
};

Sauvegardez simplement le fichier sous le nom file-reader.js et utilisez-le comme ceci:

var FileReader = require('./file-reader');
var fileReader = new FileReader();
fileReader.readJSON(__dirname + '/largeFile.json', function(jsonObj){/*callback logic here*/});
1
Eyal Zoref

node-byline utilise des flux, je préférerais donc celui-ci pour vos gros fichiers. 

pour vos conversions de date, je voudrais utiliser moment.js

pour maximiser votre débit, vous pouvez envisager d’utiliser un cluster de logiciels. Il existe quelques Nice-modules qui encapsulent assez bien le module cluster natif-nœud. j'aime cluster-master d'isaacs. par exemple. vous pouvez créer un cluster de x travailleurs qui calculent tous un fichier. 

pour effectuer une analyse comparative entre les divisions et les expressions rationnelles, utilisez benchmark.js . Je n'ai pas testé jusqu'à maintenant. benchmark.js est disponible en tant que module de nœud

1
hereandnow78

La documentation Node.js offre un exemple très élégant utilisant le module Readline.

Exemple: Lire le flux de fichiers ligne par ligne

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

const rl = readline.createInterface({
    input: fs.createReadStream('sample.txt'),
    crlfDelay: Infinity
});

rl.on('line', (line) => {
    console.log(`Line from file: ${line}`);
});

Remarque: nous utilisons l’option crlfDelay pour reconnaître toutes les instances de CR LF ('\ r\n') sous la forme d’un saut de ligne.

0
Jaime Gómez
import * as csv from 'fast-csv';
import * as fs from 'fs';
interface Row {
  [s: string]: string;
}
type RowCallBack = (data: Row, index: number) => object;
export class CSVReader {
  protected file: string;
  protected csvOptions = {
    delimiter: ',',
    headers: true,
    ignoreEmpty: true,
    trim: true
  };
  constructor(file: string, csvOptions = {}) {
    if (!fs.existsSync(file)) {
      throw new Error(`File ${file} not found.`);
    }
    this.file = file;
    this.csvOptions = Object.assign({}, this.csvOptions, csvOptions);
  }
  public read(callback: RowCallBack): Promise < Array < object >> {
    return new Promise < Array < object >> (resolve => {
      const readStream = fs.createReadStream(this.file);
      const results: Array < any > = [];
      let index = 0;
      const csvStream = csv.parse(this.csvOptions).on('data', async (data: Row) => {
        index++;
        results.Push(await callback(data, index));
      }).on('error', (err: Error) => {
        console.error(err.message);
        throw err;
      }).on('end', () => {
        resolve(results);
      });
      readStream.pipe(csvStream);
    });
  }
}
import { CSVReader } from '../src/helpers/CSVReader';
(async () => {
  const reader = new CSVReader('./database/migrations/csv/users.csv');
  const users = await reader.read(async data => {
    return {
      username: data.username,
      name: data.name,
      email: data.email,
      cellPhone: data.cell_phone,
      homePhone: data.home_phone,
      roleId: data.role_id,
      description: data.description,
      state: data.state,
    };
  });
  console.log(users);
})();
0
Raza

Sur la base de this 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 "reprendre" en utilisant une promesse Q (jQuery semble nécessiter un DOM, vous ne pouvez donc pas l'exécuter 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;
      }
    }
  }; 
}
0
Benvorth