@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.
Files changed (45) hide show
  1. package/.eslintrc.json +6 -0
  2. package/README.md +122 -0
  3. package/dist/cli/formatter.d.ts +8 -0
  4. package/dist/cli/formatter.js +60 -0
  5. package/dist/cli/index.d.ts +9 -0
  6. package/dist/cli/index.js +24 -0
  7. package/dist/cli/viewer.d.ts +13 -0
  8. package/dist/cli/viewer.js +63 -0
  9. package/dist/filters/index.d.ts +10 -0
  10. package/dist/filters/index.js +45 -0
  11. package/dist/filters/level.d.ts +8 -0
  12. package/dist/filters/level.js +28 -0
  13. package/dist/filters/search.d.ts +7 -0
  14. package/dist/filters/search.js +29 -0
  15. package/dist/filters/source.d.ts +7 -0
  16. package/dist/filters/source.js +11 -0
  17. package/dist/index.d.ts +11 -0
  18. package/dist/index.js +26 -0
  19. package/dist/server/index.d.ts +26 -0
  20. package/dist/server/index.js +119 -0
  21. package/dist/server/storage.d.ts +15 -0
  22. package/dist/server/storage.js +70 -0
  23. package/dist/types/index.d.ts +14 -0
  24. package/dist/types/index.js +6 -0
  25. package/dist/utils/colors.d.ts +9 -0
  26. package/dist/utils/colors.js +50 -0
  27. package/dist/utils/index.d.ts +5 -0
  28. package/dist/utils/index.js +21 -0
  29. package/package.json +65 -0
  30. package/src/cli/formatter.ts +63 -0
  31. package/src/cli/index.ts +24 -0
  32. package/src/cli/viewer.ts +64 -0
  33. package/src/filters/index.ts +51 -0
  34. package/src/filters/level.ts +28 -0
  35. package/src/filters/search.ts +32 -0
  36. package/src/filters/source.ts +10 -0
  37. package/src/index.ts +19 -0
  38. package/src/server/index.ts +134 -0
  39. package/src/server/storage.ts +77 -0
  40. package/src/types/index.ts +15 -0
  41. package/src/utils/colors.ts +45 -0
  42. package/src/utils/index.ts +5 -0
  43. package/tests/filters.test.ts +81 -0
  44. package/tests/storage.test.ts +89 -0
  45. 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,6 @@
1
+ "use strict";
2
+ /**
3
+ * Logs-specific types
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ //# sourceMappingURL=index.js.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,5 @@
1
+ /**
2
+ * Export all utilities
3
+ */
4
+ export * from './colors';
5
+ //# sourceMappingURL=index.d.ts.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
+ }
@@ -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
+ }