vladx 1.0.0
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/README.md +256 -0
- package/bin/cli.js +486 -0
- package/bin/vlad.js +539 -0
- package/bin/vladpm.js +710 -0
- package/bin/vladx.js +491 -0
- package/package.json +57 -0
- package/src/engine/jit-compiler.js +285 -0
- package/src/engine/vladx-engine.js +941 -0
- package/src/index.js +44 -0
- package/src/interpreter/interpreter.js +2114 -0
- package/src/lexer/lexer.js +658 -0
- package/src/lexer/optimized-lexer.js +106 -0
- package/src/lexer/regex-cache.js +83 -0
- package/src/parser/ast-nodes.js +472 -0
- package/src/parser/parser.js +1408 -0
- package/src/runtime/advanced-type-system.js +209 -0
- package/src/runtime/async-manager.js +252 -0
- package/src/runtime/builtins.js +143 -0
- package/src/runtime/bundler.js +422 -0
- package/src/runtime/cache-manager.js +126 -0
- package/src/runtime/data-structures.js +612 -0
- package/src/runtime/debugger.js +260 -0
- package/src/runtime/enhanced-module-system.js +196 -0
- package/src/runtime/environment-enhanced.js +272 -0
- package/src/runtime/environment.js +140 -0
- package/src/runtime/event-emitter.js +232 -0
- package/src/runtime/formatter.js +280 -0
- package/src/runtime/functional.js +359 -0
- package/src/runtime/io-operations.js +390 -0
- package/src/runtime/linter.js +374 -0
- package/src/runtime/logging.js +314 -0
- package/src/runtime/minifier.js +242 -0
- package/src/runtime/module-system.js +377 -0
- package/src/runtime/network-operations.js +373 -0
- package/src/runtime/profiler.js +295 -0
- package/src/runtime/repl.js +336 -0
- package/src/runtime/security-manager.js +244 -0
- package/src/runtime/source-map-generator.js +208 -0
- package/src/runtime/test-runner.js +394 -0
- package/src/runtime/transformer.js +277 -0
- package/src/runtime/type-system.js +244 -0
- package/src/runtime/vladx-object.js +250 -0
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Linter — Линтер для кода VladX
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Lexer } from '../lexer/lexer.js';
|
|
6
|
+
import { Parser } from '../parser/parser.js';
|
|
7
|
+
|
|
8
|
+
export class Linter {
|
|
9
|
+
constructor(options = {}) {
|
|
10
|
+
this.rules = new Map();
|
|
11
|
+
this.config = options.config || {};
|
|
12
|
+
this.severity = options.severity || 'error'; // error, warning, info
|
|
13
|
+
this.autoFix = options.autoFix || false;
|
|
14
|
+
|
|
15
|
+
this.registerDefaultRules();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Добавить правило
|
|
20
|
+
*/
|
|
21
|
+
addRule(name, rule) {
|
|
22
|
+
this.rules.set(name, rule);
|
|
23
|
+
return this;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Удалить правило
|
|
28
|
+
*/
|
|
29
|
+
removeRule(name) {
|
|
30
|
+
return this.rules.delete(name);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Проверить файл
|
|
35
|
+
*/
|
|
36
|
+
lint(source, filename = '<anonymous>') {
|
|
37
|
+
const results = [];
|
|
38
|
+
|
|
39
|
+
// Лексический анализ
|
|
40
|
+
const lexer = new Lexer(source, filename);
|
|
41
|
+
const tokens = lexer.tokenize();
|
|
42
|
+
|
|
43
|
+
// Синтаксический анализ
|
|
44
|
+
const parser = new Parser(tokens);
|
|
45
|
+
const ast = parser.parse();
|
|
46
|
+
|
|
47
|
+
// Применить правила
|
|
48
|
+
for (const [ruleName, rule] of this.rules) {
|
|
49
|
+
if (rule.checkTokens) {
|
|
50
|
+
const tokenResults = rule.checkTokens(tokens, filename);
|
|
51
|
+
results.push(...tokenResults.map(r => ({ ...r, rule: ruleName })));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (rule.checkAST) {
|
|
55
|
+
const astResults = rule.checkAST(ast, filename);
|
|
56
|
+
results.push(...astResults.map(r => ({ ...r, rule: ruleName })));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (rule.checkSource) {
|
|
60
|
+
const sourceResults = rule.checkSource(source, filename);
|
|
61
|
+
results.push(...sourceResults.map(r => ({ ...r, rule: ruleName })));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
errors: results.filter(r => r.severity === 'error'),
|
|
67
|
+
warnings: results.filter(r => r.severity === 'warning'),
|
|
68
|
+
info: results.filter(r => r.severity === 'info'),
|
|
69
|
+
all: results,
|
|
70
|
+
hasErrors: results.some(r => r.severity === 'error')
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Автофикс проблем
|
|
76
|
+
*/
|
|
77
|
+
fix(source, filename = '<anonymous>') {
|
|
78
|
+
const results = this.lint(source, filename);
|
|
79
|
+
let fixedSource = source;
|
|
80
|
+
|
|
81
|
+
if (!this.autoFix) {
|
|
82
|
+
return {
|
|
83
|
+
source,
|
|
84
|
+
results,
|
|
85
|
+
fixed: false
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
for (const [ruleName, rule] of this.rules) {
|
|
90
|
+
if (rule.fix && results.some(r => r.rule === ruleName)) {
|
|
91
|
+
fixedSource = rule.fix(fixedSource, filename);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const newResults = this.lint(fixedSource, filename);
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
source: fixedSource,
|
|
99
|
+
originalResults: results,
|
|
100
|
+
results: newResults,
|
|
101
|
+
fixed: newResults.all.length < results.all.length
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Зарегистрировать правила по умолчанию
|
|
107
|
+
*/
|
|
108
|
+
registerDefaultRules() {
|
|
109
|
+
// Правило: unused variables
|
|
110
|
+
this.addRule('no-unused-vars', {
|
|
111
|
+
checkAST: (ast, filename) => {
|
|
112
|
+
const results = [];
|
|
113
|
+
const usedVars = new Set();
|
|
114
|
+
const declaredVars = new Set();
|
|
115
|
+
|
|
116
|
+
const traverse = (node) => {
|
|
117
|
+
if (!node) return;
|
|
118
|
+
|
|
119
|
+
if (node.type === 'LetStatement' || node.type === 'ConstStatement') {
|
|
120
|
+
declaredVars.add(node.name);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (node.type === 'Identifier') {
|
|
124
|
+
usedVars.add(node.name);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
for (const key in node) {
|
|
128
|
+
if (Array.isArray(node[key])) {
|
|
129
|
+
node[key].forEach(traverse);
|
|
130
|
+
} else if (typeof node[key] === 'object') {
|
|
131
|
+
traverse(node[key]);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
traverse(ast);
|
|
137
|
+
|
|
138
|
+
for (const varName of declaredVars) {
|
|
139
|
+
if (!usedVars.has(varName)) {
|
|
140
|
+
results.push({
|
|
141
|
+
message: `Переменная "${varName}" не используется`,
|
|
142
|
+
line: ast.body?.[0]?.line || 0,
|
|
143
|
+
column: 0,
|
|
144
|
+
severity: 'warning',
|
|
145
|
+
filename
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return results;
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// Правило: console.log в production
|
|
155
|
+
this.addRule('no-console', {
|
|
156
|
+
checkAST: (ast, filename) => {
|
|
157
|
+
const results = [];
|
|
158
|
+
|
|
159
|
+
const traverse = (node) => {
|
|
160
|
+
if (!node) return;
|
|
161
|
+
|
|
162
|
+
if (node.type === 'CallExpression') {
|
|
163
|
+
if (node.callee === 'печать' || node.callee === 'console.log') {
|
|
164
|
+
results.push({
|
|
165
|
+
message: 'Не используйте console.log в production коде',
|
|
166
|
+
line: node.line || 0,
|
|
167
|
+
column: 0,
|
|
168
|
+
severity: 'warning',
|
|
169
|
+
filename
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
for (const key in node) {
|
|
175
|
+
if (Array.isArray(node[key])) {
|
|
176
|
+
node[key].forEach(traverse);
|
|
177
|
+
} else if (typeof node[key] === 'object') {
|
|
178
|
+
traverse(node[key]);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
traverse(ast);
|
|
184
|
+
return results;
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// Правило: empty blocks
|
|
189
|
+
this.addRule('no-empty-blocks', {
|
|
190
|
+
checkAST: (ast, filename) => {
|
|
191
|
+
const results = [];
|
|
192
|
+
|
|
193
|
+
const traverse = (node) => {
|
|
194
|
+
if (!node) return;
|
|
195
|
+
|
|
196
|
+
if (node.type === 'IfStatement' || node.type === 'WhileStatement') {
|
|
197
|
+
const body = node.thenBranch?.body || node.body;
|
|
198
|
+
if (body && body.length === 0) {
|
|
199
|
+
results.push({
|
|
200
|
+
message: 'Пустой блок кода',
|
|
201
|
+
line: node.line || 0,
|
|
202
|
+
column: 0,
|
|
203
|
+
severity: 'warning',
|
|
204
|
+
filename
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
for (const key in node) {
|
|
210
|
+
if (Array.isArray(node[key])) {
|
|
211
|
+
node[key].forEach(traverse);
|
|
212
|
+
} else if (typeof node[key] === 'object') {
|
|
213
|
+
traverse(node[key]);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
traverse(ast);
|
|
219
|
+
return results;
|
|
220
|
+
},
|
|
221
|
+
fix: (source) => {
|
|
222
|
+
return source.replace(/(?:если|if)\s*\([^)]*\)\s*{\s*}/g, '');
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// Правило: trailing whitespace
|
|
227
|
+
this.addRule('no-trailing-whitespace', {
|
|
228
|
+
checkSource: (source, filename) => {
|
|
229
|
+
const results = [];
|
|
230
|
+
const lines = source.split('\n');
|
|
231
|
+
|
|
232
|
+
lines.forEach((line, index) => {
|
|
233
|
+
if (line !== line.trimEnd()) {
|
|
234
|
+
results.push({
|
|
235
|
+
message: 'Trailing whitespace в конце строки',
|
|
236
|
+
line: index + 1,
|
|
237
|
+
column: line.length,
|
|
238
|
+
severity: 'info',
|
|
239
|
+
filename
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
return results;
|
|
245
|
+
},
|
|
246
|
+
fix: (source) => {
|
|
247
|
+
return source.split('\n').map(line => line.trimEnd()).join('\n');
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
// Правило: line length
|
|
252
|
+
this.addRule('max-line-length', {
|
|
253
|
+
checkSource: (source, filename) => {
|
|
254
|
+
const results = [];
|
|
255
|
+
const maxLength = this.config.maxLineLength || 100;
|
|
256
|
+
const lines = source.split('\n');
|
|
257
|
+
|
|
258
|
+
lines.forEach((line, index) => {
|
|
259
|
+
if (line.length > maxLength) {
|
|
260
|
+
results.push({
|
|
261
|
+
message: `Строка слишком длинная: ${line.length} > ${maxLength}`,
|
|
262
|
+
line: index + 1,
|
|
263
|
+
column: maxLength,
|
|
264
|
+
severity: 'warning',
|
|
265
|
+
filename
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
return results;
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
// Правило: no var
|
|
275
|
+
this.addRule('no-var', {
|
|
276
|
+
checkTokens: (tokens, filename) => {
|
|
277
|
+
const results = [];
|
|
278
|
+
|
|
279
|
+
tokens.forEach(token => {
|
|
280
|
+
if (token.type === 'VAR' || token.value === 'переменная') {
|
|
281
|
+
results.push({
|
|
282
|
+
message: 'Используйте "пусть" или "константа" вместо "переменная"',
|
|
283
|
+
line: token.line,
|
|
284
|
+
column: token.column,
|
|
285
|
+
severity: 'error',
|
|
286
|
+
filename
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
return results;
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
// Правило: curly braces
|
|
296
|
+
this.addRule('curly', {
|
|
297
|
+
checkAST: (ast, filename) => {
|
|
298
|
+
const results = [];
|
|
299
|
+
|
|
300
|
+
const traverse = (node) => {
|
|
301
|
+
if (!node) return;
|
|
302
|
+
|
|
303
|
+
if (node.type === 'IfStatement') {
|
|
304
|
+
const thenBranch = node.thenBranch;
|
|
305
|
+
if (thenBranch && thenBranch.type !== 'BlockStatement') {
|
|
306
|
+
results.push({
|
|
307
|
+
message: 'Используйте фигурные скобки для if блоков',
|
|
308
|
+
line: node.line || 0,
|
|
309
|
+
column: 0,
|
|
310
|
+
severity: 'error',
|
|
311
|
+
filename
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
for (const key in node) {
|
|
317
|
+
if (Array.isArray(node[key])) {
|
|
318
|
+
node[key].forEach(traverse);
|
|
319
|
+
} else if (typeof node[key] === 'object') {
|
|
320
|
+
traverse(node[key]);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
traverse(ast);
|
|
326
|
+
return results;
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Получить результаты в формате JSON
|
|
333
|
+
*/
|
|
334
|
+
getResultsJSON(lintResults) {
|
|
335
|
+
return JSON.stringify(lintResults, null, 2);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Получить результаты в формате JUnit
|
|
340
|
+
*/
|
|
341
|
+
getResultsJUnit(lintResults) {
|
|
342
|
+
const errors = lintResults.errors;
|
|
343
|
+
const warnings = lintResults.warnings;
|
|
344
|
+
|
|
345
|
+
let xml = '<?xml version="1.0" encoding="UTF-8"?>\n';
|
|
346
|
+
xml += `<testsuites errors="${errors.length}" failures="0" tests="${errors.length + warnings.length}">\n`;
|
|
347
|
+
xml += ' <testsuite name="VladX Linter">\n';
|
|
348
|
+
|
|
349
|
+
for (const error of errors) {
|
|
350
|
+
xml += ` <testcase name="${error.rule}">\n`;
|
|
351
|
+
xml += ` <error message="${error.message}" line="${error.line}"/>\n`;
|
|
352
|
+
xml += ' </testcase>\n';
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
for (const warning of warnings) {
|
|
356
|
+
xml += ` <testcase name="${warning.rule}"/>\n`;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
xml += ' </testsuite>\n';
|
|
360
|
+
xml += '</testsuites>';
|
|
361
|
+
|
|
362
|
+
return xml;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Очистить правила
|
|
367
|
+
*/
|
|
368
|
+
clearRules() {
|
|
369
|
+
this.rules.clear();
|
|
370
|
+
return this;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
export default Linter;
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logging — Система логирования
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { existsSync, writeFileSync, appendFileSync, mkdirSync } from 'fs';
|
|
6
|
+
import { dirname } from 'path';
|
|
7
|
+
|
|
8
|
+
export class Logging {
|
|
9
|
+
constructor(options = {}) {
|
|
10
|
+
this.level = options.level || 'info'; // debug, info, warn, error
|
|
11
|
+
this.format = options.format || 'text'; // text, json
|
|
12
|
+
this.file = options.file || null;
|
|
13
|
+
this.console = options.console !== false;
|
|
14
|
+
this.colors = options.colors !== false;
|
|
15
|
+
this.timestamp = options.timestamp !== false;
|
|
16
|
+
|
|
17
|
+
this.levels = {
|
|
18
|
+
debug: 0,
|
|
19
|
+
info: 1,
|
|
20
|
+
warn: 2,
|
|
21
|
+
error: 3
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
this.colorsMap = {
|
|
25
|
+
debug: '\x1b[36m', // cyan
|
|
26
|
+
info: '\x1b[32m', // green
|
|
27
|
+
warn: '\x1b[33m', // yellow
|
|
28
|
+
error: '\x1b[31m', // red
|
|
29
|
+
reset: '\x1b[0m'
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
this.logBuffer = [];
|
|
33
|
+
this.maxBufferSize = options.maxBufferSize || 1000;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Логирование
|
|
38
|
+
*/
|
|
39
|
+
log(level, message, context = {}) {
|
|
40
|
+
if (this.levels[level] < this.levels[this.level]) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const logEntry = {
|
|
45
|
+
level,
|
|
46
|
+
message,
|
|
47
|
+
context,
|
|
48
|
+
timestamp: this.timestamp ? new Date().toISOString() : undefined
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
this.logBuffer.push(logEntry);
|
|
52
|
+
|
|
53
|
+
if (this.logBuffer.length > this.maxBufferSize) {
|
|
54
|
+
this.logBuffer.shift();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const formatted = this.formatLogEntry(logEntry);
|
|
58
|
+
|
|
59
|
+
if (this.console) {
|
|
60
|
+
console.log(formatted);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (this.file) {
|
|
64
|
+
this.writeToFile(formatted);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Format log entry
|
|
70
|
+
*/
|
|
71
|
+
formatLogEntry(entry) {
|
|
72
|
+
if (this.format === 'json') {
|
|
73
|
+
return JSON.stringify(entry);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
let output = '';
|
|
77
|
+
|
|
78
|
+
// Timestamp
|
|
79
|
+
if (entry.timestamp) {
|
|
80
|
+
output += `[${entry.timestamp}] `;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Level
|
|
84
|
+
const levelUpper = entry.level.toUpperCase();
|
|
85
|
+
if (this.colors) {
|
|
86
|
+
output += `${this.colorsMap[entry.level]}${levelUpper}${this.colorsMap.reset} `;
|
|
87
|
+
} else {
|
|
88
|
+
output += `${levelUpper} `;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Message
|
|
92
|
+
output += entry.message;
|
|
93
|
+
|
|
94
|
+
// Context
|
|
95
|
+
if (Object.keys(entry.context).length > 0) {
|
|
96
|
+
output += ` ${JSON.stringify(entry.context)}`;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return output;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Записать в файл
|
|
104
|
+
*/
|
|
105
|
+
writeToFile(formatted) {
|
|
106
|
+
try {
|
|
107
|
+
if (!existsSync(this.file)) {
|
|
108
|
+
mkdirSync(dirname(this.file), { recursive: true });
|
|
109
|
+
}
|
|
110
|
+
appendFileSync(this.file, formatted + '\n', 'utf-8');
|
|
111
|
+
} catch (error) {
|
|
112
|
+
console.error('Ошибка записи лога:', error);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Debug
|
|
118
|
+
*/
|
|
119
|
+
debug(message, context) {
|
|
120
|
+
this.log('debug', message, context);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Info
|
|
125
|
+
*/
|
|
126
|
+
info(message, context) {
|
|
127
|
+
this.log('info', message, context);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Warn
|
|
132
|
+
*/
|
|
133
|
+
warn(message, context) {
|
|
134
|
+
this.log('warn', message, context);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Error
|
|
139
|
+
*/
|
|
140
|
+
error(message, context) {
|
|
141
|
+
this.log('error', message, context);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Создать child logger с контекстом
|
|
146
|
+
*/
|
|
147
|
+
child(context) {
|
|
148
|
+
const child = new Logging({
|
|
149
|
+
level: this.level,
|
|
150
|
+
format: this.format,
|
|
151
|
+
file: this.file,
|
|
152
|
+
console: this.console,
|
|
153
|
+
colors: this.colors,
|
|
154
|
+
timestamp: this.timestamp
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
child.defaultContext = { ...this.defaultContext, ...context };
|
|
158
|
+
|
|
159
|
+
const originalLog = child.log.bind(child);
|
|
160
|
+
child.log = (level, message, context) => {
|
|
161
|
+
originalLog(level, message, { ...this.defaultContext, ...context });
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
return child;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Создать logger с уровнем
|
|
169
|
+
*/
|
|
170
|
+
withLevel(level) {
|
|
171
|
+
const logger = new Logging({
|
|
172
|
+
level,
|
|
173
|
+
format: this.format,
|
|
174
|
+
file: this.file,
|
|
175
|
+
console: this.console,
|
|
176
|
+
colors: this.colors,
|
|
177
|
+
timestamp: this.timestamp
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
return logger;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Установить уровень логирования
|
|
185
|
+
*/
|
|
186
|
+
setLevel(level) {
|
|
187
|
+
this.level = level;
|
|
188
|
+
return this;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Получить буфер логов
|
|
193
|
+
*/
|
|
194
|
+
getLogs() {
|
|
195
|
+
return [...this.logBuffer];
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Очистить буфер логов
|
|
200
|
+
*/
|
|
201
|
+
clearLogs() {
|
|
202
|
+
this.logBuffer = [];
|
|
203
|
+
return this;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Экспорт логов
|
|
208
|
+
*/
|
|
209
|
+
exportLogs(format = 'json') {
|
|
210
|
+
if (format === 'json') {
|
|
211
|
+
return JSON.stringify(this.logBuffer, null, 2);
|
|
212
|
+
} else {
|
|
213
|
+
return this.logBuffer.map(entry => this.formatLogEntry(entry)).join('\n');
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Фильтр логов по уровню
|
|
219
|
+
*/
|
|
220
|
+
filterLogs(level) {
|
|
221
|
+
const minLevel = this.levels[level];
|
|
222
|
+
return this.logBuffer.filter(entry => this.levels[entry.level] >= minLevel);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Фильтр логов по времени
|
|
227
|
+
*/
|
|
228
|
+
filterLogsByTime(startTime, endTime) {
|
|
229
|
+
return this.logBuffer.filter(entry => {
|
|
230
|
+
if (!entry.timestamp) return true;
|
|
231
|
+
const time = new Date(entry.timestamp).getTime();
|
|
232
|
+
return time >= startTime.getTime() && time <= endTime.getTime();
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Создать logger с метриками
|
|
238
|
+
*/
|
|
239
|
+
static withMetrics(logger) {
|
|
240
|
+
const metrics = {
|
|
241
|
+
debug: 0,
|
|
242
|
+
info: 0,
|
|
243
|
+
warn: 0,
|
|
244
|
+
error: 0
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
const originalLog = logger.log.bind(logger);
|
|
248
|
+
logger.log = (level, message, context) => {
|
|
249
|
+
metrics[level]++;
|
|
250
|
+
originalLog(level, message, context);
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
logger.getMetrics = () => metrics;
|
|
254
|
+
|
|
255
|
+
return logger;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Создать logger с ротацией файлов
|
|
260
|
+
*/
|
|
261
|
+
static withFileRotation(logger, options = {}) {
|
|
262
|
+
const maxSize = options.maxSize || 1024 * 1024; // 1MB
|
|
263
|
+
const maxFiles = options.maxFiles || 5;
|
|
264
|
+
|
|
265
|
+
const checkAndRotate = () => {
|
|
266
|
+
if (!logger.file) return;
|
|
267
|
+
|
|
268
|
+
try {
|
|
269
|
+
const stats = require('fs').statSync(logger.file);
|
|
270
|
+
if (stats.size > maxSize) {
|
|
271
|
+
// Ротация файлов
|
|
272
|
+
for (let i = maxFiles - 1; i >= 1; i--) {
|
|
273
|
+
const oldFile = i === 1 ? logger.file : `${logger.file}.${i - 1}`;
|
|
274
|
+
const newFile = `${logger.file}.${i}`;
|
|
275
|
+
|
|
276
|
+
try {
|
|
277
|
+
require('fs').renameSync(oldFile, newFile);
|
|
278
|
+
} catch (e) {}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Создать новый файл
|
|
282
|
+
require('fs').writeFileSync(logger.file, '', 'utf-8');
|
|
283
|
+
}
|
|
284
|
+
} catch (e) {}
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
// Перехватить writeToFile
|
|
288
|
+
const originalWrite = logger.writeToFile.bind(logger);
|
|
289
|
+
logger.writeToFile = (formatted) => {
|
|
290
|
+
checkAndRotate();
|
|
291
|
+
originalWrite(formatted);
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
return logger;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Создать logger с цветным выводом
|
|
299
|
+
*/
|
|
300
|
+
static withColors(logger) {
|
|
301
|
+
logger.colors = true;
|
|
302
|
+
return logger;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Создать logger без цветов
|
|
307
|
+
*/
|
|
308
|
+
static withoutColors(logger) {
|
|
309
|
+
logger.colors = false;
|
|
310
|
+
return logger;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
export default Logging;
|