Inspired by @gerard 's answer, and I want to provide a controlled way of reading chunk by chunk.
I have an electron app, which read multiple large log files chunk by chunk on user's request, the next chunk will only be requested when user asking for it.
Here is my LogReader class
// A singleton class, used to read log chunk by chunk
import * as fs from 'fs';
import { logDirPath } from './mainConfig';
import * as path from 'path';
type ICallback = (data: string) => Promise<void> | void;
export default class LogReader {
filenames: string[];
readstreams: fs.ReadStream[];
chunkSize: number;
lineNumber: number;
data: string;
static instance: LogReader;
private constructor(chunkSize = 10240) {
this.chunkSize = chunkSize || 10240; // default to 10kB per chunk
this.filenames = [];
// collect all log files and sort from latest to oldest
fs.readdirSync(logDirPath).forEach((file) => {
if (file.endsWith('.log')) {
this.filenames.push(path.join(logDirPath, file));
}
});
this.filenames = this.filenames.sort().reverse();
this.lineNumber = 0;
}
static getInstance() {
if (!this.instance) {
this.instance = new LogReader();
}
return this.instance;
}
// read a chunk from a log file
read(fileIndex: number, chunkIndex: number, cb: ICallback) {
// file index out of range, return "end of all files"
if (fileIndex >= this.filenames.length) {
cb('EOAF');
return;
}
const chunkSize = this.chunkSize;
fs.createReadStream(this.filenames[fileIndex], {
highWaterMark: chunkSize, // 1kb per read
start: chunkIndex * chunkSize, // start byte of this chunk
end: (chunkIndex + 1) * chunkSize - 1, // end byte of this chunk (end index was included, so minus 1)
})
.on('data', (data) => {
cb(data.toString());
})
.on('error', (e) => {
console.error('Error while reading file.');
console.error(e);
cb('EOF');
})
.on('end', () => {
console.log('Read entire chunk.');
cb('EOF');
});
}
}
Then to read chunk by chunk, the main process just need to call:
const readLogChunk = (fileIndex: number, chunkIndex: number): Promise<string> => {
console.log(`=== load log chunk ${fileIndex}: ${chunkIndex}====`);
return new Promise((resolve) => {
LogReader.getInstance().read(fileIndex, chunkIndex, (data) => resolve(data));
});
};
Keep increment chunkIndex to read chunk by chunk
When `EOF` is returned, means one file finished, just increment the fileIndex,
When `EOAF` is returned, means all files are read, just stop.