UNPKG

4.58 kBJavaScriptView Raw
1/**
2 * @copyright Copyright (c) 2019 Maxim Khorin <maksimovichu@gmail.com>
3 */
4'use strict';
5
6const Base = require('./LogStore');
7
8module.exports = class FileLogStore extends Base {
9
10 static parseFilename (name) {
11 if (typeof name !== 'string') {
12 return null;
13 }
14 name = FileHelper.getBasename(name);
15 const index = name.lastIndexOf('-');
16 return index !== -1
17 ? [name.substring(0, index), name.substring(index + 1)]
18 : [name];
19 }
20
21 constructor (config) {
22 super({
23 basePath: 'log',
24 observePeriod: 30, // seconds, 0 - off
25 maxFileSize: 2, // megabytes
26 maxFiles: 1,
27 ...config
28 });
29 this.basePath = this.module.resolvePath(this.basePath);
30 this.file = this.getFile();
31 this.fullFile = this.getFile('full');
32 this.maxFileSize *= 1024 * 1024; // megabytes to bytes
33
34 fs.mkdirSync(this.basePath, {recursive: true});
35 this.openFile();
36 }
37
38 init () {
39 if (this.observePeriod) {
40 this.observe();
41 }
42 }
43
44 getFile (suffix) {
45 return path.join(this.basePath, this.name + (suffix ? `-${suffix}` : '') +'.log');
46 }
47
48 openFile () {
49 this._fd = fs.openSync(this.file, 'a');
50 }
51
52 save () {
53 fs.write(this._fd, this.format(...arguments), err => {
54 if (err) {
55 console.error(this.wrapClassMessage(`save`), err);
56 }
57 });
58 }
59
60 format (type, text, data) {
61 if (text instanceof Exception) {
62 text = text.toString();
63 } else if (text instanceof Error) {
64 text = `${text.message} ${text.stack}`;
65 }
66 text = `${new Date().toISOString()} ${type.toUpperCase()} ${text}`;
67 if (data) {
68 text = `${text} ${data}`;
69 }
70 return text + os.EOL;
71 }
72
73 observe () {
74 setTimeout(async () => {
75 await this.checkout();
76 this.observe();
77 }, this.observePeriod * 1000);
78 }
79
80 async checkout () {
81 try {
82 const {size} = await PromiseHelper.promise(fs.fstat.bind(fs, this._fd));
83 if (size > this.maxFileSize) {
84 await this.rotate();
85 }
86 } catch (err) {
87 this.log('error', this.wrapClassMessage('checkout'), err);
88 }
89 }
90
91 async rotate () {
92 await fs.promises.rename(this.file, this.fullFile);
93 fs.closeSync(this._fd);
94 this.openFile();
95 const files = await this.getSortedFiles();
96 await this.deleteExcessFiles(files);
97 for (let i = files.length - 1; i >= 0; --i) {
98 await fs.promises.rename(files[i], this.getFile(i + 1));
99 }
100 this.log('info', `Rotate success: ${this.name}`);
101 }
102
103 async deleteExcessFiles (files) {
104 if (files.length > this.maxFiles) {
105 const unlinks = files.splice(this.maxFiles, files.length);
106 for (const file of unlinks) {
107 await fs.promises.unlink(file);
108 }
109 }
110 }
111
112 async getSortedFiles () {
113 const baseName = path.basename(this.file);
114 const items = [];
115 for (let name of await fs.promises.readdir(this.basePath)) {
116 if (name.indexOf(this.name) === 0 && name !== baseName) {
117 const file = path.join(this.basePath, name);
118 const stat = await fs.promises.stat(file);
119 const time = stat.mtime.getTime();
120 if (stat.isFile()) {
121 items.push({file, time});
122 }
123 }
124 }
125 return items.sort((a, b) => b.time - a.time).map(item => item.file);
126 }
127
128 async getFiles () {
129 const items = [];
130 for (let name of await fs.promises.readdir(this.basePath)) {
131 if (name.indexOf(this.name) === 0) {
132 const file = path.join(this.basePath, name);
133 const stat = await fs.promises.stat(file);
134 if (stat.isFile()) {
135 items.push({name, file, stat});
136 }
137 }
138 }
139 return items;
140 }
141};
142
143const fs = require('fs');
144const os = require('os');
145const path = require('path');
146const Exception = require('../error/Exception');
147const FileHelper = require('../helper/FileHelper');
148const PromiseHelper = require('../helper/PromiseHelper');
\No newline at end of file