@jetstart/logs 1.5.2
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/.eslintrc.json +6 -0
- package/README.md +122 -0
- package/dist/cli/formatter.d.ts +8 -0
- package/dist/cli/formatter.js +60 -0
- package/dist/cli/index.d.ts +9 -0
- package/dist/cli/index.js +24 -0
- package/dist/cli/viewer.d.ts +13 -0
- package/dist/cli/viewer.js +63 -0
- package/dist/filters/index.d.ts +10 -0
- package/dist/filters/index.js +45 -0
- package/dist/filters/level.d.ts +8 -0
- package/dist/filters/level.js +28 -0
- package/dist/filters/search.d.ts +7 -0
- package/dist/filters/search.js +29 -0
- package/dist/filters/source.d.ts +7 -0
- package/dist/filters/source.js +11 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +26 -0
- package/dist/server/index.d.ts +26 -0
- package/dist/server/index.js +119 -0
- package/dist/server/storage.d.ts +15 -0
- package/dist/server/storage.js +70 -0
- package/dist/types/index.d.ts +14 -0
- package/dist/types/index.js +6 -0
- package/dist/utils/colors.d.ts +9 -0
- package/dist/utils/colors.js +50 -0
- package/dist/utils/index.d.ts +5 -0
- package/dist/utils/index.js +21 -0
- package/package.json +65 -0
- package/src/cli/formatter.ts +63 -0
- package/src/cli/index.ts +24 -0
- package/src/cli/viewer.ts +64 -0
- package/src/filters/index.ts +51 -0
- package/src/filters/level.ts +28 -0
- package/src/filters/search.ts +32 -0
- package/src/filters/source.ts +10 -0
- package/src/index.ts +19 -0
- package/src/server/index.ts +134 -0
- package/src/server/storage.ts +77 -0
- package/src/types/index.ts +15 -0
- package/src/utils/colors.ts +45 -0
- package/src/utils/index.ts +5 -0
- package/tests/filters.test.ts +81 -0
- package/tests/storage.test.ts +89 -0
- package/tsconfig.json +25 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Log Storage
|
|
4
|
+
* In-memory log storage with size limits
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.LogStorage = void 0;
|
|
8
|
+
const shared_1 = require("@jetstart/shared");
|
|
9
|
+
class LogStorage {
|
|
10
|
+
logs = [];
|
|
11
|
+
maxEntries;
|
|
12
|
+
constructor(maxEntries = 10000) {
|
|
13
|
+
this.maxEntries = maxEntries;
|
|
14
|
+
}
|
|
15
|
+
add(entry) {
|
|
16
|
+
this.logs.push(entry);
|
|
17
|
+
// Trim if exceeds max
|
|
18
|
+
if (this.logs.length > this.maxEntries) {
|
|
19
|
+
this.logs.shift();
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
getAll() {
|
|
23
|
+
return [...this.logs];
|
|
24
|
+
}
|
|
25
|
+
clear() {
|
|
26
|
+
this.logs = [];
|
|
27
|
+
}
|
|
28
|
+
getStats() {
|
|
29
|
+
const stats = {
|
|
30
|
+
totalLogs: this.logs.length,
|
|
31
|
+
errorCount: 0,
|
|
32
|
+
warningCount: 0,
|
|
33
|
+
lastLogTime: this.logs.length > 0 ? this.logs[this.logs.length - 1].timestamp : 0,
|
|
34
|
+
logsByLevel: {
|
|
35
|
+
[shared_1.LogLevel.VERBOSE]: 0,
|
|
36
|
+
[shared_1.LogLevel.DEBUG]: 0,
|
|
37
|
+
[shared_1.LogLevel.INFO]: 0,
|
|
38
|
+
[shared_1.LogLevel.WARN]: 0,
|
|
39
|
+
[shared_1.LogLevel.ERROR]: 0,
|
|
40
|
+
[shared_1.LogLevel.FATAL]: 0,
|
|
41
|
+
},
|
|
42
|
+
logsBySource: {
|
|
43
|
+
[shared_1.LogSource.CLI]: 0,
|
|
44
|
+
[shared_1.LogSource.CORE]: 0,
|
|
45
|
+
[shared_1.LogSource.CLIENT]: 0,
|
|
46
|
+
[shared_1.LogSource.BUILD]: 0,
|
|
47
|
+
[shared_1.LogSource.NETWORK]: 0,
|
|
48
|
+
[shared_1.LogSource.SYSTEM]: 0,
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
this.logs.forEach(log => {
|
|
52
|
+
// Count by level
|
|
53
|
+
stats.logsByLevel[log.level]++;
|
|
54
|
+
// Count errors and warnings
|
|
55
|
+
if (log.level === shared_1.LogLevel.ERROR || log.level === shared_1.LogLevel.FATAL) {
|
|
56
|
+
stats.errorCount++;
|
|
57
|
+
}
|
|
58
|
+
if (log.level === shared_1.LogLevel.WARN) {
|
|
59
|
+
stats.warningCount++;
|
|
60
|
+
}
|
|
61
|
+
// Count by source
|
|
62
|
+
if (log.source in stats.logsBySource) {
|
|
63
|
+
stats.logsBySource[log.source]++;
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
return stats;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
exports.LogStorage = LogStorage;
|
|
70
|
+
//# sourceMappingURL=storage.js.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logs-specific types
|
|
3
|
+
*/
|
|
4
|
+
export interface LogViewerOptions {
|
|
5
|
+
follow?: boolean;
|
|
6
|
+
tail?: number;
|
|
7
|
+
filter?: any;
|
|
8
|
+
}
|
|
9
|
+
export interface LogServerStats {
|
|
10
|
+
totalLogs: number;
|
|
11
|
+
activeClients: number;
|
|
12
|
+
uptime: number;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Color Utilities
|
|
3
|
+
* Colors for different log levels and sources
|
|
4
|
+
*/
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import { LogLevel, LogSource } from '@jetstart/shared';
|
|
7
|
+
export declare function getColorForLevel(level: LogLevel): chalk.Chalk;
|
|
8
|
+
export declare function getColorForSource(source: LogSource): chalk.Chalk;
|
|
9
|
+
//# sourceMappingURL=colors.d.ts.map
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Color Utilities
|
|
4
|
+
* Colors for different log levels and sources
|
|
5
|
+
*/
|
|
6
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
7
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
8
|
+
};
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.getColorForLevel = getColorForLevel;
|
|
11
|
+
exports.getColorForSource = getColorForSource;
|
|
12
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
13
|
+
const shared_1 = require("@jetstart/shared");
|
|
14
|
+
function getColorForLevel(level) {
|
|
15
|
+
switch (level) {
|
|
16
|
+
case shared_1.LogLevel.VERBOSE:
|
|
17
|
+
return chalk_1.default.gray;
|
|
18
|
+
case shared_1.LogLevel.DEBUG:
|
|
19
|
+
return chalk_1.default.blue;
|
|
20
|
+
case shared_1.LogLevel.INFO:
|
|
21
|
+
return chalk_1.default.green;
|
|
22
|
+
case shared_1.LogLevel.WARN:
|
|
23
|
+
return chalk_1.default.yellow;
|
|
24
|
+
case shared_1.LogLevel.ERROR:
|
|
25
|
+
return chalk_1.default.red;
|
|
26
|
+
case shared_1.LogLevel.FATAL:
|
|
27
|
+
return chalk_1.default.bgRed.white;
|
|
28
|
+
default:
|
|
29
|
+
return chalk_1.default.white;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function getColorForSource(source) {
|
|
33
|
+
switch (source) {
|
|
34
|
+
case shared_1.LogSource.CLI:
|
|
35
|
+
return chalk_1.default.cyan;
|
|
36
|
+
case shared_1.LogSource.CORE:
|
|
37
|
+
return chalk_1.default.magenta;
|
|
38
|
+
case shared_1.LogSource.CLIENT:
|
|
39
|
+
return chalk_1.default.green;
|
|
40
|
+
case shared_1.LogSource.BUILD:
|
|
41
|
+
return chalk_1.default.yellow;
|
|
42
|
+
case shared_1.LogSource.NETWORK:
|
|
43
|
+
return chalk_1.default.blue;
|
|
44
|
+
case shared_1.LogSource.SYSTEM:
|
|
45
|
+
return chalk_1.default.gray;
|
|
46
|
+
default:
|
|
47
|
+
return chalk_1.default.white;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=colors.js.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Export all utilities
|
|
4
|
+
*/
|
|
5
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
8
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
9
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
10
|
+
}
|
|
11
|
+
Object.defineProperty(o, k2, desc);
|
|
12
|
+
}) : (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
o[k2] = m[k];
|
|
15
|
+
}));
|
|
16
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
17
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
18
|
+
};
|
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
__exportStar(require("./colors"), exports);
|
|
21
|
+
//# sourceMappingURL=index.js.map
|
package/package.json
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@jetstart/logs",
|
|
3
|
+
"version": "1.5.2",
|
|
4
|
+
"description": "Logging service for JetStart",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./dist/index.js",
|
|
9
|
+
"./server": "./dist/server/index.js",
|
|
10
|
+
"./cli": "./dist/cli/index.js"
|
|
11
|
+
},
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsc",
|
|
14
|
+
"dev": "tsc --watch",
|
|
15
|
+
"start": "node dist/server/index.js",
|
|
16
|
+
"start:dev": "ts-node src/server/index.ts",
|
|
17
|
+
"clean": "rm -rf dist",
|
|
18
|
+
"test": "jest",
|
|
19
|
+
"test:watch": "jest --watch",
|
|
20
|
+
"test:coverage": "jest --coverage",
|
|
21
|
+
"typecheck": "tsc --noEmit"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"jetstart",
|
|
25
|
+
"logs",
|
|
26
|
+
"logging",
|
|
27
|
+
"logcat"
|
|
28
|
+
],
|
|
29
|
+
"author": "Phantom",
|
|
30
|
+
"license": "Apache-2.0",
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@jetstart/shared": "^1.5.2",
|
|
33
|
+
"ws": "^8.14.2",
|
|
34
|
+
"chalk": "^4.1.2",
|
|
35
|
+
"date-fns": "^2.30.0"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@types/ws": "^8.5.10",
|
|
39
|
+
"@types/node": "^20.10.0",
|
|
40
|
+
"@types/jest": "^29.5.0",
|
|
41
|
+
"jest": "^29.5.0",
|
|
42
|
+
"ts-jest": "^29.1.0",
|
|
43
|
+
"ts-node": "^10.9.2",
|
|
44
|
+
"typescript": "^5.3.0"
|
|
45
|
+
},
|
|
46
|
+
"engines": {
|
|
47
|
+
"node": ">=18.0.0",
|
|
48
|
+
"npm": ">=9.0.0"
|
|
49
|
+
},
|
|
50
|
+
"jest": {
|
|
51
|
+
"preset": "ts-jest",
|
|
52
|
+
"testEnvironment": "node",
|
|
53
|
+
"roots": [
|
|
54
|
+
"<rootDir>/tests"
|
|
55
|
+
],
|
|
56
|
+
"testMatch": [
|
|
57
|
+
"**/*.test.ts"
|
|
58
|
+
],
|
|
59
|
+
"collectCoverageFrom": [
|
|
60
|
+
"src/**/*.ts",
|
|
61
|
+
"!src/**/*.d.ts",
|
|
62
|
+
"!src/**/index.ts"
|
|
63
|
+
]
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Log Formatter
|
|
3
|
+
* Formats log entries for terminal display
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import { format } from 'date-fns';
|
|
8
|
+
import { LogEntry, LogLevel, LogSource } from '@jetstart/shared';
|
|
9
|
+
import { getColorForLevel, getColorForSource } from '../utils/colors';
|
|
10
|
+
|
|
11
|
+
export function formatLog(entry: LogEntry): void {
|
|
12
|
+
const timestamp = formatTimestamp(entry.timestamp);
|
|
13
|
+
const level = formatLevel(entry.level);
|
|
14
|
+
const source = formatSource(entry.source);
|
|
15
|
+
const tag = formatTag(entry.tag);
|
|
16
|
+
const message = entry.message;
|
|
17
|
+
|
|
18
|
+
console.log(`${timestamp} ${level} ${source} ${tag} ${message}`);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function formatTimestamp(timestamp: number): string {
|
|
22
|
+
return chalk.gray(format(timestamp, 'HH:mm:ss.SSS'));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function formatLevel(level: LogLevel): string {
|
|
26
|
+
const color = getColorForLevel(level);
|
|
27
|
+
const label = level.toUpperCase().padEnd(7);
|
|
28
|
+
return color(label);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function formatSource(source: LogSource): string {
|
|
32
|
+
const color = getColorForSource(source);
|
|
33
|
+
const label = `[${source}]`.padEnd(10);
|
|
34
|
+
return color(label);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function formatTag(tag: string): string {
|
|
38
|
+
return chalk.gray(`[${tag}]`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function formatStats(stats: any): void {
|
|
42
|
+
console.log();
|
|
43
|
+
console.log(chalk.bold('Log Statistics:'));
|
|
44
|
+
console.log();
|
|
45
|
+
console.log(`Total Logs: ${chalk.cyan(stats.totalLogs)}`);
|
|
46
|
+
console.log(`Errors: ${chalk.red(stats.errorCount)}`);
|
|
47
|
+
console.log(`Warnings: ${chalk.yellow(stats.warningCount)}`);
|
|
48
|
+
console.log();
|
|
49
|
+
|
|
50
|
+
console.log(chalk.bold('By Level:'));
|
|
51
|
+
Object.entries(stats.logsByLevel).forEach(([level, count]) => {
|
|
52
|
+
const color = getColorForLevel(level as LogLevel);
|
|
53
|
+
console.log(` ${level.padEnd(10)}: ${color(count)}`);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
console.log();
|
|
57
|
+
console.log(chalk.bold('By Source:'));
|
|
58
|
+
Object.entries(stats.logsBySource).forEach(([source, count]) => {
|
|
59
|
+
const color = getColorForSource(source as LogSource);
|
|
60
|
+
console.log(` ${source.padEnd(10)}: ${color(count)}`);
|
|
61
|
+
});
|
|
62
|
+
console.log();
|
|
63
|
+
}
|
package/src/cli/index.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI Log Viewer
|
|
3
|
+
* Terminal-based log viewer
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
import { LogViewer } from './viewer';
|
|
9
|
+
import { LogFilter } from '@jetstart/shared';
|
|
10
|
+
|
|
11
|
+
export async function viewLogs(filter?: LogFilter): Promise<void> {
|
|
12
|
+
const viewer = new LogViewer();
|
|
13
|
+
await viewer.connect();
|
|
14
|
+
viewer.subscribe(filter);
|
|
15
|
+
|
|
16
|
+
// Keep process alive
|
|
17
|
+
process.on('SIGINT', async () => {
|
|
18
|
+
await viewer.disconnect();
|
|
19
|
+
process.exit(0);
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export { LogViewer } from './viewer';
|
|
24
|
+
export { formatLog } from './formatter';
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Log Viewer
|
|
3
|
+
* Connects to logs service and displays logs
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import WebSocket from 'ws';
|
|
7
|
+
import { LogEntry, LogFilter, DEFAULT_LOGS_PORT } from '@jetstart/shared';
|
|
8
|
+
import { formatLog } from './formatter';
|
|
9
|
+
import chalk from 'chalk';
|
|
10
|
+
|
|
11
|
+
export class LogViewer {
|
|
12
|
+
private ws?: WebSocket;
|
|
13
|
+
private connected: boolean = false;
|
|
14
|
+
|
|
15
|
+
async connect(port: number = DEFAULT_LOGS_PORT): Promise<void> {
|
|
16
|
+
return new Promise((resolve, reject) => {
|
|
17
|
+
this.ws = new WebSocket(`ws://localhost:${port}`);
|
|
18
|
+
|
|
19
|
+
this.ws.on('open', () => {
|
|
20
|
+
this.connected = true;
|
|
21
|
+
console.log(chalk.green('✔'), 'Connected to logs service');
|
|
22
|
+
console.log();
|
|
23
|
+
resolve();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
this.ws.on('message', (data: Buffer) => {
|
|
27
|
+
try {
|
|
28
|
+
const log: LogEntry = JSON.parse(data.toString());
|
|
29
|
+
formatLog(log);
|
|
30
|
+
} catch (err) {
|
|
31
|
+
console.log(data.toString());
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
this.ws.on('error', (err: Error) => {
|
|
36
|
+
reject(err);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
this.ws.on('close', () => {
|
|
40
|
+
this.connected = false;
|
|
41
|
+
console.log();
|
|
42
|
+
console.log(chalk.yellow('⚠'), 'Disconnected from logs service');
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
subscribe(filter?: LogFilter, maxLines?: number): void {
|
|
48
|
+
if (!this.ws || !this.connected) {
|
|
49
|
+
throw new Error('Not connected to logs service');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
this.ws.send(JSON.stringify({
|
|
53
|
+
type: 'subscribe',
|
|
54
|
+
filter,
|
|
55
|
+
maxLines,
|
|
56
|
+
}));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async disconnect(): Promise<void> {
|
|
60
|
+
if (this.ws) {
|
|
61
|
+
this.ws.close();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Log Filters
|
|
3
|
+
* Filter logs by level, source, tag, or search query
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
import { LogEntry, LogFilter } from '@jetstart/shared';
|
|
8
|
+
import { filterByLevel } from './level';
|
|
9
|
+
import { filterBySource } from './source';
|
|
10
|
+
import { filterBySearch } from './search';
|
|
11
|
+
|
|
12
|
+
export function applyFilters(logs: LogEntry[], filter: LogFilter): LogEntry[] {
|
|
13
|
+
let filtered = logs;
|
|
14
|
+
|
|
15
|
+
// Filter by level
|
|
16
|
+
if (filter.levels && filter.levels.length > 0) {
|
|
17
|
+
filtered = filterByLevel(filtered, filter.levels);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Filter by source
|
|
21
|
+
if (filter.sources && filter.sources.length > 0) {
|
|
22
|
+
filtered = filterBySource(filtered, filter.sources);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Filter by tags
|
|
26
|
+
if (filter.tags && filter.tags.length > 0) {
|
|
27
|
+
filtered = filtered.filter(log =>
|
|
28
|
+
filter.tags!.includes(log.tag)
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Filter by search query
|
|
33
|
+
if (filter.searchQuery) {
|
|
34
|
+
filtered = filterBySearch(filtered, filter.searchQuery);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Filter by time range
|
|
38
|
+
if (filter.startTime) {
|
|
39
|
+
filtered = filtered.filter(log => log.timestamp >= filter.startTime!);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (filter.endTime) {
|
|
43
|
+
filtered = filtered.filter(log => log.timestamp <= filter.endTime!);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return filtered;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export { filterByLevel } from './level';
|
|
50
|
+
export { filterBySource } from './source';
|
|
51
|
+
export { filterBySearch } from './search';
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Level Filter
|
|
3
|
+
* Filter logs by severity level
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { LogEntry, LogLevel } from '@jetstart/shared';
|
|
7
|
+
|
|
8
|
+
const LEVEL_PRIORITY: Record<LogLevel, number> = {
|
|
9
|
+
[LogLevel.VERBOSE]: 0,
|
|
10
|
+
[LogLevel.DEBUG]: 1,
|
|
11
|
+
[LogLevel.INFO]: 2,
|
|
12
|
+
[LogLevel.WARN]: 3,
|
|
13
|
+
[LogLevel.ERROR]: 4,
|
|
14
|
+
[LogLevel.FATAL]: 5,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export function filterByLevel(logs: LogEntry[], levels: LogLevel[]): LogEntry[] {
|
|
18
|
+
return logs.filter(log => levels.includes(log.level));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function filterByMinLevel(logs: LogEntry[], minLevel: LogLevel): LogEntry[] {
|
|
22
|
+
const minPriority = LEVEL_PRIORITY[minLevel];
|
|
23
|
+
|
|
24
|
+
return logs.filter(log => {
|
|
25
|
+
const logPriority = LEVEL_PRIORITY[log.level];
|
|
26
|
+
return logPriority >= minPriority;
|
|
27
|
+
});
|
|
28
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Search Filter
|
|
3
|
+
* Filter logs by text search
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { LogEntry } from '@jetstart/shared';
|
|
7
|
+
|
|
8
|
+
export function filterBySearch(logs: LogEntry[], query: string): LogEntry[] {
|
|
9
|
+
const lowerQuery = query.toLowerCase();
|
|
10
|
+
|
|
11
|
+
return logs.filter(log => {
|
|
12
|
+
// Search in message
|
|
13
|
+
if (log.message.toLowerCase().includes(lowerQuery)) {
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Search in tag
|
|
18
|
+
if (log.tag.toLowerCase().includes(lowerQuery)) {
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Search in metadata
|
|
23
|
+
if (log.metadata) {
|
|
24
|
+
const metadataStr = JSON.stringify(log.metadata).toLowerCase();
|
|
25
|
+
if (metadataStr.includes(lowerQuery)) {
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return false;
|
|
31
|
+
});
|
|
32
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Source Filter
|
|
3
|
+
* Filter logs by source (CLI, Core, Client, etc.)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { LogEntry, LogSource } from '@jetstart/shared';
|
|
7
|
+
|
|
8
|
+
export function filterBySource(logs: LogEntry[], sources: LogSource[]): LogEntry[] {
|
|
9
|
+
return logs.filter(log => sources.includes(log.source));
|
|
10
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logs Package Entry Point
|
|
3
|
+
* Logging infrastructure for JetStart
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export * from './server';
|
|
7
|
+
export * from './cli';
|
|
8
|
+
export * from './filters';
|
|
9
|
+
export * from './types';
|
|
10
|
+
export * from './utils';
|
|
11
|
+
|
|
12
|
+
// Re-export shared log types
|
|
13
|
+
export type {
|
|
14
|
+
LogEntry,
|
|
15
|
+
LogLevel,
|
|
16
|
+
LogSource,
|
|
17
|
+
LogFilter,
|
|
18
|
+
LogStats,
|
|
19
|
+
} from '@jetstart/shared';
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logs Server
|
|
3
|
+
* WebSocket server for streaming logs
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { WebSocketServer, WebSocket } from 'ws';
|
|
7
|
+
import { LogStorage } from './storage';
|
|
8
|
+
import { LogEntry, LogFilter, DEFAULT_LOGS_PORT } from '@jetstart/shared';
|
|
9
|
+
import { applyFilters } from '../filters';
|
|
10
|
+
import chalk from 'chalk';
|
|
11
|
+
|
|
12
|
+
export interface LogsServerConfig {
|
|
13
|
+
port?: number;
|
|
14
|
+
maxLogEntries?: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class LogsServer {
|
|
18
|
+
private wss?: WebSocketServer;
|
|
19
|
+
private storage: LogStorage;
|
|
20
|
+
private clients: Set<WebSocket> = new Set();
|
|
21
|
+
private config: Required<LogsServerConfig>;
|
|
22
|
+
|
|
23
|
+
constructor(config: LogsServerConfig = {}) {
|
|
24
|
+
this.config = {
|
|
25
|
+
port: config.port || DEFAULT_LOGS_PORT,
|
|
26
|
+
maxLogEntries: config.maxLogEntries || 10000,
|
|
27
|
+
};
|
|
28
|
+
this.storage = new LogStorage(this.config.maxLogEntries);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async start(): Promise<void> {
|
|
32
|
+
this.wss = new WebSocketServer({ port: this.config.port });
|
|
33
|
+
|
|
34
|
+
this.wss.on('connection', (ws: WebSocket) => {
|
|
35
|
+
console.log(chalk.green('✔'), 'Client connected to logs service');
|
|
36
|
+
this.clients.add(ws);
|
|
37
|
+
|
|
38
|
+
ws.on('message', (data: Buffer) => {
|
|
39
|
+
this.handleMessage(ws, data);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
ws.on('close', () => {
|
|
43
|
+
console.log(chalk.yellow('⚠'), 'Client disconnected from logs service');
|
|
44
|
+
this.clients.delete(ws);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
ws.on('error', (err: Error) => {
|
|
48
|
+
console.error(chalk.red('✖'), 'WebSocket error:', err.message);
|
|
49
|
+
this.clients.delete(ws);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
console.log(chalk.cyan('[Logs]'), `Server listening on port ${this.config.port}`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async stop(): Promise<void> {
|
|
57
|
+
if (this.wss) {
|
|
58
|
+
this.wss.close();
|
|
59
|
+
console.log(chalk.cyan('[Logs]'), 'Server stopped');
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
addLog(entry: LogEntry): void {
|
|
64
|
+
this.storage.add(entry);
|
|
65
|
+
this.broadcast(entry);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
getLogs(filter?: LogFilter): LogEntry[] {
|
|
69
|
+
const logs = this.storage.getAll();
|
|
70
|
+
return filter ? applyFilters(logs, filter) : logs;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
getStats(): any {
|
|
74
|
+
return this.storage.getStats();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
private handleMessage(ws: WebSocket, data: Buffer): void {
|
|
78
|
+
try {
|
|
79
|
+
const message = JSON.parse(data.toString());
|
|
80
|
+
|
|
81
|
+
switch (message.type) {
|
|
82
|
+
case 'subscribe':
|
|
83
|
+
this.handleSubscribe(ws, message.filter, message.maxLines);
|
|
84
|
+
break;
|
|
85
|
+
case 'log':
|
|
86
|
+
this.addLog(message.log);
|
|
87
|
+
break;
|
|
88
|
+
case 'clear':
|
|
89
|
+
this.storage.clear();
|
|
90
|
+
break;
|
|
91
|
+
case 'stats':
|
|
92
|
+
this.sendStats(ws);
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
} catch (err: any) {
|
|
96
|
+
console.error(chalk.red('✖'), 'Failed to parse message:', err.message);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
private handleSubscribe(ws: WebSocket, filter?: LogFilter, maxLines?: number): void {
|
|
101
|
+
const logs = this.getLogs(filter);
|
|
102
|
+
const recentLogs = maxLines ? logs.slice(-maxLines) : logs;
|
|
103
|
+
|
|
104
|
+
// Send existing logs
|
|
105
|
+
recentLogs.forEach(log => {
|
|
106
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
107
|
+
ws.send(JSON.stringify(log));
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
private broadcast(entry: LogEntry): void {
|
|
113
|
+
const data = JSON.stringify(entry);
|
|
114
|
+
|
|
115
|
+
this.clients.forEach(ws => {
|
|
116
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
117
|
+
try {
|
|
118
|
+
ws.send(data);
|
|
119
|
+
} catch (err) {
|
|
120
|
+
console.error(chalk.red('✖'), 'Failed to broadcast log');
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
private sendStats(ws: WebSocket): void {
|
|
127
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
128
|
+
ws.send(JSON.stringify({
|
|
129
|
+
type: 'stats',
|
|
130
|
+
stats: this.storage.getStats(),
|
|
131
|
+
}));
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|