@abtnode/util 1.8.26 → 1.8.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/log.js +250 -0
- package/package.json +11 -8
package/lib/log.js
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
/* eslint-disable no-underscore-dangle */
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const isEqual = require('lodash/isEqual');
|
|
5
|
+
const { Tail } = require('tail');
|
|
6
|
+
const readLastLines = require('read-last-lines');
|
|
7
|
+
const { BLOCKLET_MODES } = require('@blocklet/constant');
|
|
8
|
+
const logger = require('@abtnode/logger')(require('../package.json').name);
|
|
9
|
+
|
|
10
|
+
class StreamLog {
|
|
11
|
+
constructor() {
|
|
12
|
+
this._files = null; // Object { <level>: <filePath> }
|
|
13
|
+
this._tails = null; // Object { <level>: <Tail> }
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @param {Object} files Object { <level>: <filePath> }
|
|
18
|
+
* @return {Boolean} if files change
|
|
19
|
+
*/
|
|
20
|
+
async setFiles(files) {
|
|
21
|
+
if (!isEqual(this._files, files)) {
|
|
22
|
+
this.clearTails();
|
|
23
|
+
this._files = files;
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
getRecent(lineNum, callback) {
|
|
30
|
+
if (!this._files) {
|
|
31
|
+
callback(new Error('files is empty'));
|
|
32
|
+
}
|
|
33
|
+
Object.entries(this._files).forEach(([level, file]) => {
|
|
34
|
+
if (!file || !fs.existsSync(file)) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
readLastLines
|
|
38
|
+
.read(file, lineNum || 0)
|
|
39
|
+
.then((data) => {
|
|
40
|
+
callback(null, level, data);
|
|
41
|
+
})
|
|
42
|
+
.catch((error) => {
|
|
43
|
+
callback(error, level);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
ensureTails() {
|
|
49
|
+
if (this._tails) {
|
|
50
|
+
return this._tails;
|
|
51
|
+
}
|
|
52
|
+
if (!this._files) {
|
|
53
|
+
return {};
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
this._tails = {};
|
|
57
|
+
Object.entries(this._files).forEach(([level, file]) => {
|
|
58
|
+
if (!file || !fs.existsSync(file)) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
this._tails[level] = new Tail(file);
|
|
62
|
+
});
|
|
63
|
+
return this._tails;
|
|
64
|
+
} catch (error) {
|
|
65
|
+
this._tails = null;
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
clearTails() {
|
|
71
|
+
if (this._tails) {
|
|
72
|
+
Object.values(this._tails).forEach((tail) => {
|
|
73
|
+
tail.unwatch();
|
|
74
|
+
});
|
|
75
|
+
this._tails = null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const createFile = (files) => {
|
|
81
|
+
Object.values(files).forEach((file) => {
|
|
82
|
+
if (!fs.existsSync(file)) {
|
|
83
|
+
try {
|
|
84
|
+
const dir = path.dirname(file);
|
|
85
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
86
|
+
} catch (err) {
|
|
87
|
+
// Do nothing
|
|
88
|
+
}
|
|
89
|
+
fs.writeFileSync(file, '', 'utf8');
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const getLogFiles = async ({ name, node }) => {
|
|
95
|
+
const dt = new Date();
|
|
96
|
+
const { yyyy, mm, dd } = {
|
|
97
|
+
yyyy: dt.getFullYear(),
|
|
98
|
+
mm: `${dt.getMonth() + 1}`.padStart(2, 0),
|
|
99
|
+
dd: `${dt.getDate()}`.padStart(2, 0),
|
|
100
|
+
};
|
|
101
|
+
const date = `${yyyy}-${mm}-${dd}`;
|
|
102
|
+
|
|
103
|
+
if (name === 'abtnode') {
|
|
104
|
+
const logDir = path.join(node.dataDirs.logs, '_abtnode');
|
|
105
|
+
|
|
106
|
+
const info = path.join(logDir, `daemon-${date}.log`);
|
|
107
|
+
const error = path.join(logDir, `daemon-error-${date}.log`);
|
|
108
|
+
const access = path.join(logDir, 'access.log');
|
|
109
|
+
const stdout = path.join(logDir, 'daemon.stdout.log');
|
|
110
|
+
const stderr = path.join(logDir, 'daemon.stderr.log');
|
|
111
|
+
|
|
112
|
+
createFile({
|
|
113
|
+
info,
|
|
114
|
+
error,
|
|
115
|
+
access,
|
|
116
|
+
stdout,
|
|
117
|
+
stderr,
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
info,
|
|
122
|
+
error,
|
|
123
|
+
access,
|
|
124
|
+
stdout,
|
|
125
|
+
stderr,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (name === 'blocklet-services') {
|
|
130
|
+
const logDir = path.join(node.dataDirs.logs, '_abtnode');
|
|
131
|
+
const info = path.join(logDir, 'service.log');
|
|
132
|
+
const error = path.join(logDir, 'service.error.log');
|
|
133
|
+
createFile({ info, error });
|
|
134
|
+
return { info, error };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (name.indexOf('blocklet-') === 0) {
|
|
138
|
+
const did = name.substring('blocklet-'.length);
|
|
139
|
+
const blocklet = await node.getBlocklet({ did, attachConfig: false });
|
|
140
|
+
const { name: blockletName } = blocklet.meta;
|
|
141
|
+
|
|
142
|
+
const dir = path.join(node.dataDirs.logs, blockletName);
|
|
143
|
+
const info = path.join(dir, `info-${date}.log`);
|
|
144
|
+
const error = path.join(dir, `info-error-${date}.log`);
|
|
145
|
+
const access = path.join(dir, 'access.log');
|
|
146
|
+
const stdout = path.join(dir, 'output.log');
|
|
147
|
+
const stderr = path.join(dir, 'error.log');
|
|
148
|
+
|
|
149
|
+
createFile({
|
|
150
|
+
info,
|
|
151
|
+
error,
|
|
152
|
+
access,
|
|
153
|
+
stdout,
|
|
154
|
+
stderr,
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
info,
|
|
159
|
+
error,
|
|
160
|
+
access,
|
|
161
|
+
stdout,
|
|
162
|
+
stderr,
|
|
163
|
+
recent: blocklet.mode === BLOCKLET_MODES.DEVELOPMENT ? 0 : 100,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (name.startsWith('service-gateway-')) {
|
|
168
|
+
const providerName = name.substring('service-gateway-'.length);
|
|
169
|
+
const provider = node.getRouterProvider(providerName);
|
|
170
|
+
if (!provider) {
|
|
171
|
+
logger.error('router engine is empty', { name, providerName });
|
|
172
|
+
return {};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return provider.getLogFilesForToday(path.join(node.dataDirs.router, providerName));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return {};
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
const createStreamLogManager = ({ onLog, onGetLogFiles }) => {
|
|
182
|
+
const store = {}; // Object<name: streamLog>
|
|
183
|
+
|
|
184
|
+
const ensure = (name) => {
|
|
185
|
+
if (!store[name]) {
|
|
186
|
+
store[name] = new StreamLog();
|
|
187
|
+
}
|
|
188
|
+
return store[name];
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
const destroy = (name) => {
|
|
192
|
+
logger.info('log stream: destroy', { name });
|
|
193
|
+
if (!store[name]) {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
const log = store[name];
|
|
197
|
+
try {
|
|
198
|
+
log.clearTails();
|
|
199
|
+
delete store[name];
|
|
200
|
+
} catch (error) {
|
|
201
|
+
logger.error('log stream: remove ref error ', error);
|
|
202
|
+
delete store[name];
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const add = async (name, topic, firstLogCb) => {
|
|
207
|
+
logger.info('log stream: add', { name });
|
|
208
|
+
const log = ensure(name);
|
|
209
|
+
try {
|
|
210
|
+
// update files
|
|
211
|
+
// push recent 100 log
|
|
212
|
+
const { recent = 100, ...logFiles } = await onGetLogFiles(name);
|
|
213
|
+
const changed = await log.setFiles(logFiles);
|
|
214
|
+
logger.info('log stream: added', { name, logFiles });
|
|
215
|
+
log.getRecent(recent, (error, level, data) => {
|
|
216
|
+
if (error) {
|
|
217
|
+
logger.error('log stream error ', error);
|
|
218
|
+
}
|
|
219
|
+
if (firstLogCb) {
|
|
220
|
+
firstLogCb(level, data, logFiles);
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
// stream
|
|
224
|
+
if (changed) {
|
|
225
|
+
const tails = log.ensureTails();
|
|
226
|
+
Object.entries(tails).forEach(([level, tail]) => {
|
|
227
|
+
tail.on('line', (data) => {
|
|
228
|
+
onLog({ topic, level, logFiles, data });
|
|
229
|
+
});
|
|
230
|
+
tail.on('error', (error) => {
|
|
231
|
+
logger.error('log tail error ', { error });
|
|
232
|
+
destroy(name);
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
} catch (error) {
|
|
237
|
+
logger.error('log stream error ', error);
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
return {
|
|
242
|
+
add,
|
|
243
|
+
destroy,
|
|
244
|
+
};
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
module.exports = {
|
|
248
|
+
createStreamLogManager,
|
|
249
|
+
getLogFiles,
|
|
250
|
+
};
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "1.8.
|
|
6
|
+
"version": "1.8.27",
|
|
7
7
|
"description": "ArcBlock's JavaScript utility",
|
|
8
8
|
"main": "lib/index.js",
|
|
9
9
|
"files": [
|
|
@@ -18,12 +18,13 @@
|
|
|
18
18
|
"author": "polunzh <polunzh@gmail.com> (http://github.com/polunzh)",
|
|
19
19
|
"license": "MIT",
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@abtnode/constant": "1.8.
|
|
22
|
-
"@abtnode/logger": "1.8.
|
|
23
|
-
"@arcblock/jwt": "^1.17.
|
|
24
|
-
"@
|
|
25
|
-
"@ocap/
|
|
26
|
-
"@ocap/
|
|
21
|
+
"@abtnode/constant": "1.8.27",
|
|
22
|
+
"@abtnode/logger": "1.8.27",
|
|
23
|
+
"@arcblock/jwt": "^1.17.20",
|
|
24
|
+
"@blocklet/constant": "1.8.27",
|
|
25
|
+
"@ocap/mcrypto": "1.17.20",
|
|
26
|
+
"@ocap/util": "1.17.20",
|
|
27
|
+
"@ocap/wallet": "1.17.20",
|
|
27
28
|
"axios": "^0.27.2",
|
|
28
29
|
"axios-mock-adapter": "^1.21.2",
|
|
29
30
|
"axon": "^2.0.3",
|
|
@@ -42,10 +43,12 @@
|
|
|
42
43
|
"parallel-transform": "^1.2.0",
|
|
43
44
|
"public-ip": "^4.0.4",
|
|
44
45
|
"pump": "^3.0.0",
|
|
46
|
+
"read-last-lines": "^1.8.0",
|
|
45
47
|
"semver": "^7.3.7",
|
|
46
48
|
"semver-sort": "^1.0.0",
|
|
47
49
|
"shelljs": "^0.8.5",
|
|
48
50
|
"stream-to-promise": "^3.0.0",
|
|
51
|
+
"tail": "^2.2.4",
|
|
49
52
|
"through2-filter": "^3.0.0",
|
|
50
53
|
"through2-map": "^3.0.0",
|
|
51
54
|
"to-semver": "^3.0.0",
|
|
@@ -58,5 +61,5 @@
|
|
|
58
61
|
"fs-extra": "^10.1.0",
|
|
59
62
|
"jest": "^27.5.1"
|
|
60
63
|
},
|
|
61
|
-
"gitHead": "
|
|
64
|
+
"gitHead": "84b2cb3ee703479f17e88c91650ecf3015cbf6af"
|
|
62
65
|
}
|