@abtnode/util 1.8.25 → 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.
Files changed (2) hide show
  1. package/lib/log.js +250 -0
  2. 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.25",
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.25",
22
- "@abtnode/logger": "1.8.25",
23
- "@arcblock/jwt": "^1.17.19",
24
- "@ocap/mcrypto": "1.17.19",
25
- "@ocap/util": "1.17.19",
26
- "@ocap/wallet": "1.17.19",
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": "ea34dcd0037c63cb63b31744efcf169edf8aa776"
64
+ "gitHead": "84b2cb3ee703479f17e88c91650ecf3015cbf6af"
62
65
  }