@superdangerous/app-framework 4.9.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/LICENSE +21 -0
- package/README.md +652 -0
- package/dist/api/logsRouter.d.ts +20 -0
- package/dist/api/logsRouter.d.ts.map +1 -0
- package/dist/api/logsRouter.js +515 -0
- package/dist/api/logsRouter.js.map +1 -0
- package/dist/cli/dev-server.d.ts +7 -0
- package/dist/cli/dev-server.d.ts.map +1 -0
- package/dist/cli/dev-server.js +640 -0
- package/dist/cli/dev-server.js.map +1 -0
- package/dist/cli/index.d.ts +7 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +26 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/core/StandardServer.d.ts +129 -0
- package/dist/core/StandardServer.d.ts.map +1 -0
- package/dist/core/StandardServer.js +453 -0
- package/dist/core/StandardServer.js.map +1 -0
- package/dist/core/apiResponse.d.ts +69 -0
- package/dist/core/apiResponse.d.ts.map +1 -0
- package/dist/core/apiResponse.js +127 -0
- package/dist/core/apiResponse.js.map +1 -0
- package/dist/core/healthCheck.d.ts +160 -0
- package/dist/core/healthCheck.d.ts.map +1 -0
- package/dist/core/healthCheck.js +398 -0
- package/dist/core/healthCheck.js.map +1 -0
- package/dist/core/index.d.ts +40 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +40 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/logger.d.ts +117 -0
- package/dist/core/logger.d.ts.map +1 -0
- package/dist/core/logger.js +826 -0
- package/dist/core/logger.js.map +1 -0
- package/dist/core/portUtils.d.ts +71 -0
- package/dist/core/portUtils.d.ts.map +1 -0
- package/dist/core/portUtils.js +240 -0
- package/dist/core/portUtils.js.map +1 -0
- package/dist/core/storageService.d.ts +119 -0
- package/dist/core/storageService.d.ts.map +1 -0
- package/dist/core/storageService.js +405 -0
- package/dist/core/storageService.js.map +1 -0
- package/dist/desktop/bundler.d.ts +40 -0
- package/dist/desktop/bundler.d.ts.map +1 -0
- package/dist/desktop/bundler.js +176 -0
- package/dist/desktop/bundler.js.map +1 -0
- package/dist/desktop/index.d.ts +25 -0
- package/dist/desktop/index.d.ts.map +1 -0
- package/dist/desktop/index.js +15 -0
- package/dist/desktop/index.js.map +1 -0
- package/dist/desktop/native-modules.d.ts +66 -0
- package/dist/desktop/native-modules.d.ts.map +1 -0
- package/dist/desktop/native-modules.js +200 -0
- package/dist/desktop/native-modules.js.map +1 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +39 -0
- package/dist/index.js.map +1 -0
- package/dist/logging/LogCategories.d.ts +87 -0
- package/dist/logging/LogCategories.d.ts.map +1 -0
- package/dist/logging/LogCategories.js +205 -0
- package/dist/logging/LogCategories.js.map +1 -0
- package/dist/middleware/aiErrorHandler.d.ts +31 -0
- package/dist/middleware/aiErrorHandler.d.ts.map +1 -0
- package/dist/middleware/aiErrorHandler.js +181 -0
- package/dist/middleware/aiErrorHandler.js.map +1 -0
- package/dist/middleware/auth.d.ts +101 -0
- package/dist/middleware/auth.d.ts.map +1 -0
- package/dist/middleware/auth.js +230 -0
- package/dist/middleware/auth.js.map +1 -0
- package/dist/middleware/cors.d.ts +56 -0
- package/dist/middleware/cors.d.ts.map +1 -0
- package/dist/middleware/cors.js +123 -0
- package/dist/middleware/cors.js.map +1 -0
- package/dist/middleware/errorHandler.d.ts +13 -0
- package/dist/middleware/errorHandler.d.ts.map +1 -0
- package/dist/middleware/errorHandler.js +85 -0
- package/dist/middleware/errorHandler.js.map +1 -0
- package/dist/middleware/fileUpload.d.ts +62 -0
- package/dist/middleware/fileUpload.d.ts.map +1 -0
- package/dist/middleware/fileUpload.js +175 -0
- package/dist/middleware/fileUpload.js.map +1 -0
- package/dist/middleware/health.d.ts +48 -0
- package/dist/middleware/health.d.ts.map +1 -0
- package/dist/middleware/health.js +143 -0
- package/dist/middleware/health.js.map +1 -0
- package/dist/middleware/index.d.ts +20 -0
- package/dist/middleware/index.d.ts.map +1 -0
- package/dist/middleware/index.js +18 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/middleware/openapi.d.ts +64 -0
- package/dist/middleware/openapi.d.ts.map +1 -0
- package/dist/middleware/openapi.js +258 -0
- package/dist/middleware/openapi.js.map +1 -0
- package/dist/middleware/requestLogging.d.ts +22 -0
- package/dist/middleware/requestLogging.d.ts.map +1 -0
- package/dist/middleware/requestLogging.js +61 -0
- package/dist/middleware/requestLogging.js.map +1 -0
- package/dist/middleware/session.d.ts +84 -0
- package/dist/middleware/session.d.ts.map +1 -0
- package/dist/middleware/session.js +189 -0
- package/dist/middleware/session.js.map +1 -0
- package/dist/middleware/validation.d.ts +1337 -0
- package/dist/middleware/validation.d.ts.map +1 -0
- package/dist/middleware/validation.js +483 -0
- package/dist/middleware/validation.js.map +1 -0
- package/dist/services/aiService.d.ts +180 -0
- package/dist/services/aiService.d.ts.map +1 -0
- package/dist/services/aiService.js +547 -0
- package/dist/services/aiService.js.map +1 -0
- package/dist/services/conversationStorage.d.ts +38 -0
- package/dist/services/conversationStorage.d.ts.map +1 -0
- package/dist/services/conversationStorage.js +158 -0
- package/dist/services/conversationStorage.js.map +1 -0
- package/dist/services/crossPlatformBuffer.d.ts +84 -0
- package/dist/services/crossPlatformBuffer.d.ts.map +1 -0
- package/dist/services/crossPlatformBuffer.js +246 -0
- package/dist/services/crossPlatformBuffer.js.map +1 -0
- package/dist/services/index.d.ts +17 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +18 -0
- package/dist/services/index.js.map +1 -0
- package/dist/services/networkService.d.ts +81 -0
- package/dist/services/networkService.d.ts.map +1 -0
- package/dist/services/networkService.js +268 -0
- package/dist/services/networkService.js.map +1 -0
- package/dist/services/queueService.d.ts +112 -0
- package/dist/services/queueService.d.ts.map +1 -0
- package/dist/services/queueService.js +338 -0
- package/dist/services/queueService.js.map +1 -0
- package/dist/services/settingsService.d.ts +135 -0
- package/dist/services/settingsService.d.ts.map +1 -0
- package/dist/services/settingsService.js +425 -0
- package/dist/services/settingsService.js.map +1 -0
- package/dist/services/systemMonitor.d.ts +208 -0
- package/dist/services/systemMonitor.d.ts.map +1 -0
- package/dist/services/systemMonitor.js +693 -0
- package/dist/services/systemMonitor.js.map +1 -0
- package/dist/services/updateService.d.ts +78 -0
- package/dist/services/updateService.d.ts.map +1 -0
- package/dist/services/updateService.js +252 -0
- package/dist/services/updateService.js.map +1 -0
- package/dist/services/websocketEvents.d.ts +372 -0
- package/dist/services/websocketEvents.d.ts.map +1 -0
- package/dist/services/websocketEvents.js +338 -0
- package/dist/services/websocketEvents.js.map +1 -0
- package/dist/services/websocketServer.d.ts +80 -0
- package/dist/services/websocketServer.d.ts.map +1 -0
- package/dist/services/websocketServer.js +299 -0
- package/dist/services/websocketServer.js.map +1 -0
- package/dist/settings/SettingsSchema.d.ts +151 -0
- package/dist/settings/SettingsSchema.d.ts.map +1 -0
- package/dist/settings/SettingsSchema.js +424 -0
- package/dist/settings/SettingsSchema.js.map +1 -0
- package/dist/testing/TestServer.d.ts +69 -0
- package/dist/testing/TestServer.d.ts.map +1 -0
- package/dist/testing/TestServer.js +250 -0
- package/dist/testing/TestServer.js.map +1 -0
- package/dist/types/index.d.ts +137 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +5 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/appPaths.d.ts +74 -0
- package/dist/utils/appPaths.d.ts.map +1 -0
- package/dist/utils/appPaths.js +162 -0
- package/dist/utils/appPaths.js.map +1 -0
- package/dist/utils/fs-utils.d.ts +50 -0
- package/dist/utils/fs-utils.d.ts.map +1 -0
- package/dist/utils/fs-utils.js +114 -0
- package/dist/utils/fs-utils.js.map +1 -0
- package/dist/utils/index.d.ts +12 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +10 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/standardConfig.d.ts +61 -0
- package/dist/utils/standardConfig.d.ts.map +1 -0
- package/dist/utils/standardConfig.js +109 -0
- package/dist/utils/standardConfig.js.map +1 -0
- package/dist/utils/startupBanner.d.ts +34 -0
- package/dist/utils/startupBanner.d.ts.map +1 -0
- package/dist/utils/startupBanner.js +169 -0
- package/dist/utils/startupBanner.js.map +1 -0
- package/dist/utils/startupLogger.d.ts +45 -0
- package/dist/utils/startupLogger.d.ts.map +1 -0
- package/dist/utils/startupLogger.js +200 -0
- package/dist/utils/startupLogger.js.map +1 -0
- package/package.json +151 -0
- package/src/api/logsRouter.ts +600 -0
- package/src/cli/dev-server.ts +803 -0
- package/src/cli/index.ts +31 -0
- package/src/core/StandardServer.ts +587 -0
- package/src/core/apiResponse.ts +202 -0
- package/src/core/healthCheck.ts +565 -0
- package/src/core/index.ts +80 -0
- package/src/core/logger.ts +1092 -0
- package/src/core/portUtils.ts +319 -0
- package/src/core/storageService.ts +595 -0
- package/src/desktop/bundler.ts +271 -0
- package/src/desktop/index.ts +18 -0
- package/src/desktop/native-modules.ts +289 -0
- package/src/index.ts +142 -0
- package/src/logging/LogCategories.ts +302 -0
- package/src/middleware/aiErrorHandler.ts +278 -0
- package/src/middleware/auth.ts +329 -0
- package/src/middleware/cors.ts +187 -0
- package/src/middleware/errorHandler.ts +103 -0
- package/src/middleware/fileUpload.ts +252 -0
- package/src/middleware/health.ts +206 -0
- package/src/middleware/index.ts +71 -0
- package/src/middleware/openapi.ts +305 -0
- package/src/middleware/requestLogging.ts +92 -0
- package/src/middleware/session.ts +238 -0
- package/src/middleware/validation.ts +603 -0
- package/src/services/aiService.ts +789 -0
- package/src/services/conversationStorage.ts +232 -0
- package/src/services/crossPlatformBuffer.ts +341 -0
- package/src/services/index.ts +47 -0
- package/src/services/networkService.ts +351 -0
- package/src/services/queueService.ts +446 -0
- package/src/services/settingsService.ts +549 -0
- package/src/services/systemMonitor.ts +936 -0
- package/src/services/updateService.ts +334 -0
- package/src/services/websocketEvents.ts +409 -0
- package/src/services/websocketServer.ts +394 -0
- package/src/settings/SettingsSchema.ts +664 -0
- package/src/testing/TestServer.ts +312 -0
- package/src/types/index.ts +154 -0
- package/src/utils/appPaths.ts +196 -0
- package/src/utils/fs-utils.ts +130 -0
- package/src/utils/index.ts +15 -0
- package/src/utils/standardConfig.ts +178 -0
- package/src/utils/startupBanner.ts +287 -0
- package/src/utils/startupLogger.ts +268 -0
- package/ui/dist/index.d.mts +1221 -0
- package/ui/dist/index.d.ts +1221 -0
- package/ui/dist/index.js +73 -0
- package/ui/dist/index.js.map +1 -0
- package/ui/dist/index.mjs +73 -0
- package/ui/dist/index.mjs.map +1 -0
|
@@ -0,0 +1,826 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logger Service
|
|
3
|
+
* Provides centralized logging with rotation, compression, and management
|
|
4
|
+
*/
|
|
5
|
+
import winston, { format, transports } from "winston";
|
|
6
|
+
import DailyRotateFile from "winston-daily-rotate-file";
|
|
7
|
+
import path from "path";
|
|
8
|
+
import fs from "fs/promises";
|
|
9
|
+
import { createReadStream, createWriteStream, existsSync, } from "fs";
|
|
10
|
+
import { createGzip } from "zlib";
|
|
11
|
+
import { pipeline } from "stream/promises";
|
|
12
|
+
// Use process.cwd() based paths for better compatibility
|
|
13
|
+
const getLogsDir = () => path.join(process.cwd(), "data", "logs");
|
|
14
|
+
// Log levels with colors
|
|
15
|
+
const logLevels = {
|
|
16
|
+
levels: {
|
|
17
|
+
error: 0,
|
|
18
|
+
warn: 1,
|
|
19
|
+
info: 2,
|
|
20
|
+
http: 3,
|
|
21
|
+
verbose: 4,
|
|
22
|
+
debug: 5,
|
|
23
|
+
silly: 6,
|
|
24
|
+
},
|
|
25
|
+
colors: {
|
|
26
|
+
error: "red",
|
|
27
|
+
warn: "yellow",
|
|
28
|
+
info: "green",
|
|
29
|
+
http: "magenta",
|
|
30
|
+
verbose: "cyan",
|
|
31
|
+
debug: "blue",
|
|
32
|
+
silly: "gray",
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
// Apply colors to winston
|
|
36
|
+
winston.addColors(logLevels.colors);
|
|
37
|
+
class Logger {
|
|
38
|
+
logsDir;
|
|
39
|
+
archiveDir;
|
|
40
|
+
loggers;
|
|
41
|
+
initialized;
|
|
42
|
+
messageQueue;
|
|
43
|
+
initPromise;
|
|
44
|
+
constructor() {
|
|
45
|
+
this.logsDir = getLogsDir(); // Now defaults to data/logs
|
|
46
|
+
this.archiveDir = path.join(this.logsDir, "archive");
|
|
47
|
+
this.loggers = new Map();
|
|
48
|
+
this.initialized = false;
|
|
49
|
+
this.messageQueue = [];
|
|
50
|
+
}
|
|
51
|
+
async initialize(options) {
|
|
52
|
+
if (this.initialized)
|
|
53
|
+
return;
|
|
54
|
+
// If already initializing, wait for it
|
|
55
|
+
if (this.initPromise) {
|
|
56
|
+
return this.initPromise;
|
|
57
|
+
}
|
|
58
|
+
// Start initialization
|
|
59
|
+
this.initPromise = this._doInitialize(options);
|
|
60
|
+
return this.initPromise;
|
|
61
|
+
}
|
|
62
|
+
async _doInitialize(options) {
|
|
63
|
+
// Apply options if provided
|
|
64
|
+
if (options?.logsDir) {
|
|
65
|
+
this.logsDir = options.logsDir;
|
|
66
|
+
}
|
|
67
|
+
// Create log directories
|
|
68
|
+
await this.ensureDirectories();
|
|
69
|
+
// Clean up old root-level log files and subdirectories
|
|
70
|
+
await this.cleanupOldLogs();
|
|
71
|
+
await this.cleanupSubdirectories();
|
|
72
|
+
// Create default logger with options
|
|
73
|
+
this.createLogger(options?.appName || "system", {
|
|
74
|
+
level: options?.logLevel || "info",
|
|
75
|
+
console: options?.consoleOutput !== false,
|
|
76
|
+
file: options?.fileOutput !== false,
|
|
77
|
+
});
|
|
78
|
+
this.initialized = true;
|
|
79
|
+
// Process any queued messages after a small delay to ensure transports are ready
|
|
80
|
+
setTimeout(() => this.processMessageQueue(), 100);
|
|
81
|
+
}
|
|
82
|
+
processMessageQueue() {
|
|
83
|
+
if (this.messageQueue.length === 0)
|
|
84
|
+
return;
|
|
85
|
+
const messages = [...this.messageQueue];
|
|
86
|
+
this.messageQueue = [];
|
|
87
|
+
for (const { name, level, args } of messages) {
|
|
88
|
+
const logger = this.createLogger(name);
|
|
89
|
+
if (logger) {
|
|
90
|
+
// Use a type-safe way to call the logger method
|
|
91
|
+
switch (level) {
|
|
92
|
+
case "info":
|
|
93
|
+
logger.info.apply(logger, args);
|
|
94
|
+
break;
|
|
95
|
+
case "error":
|
|
96
|
+
logger.error.apply(logger, args);
|
|
97
|
+
break;
|
|
98
|
+
case "warn":
|
|
99
|
+
logger.warn.apply(logger, args);
|
|
100
|
+
break;
|
|
101
|
+
case "debug":
|
|
102
|
+
logger.debug.apply(logger, args);
|
|
103
|
+
break;
|
|
104
|
+
case "verbose":
|
|
105
|
+
logger.verbose.apply(logger, args);
|
|
106
|
+
break;
|
|
107
|
+
case "http":
|
|
108
|
+
logger.http.apply(logger, args);
|
|
109
|
+
break;
|
|
110
|
+
case "silly":
|
|
111
|
+
logger.silly.apply(logger, args);
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
isInitialized() {
|
|
118
|
+
return this.initialized;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Get recent log entries from memory or file
|
|
122
|
+
*/
|
|
123
|
+
async getRecentLogs(limit = 100, level = "all") {
|
|
124
|
+
const logsDir = this.logsDir;
|
|
125
|
+
try {
|
|
126
|
+
// Find the most recent log file
|
|
127
|
+
const files = await fs.readdir(logsDir);
|
|
128
|
+
const logFiles = files.filter((f) => f.startsWith("app-") && f.endsWith(".log"));
|
|
129
|
+
if (logFiles.length === 0) {
|
|
130
|
+
// Fallback to app.log if no dated files exist
|
|
131
|
+
const fallbackFile = path.join(logsDir, "app.log");
|
|
132
|
+
if (!existsSync(fallbackFile)) {
|
|
133
|
+
return [];
|
|
134
|
+
}
|
|
135
|
+
logFiles.push("app.log");
|
|
136
|
+
}
|
|
137
|
+
// Sort to get most recent
|
|
138
|
+
logFiles.sort().reverse();
|
|
139
|
+
const logFile = path.join(logsDir, logFiles[0]);
|
|
140
|
+
const content = await fs.readFile(logFile, "utf-8");
|
|
141
|
+
const lines = content.split("\n").filter((line) => line.trim());
|
|
142
|
+
// Parse log lines into structured format
|
|
143
|
+
const logs = lines.map((line) => {
|
|
144
|
+
// Try to parse JSON format first
|
|
145
|
+
try {
|
|
146
|
+
const jsonLog = JSON.parse(line);
|
|
147
|
+
return {
|
|
148
|
+
timestamp: jsonLog.timestamp || new Date().toISOString(),
|
|
149
|
+
level: (jsonLog.level || "info").toLowerCase(),
|
|
150
|
+
source: jsonLog.source || jsonLog.service || undefined,
|
|
151
|
+
message: jsonLog.message || jsonLog.msg || line,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
// Try to parse standard text format: [timestamp] [level] [source] message
|
|
156
|
+
const match = line.match(/\[([\d-T:.Z]+)\]\s*\[(\w+)\]\s*(?:\[([^\]]+)\])?\s*(.*)/);
|
|
157
|
+
if (match) {
|
|
158
|
+
return {
|
|
159
|
+
timestamp: match[1],
|
|
160
|
+
level: match[2].toLowerCase(),
|
|
161
|
+
source: match[3] || undefined,
|
|
162
|
+
message: match[4],
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
// Fallback for non-standard format
|
|
166
|
+
return {
|
|
167
|
+
timestamp: new Date().toISOString(),
|
|
168
|
+
level: "info",
|
|
169
|
+
message: line,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
// Filter by level if specified
|
|
174
|
+
const filtered = level === "all"
|
|
175
|
+
? logs
|
|
176
|
+
: logs.filter((log) => log.level === level.toLowerCase());
|
|
177
|
+
// Return most recent entries, newest first
|
|
178
|
+
return filtered.slice(-limit).reverse();
|
|
179
|
+
}
|
|
180
|
+
catch (_error) {
|
|
181
|
+
console.error("Failed to read logs:", _error);
|
|
182
|
+
return [];
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Clear all log files
|
|
187
|
+
*/
|
|
188
|
+
async clearLogs() {
|
|
189
|
+
const logsDir = this.logsDir;
|
|
190
|
+
try {
|
|
191
|
+
if (existsSync(logsDir)) {
|
|
192
|
+
const files = await fs.readdir(logsDir);
|
|
193
|
+
for (const file of files) {
|
|
194
|
+
if (file.endsWith(".log") || file.endsWith(".txt")) {
|
|
195
|
+
await fs.unlink(path.join(logsDir, file));
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
catch (_error) {
|
|
201
|
+
console.error("Failed to clear logs:", _error);
|
|
202
|
+
throw _error;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
child(name) {
|
|
206
|
+
// Return a smart proxy that handles both pre and post initialization
|
|
207
|
+
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
208
|
+
const self = this;
|
|
209
|
+
const logMethod = (level) => {
|
|
210
|
+
return (...args) => {
|
|
211
|
+
if (self.initialized) {
|
|
212
|
+
// Use the real logger
|
|
213
|
+
const logger = self.createLogger(name);
|
|
214
|
+
if (logger) {
|
|
215
|
+
const method = logger[level];
|
|
216
|
+
if (typeof method === "function") {
|
|
217
|
+
method.apply(logger, args);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
// Log to console immediately
|
|
223
|
+
const prefix = level === "error"
|
|
224
|
+
? `[${name} ERROR]`
|
|
225
|
+
: level === "warn"
|
|
226
|
+
? `[${name} WARN]`
|
|
227
|
+
: level === "debug"
|
|
228
|
+
? `[${name} DEBUG]`
|
|
229
|
+
: `[${name}]`;
|
|
230
|
+
const consoleFn = level === "error"
|
|
231
|
+
? console.error
|
|
232
|
+
: level === "warn"
|
|
233
|
+
? console.warn
|
|
234
|
+
: level === "debug"
|
|
235
|
+
? console.debug
|
|
236
|
+
: console.log;
|
|
237
|
+
consoleFn(prefix, ...args);
|
|
238
|
+
// Queue for file logging
|
|
239
|
+
self.messageQueue.push({ name, level, args });
|
|
240
|
+
// Trigger initialization if not already started
|
|
241
|
+
if (!self.initPromise) {
|
|
242
|
+
self
|
|
243
|
+
.initialize({
|
|
244
|
+
appName: "app",
|
|
245
|
+
logLevel: process.env.LOG_LEVEL || "info",
|
|
246
|
+
consoleOutput: true,
|
|
247
|
+
fileOutput: true,
|
|
248
|
+
logsDir: path.join(process.cwd(), "data", "logs"),
|
|
249
|
+
})
|
|
250
|
+
.catch((err) => {
|
|
251
|
+
console.error("[Logger] Failed to initialize:", err);
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
};
|
|
257
|
+
return {
|
|
258
|
+
info: logMethod("info"),
|
|
259
|
+
error: logMethod("error"),
|
|
260
|
+
warn: logMethod("warn"),
|
|
261
|
+
debug: logMethod("debug"),
|
|
262
|
+
verbose: logMethod("verbose"),
|
|
263
|
+
http: logMethod("http"),
|
|
264
|
+
silly: logMethod("silly"),
|
|
265
|
+
logRequest: (req, res, duration) => {
|
|
266
|
+
if (self.initialized) {
|
|
267
|
+
const real = self.createLogger(name);
|
|
268
|
+
real?.logRequest?.(req, res, duration);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
logMethod("info")(`Request ${req.method} ${req.url} completed in ${duration}ms`);
|
|
272
|
+
},
|
|
273
|
+
logError: (error, context = {}) => {
|
|
274
|
+
if (self.initialized) {
|
|
275
|
+
const real = self.createLogger(name);
|
|
276
|
+
real?.logError?.(error, context);
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
logMethod("error")(error, context);
|
|
280
|
+
},
|
|
281
|
+
compactLogs: (daysToKeep) => self.compactLogs(daysToKeep),
|
|
282
|
+
getLogStats: () => self.getLogStats(),
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
async ensureDirectories() {
|
|
286
|
+
try {
|
|
287
|
+
// Create only the main logs directory and archive subdirectory
|
|
288
|
+
await fs.mkdir(this.logsDir, { recursive: true });
|
|
289
|
+
await fs.mkdir(this.archiveDir, { recursive: true });
|
|
290
|
+
}
|
|
291
|
+
catch (_error) {
|
|
292
|
+
console.error("Failed to create log directories:", _error);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Clean up subdirectories to maintain flat structure
|
|
297
|
+
*/
|
|
298
|
+
async cleanupSubdirectories() {
|
|
299
|
+
try {
|
|
300
|
+
const items = await fs.readdir(this.logsDir);
|
|
301
|
+
for (const item of items) {
|
|
302
|
+
if (item === "archive")
|
|
303
|
+
continue; // Keep archive directory
|
|
304
|
+
const itemPath = path.join(this.logsDir, item);
|
|
305
|
+
const stat = await fs.stat(itemPath);
|
|
306
|
+
if (stat.isDirectory()) {
|
|
307
|
+
// Remove empty subdirectories
|
|
308
|
+
try {
|
|
309
|
+
const contents = await fs.readdir(itemPath);
|
|
310
|
+
if (contents.length === 0) {
|
|
311
|
+
await fs.rmdir(itemPath);
|
|
312
|
+
console.log(`Removed empty log subdirectory: ${item}`);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
catch (_err) {
|
|
316
|
+
console.warn(`Could not clean up subdirectory ${item}:`, _err);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
catch (_error) {
|
|
322
|
+
// Ignore errors during cleanup
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
async cleanupOldLogs() {
|
|
326
|
+
const rootDir = process.cwd();
|
|
327
|
+
const topLevelLogsDir = path.join(rootDir, "logs");
|
|
328
|
+
// Check if there's a top-level logs directory
|
|
329
|
+
if (existsSync(topLevelLogsDir)) {
|
|
330
|
+
try {
|
|
331
|
+
// Move any log files to data/logs
|
|
332
|
+
const files = await fs.readdir(topLevelLogsDir);
|
|
333
|
+
for (const file of files) {
|
|
334
|
+
if (file.endsWith(".log") || file.endsWith(".txt")) {
|
|
335
|
+
const oldPath = path.join(topLevelLogsDir, file);
|
|
336
|
+
const newPath = path.join(this.logsDir, file);
|
|
337
|
+
try {
|
|
338
|
+
await fs.rename(oldPath, newPath);
|
|
339
|
+
console.log(`Moved log file from /logs to /data/logs: ${file}`);
|
|
340
|
+
}
|
|
341
|
+
catch (_err) {
|
|
342
|
+
// File might not exist or already moved
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
// Remove top-level logs directory if empty
|
|
347
|
+
const remainingFiles = await fs.readdir(topLevelLogsDir);
|
|
348
|
+
const hasOnlyDsStore = remainingFiles.length === 1 && remainingFiles[0] === ".DS_Store";
|
|
349
|
+
if (remainingFiles.length === 0 || hasOnlyDsStore) {
|
|
350
|
+
if (hasOnlyDsStore) {
|
|
351
|
+
await fs
|
|
352
|
+
.unlink(path.join(topLevelLogsDir, ".DS_Store"))
|
|
353
|
+
.catch(() => { });
|
|
354
|
+
}
|
|
355
|
+
await fs.rmdir(topLevelLogsDir);
|
|
356
|
+
console.log("Removed empty top-level logs directory");
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
catch (_error) {
|
|
360
|
+
// Directory might not exist or already cleaned
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
createLogger(category = "general", options = {}) {
|
|
365
|
+
const loggerKey = `${category}-${JSON.stringify(options)}`;
|
|
366
|
+
if (this.loggers.has(loggerKey)) {
|
|
367
|
+
return this.loggers.get(loggerKey);
|
|
368
|
+
}
|
|
369
|
+
// Use flat structure - all logs in data/logs directly
|
|
370
|
+
const logDir = this.logsDir;
|
|
371
|
+
const level = options.level || process.env.LOG_LEVEL || "info";
|
|
372
|
+
// Console format with colors - no JSON output
|
|
373
|
+
const consoleFormat = format.combine(format.timestamp({ format: "HH:mm:ss.SSS" }), format.colorize(), format.printf(({ timestamp, level, message, source, ...meta }) => {
|
|
374
|
+
// Extract commonly used metadata
|
|
375
|
+
const sourceStr = source ? ` [${source}]` : "";
|
|
376
|
+
// For errors, show the error message inline
|
|
377
|
+
if (meta.error && typeof meta.error === "string") {
|
|
378
|
+
return `[${timestamp}] ${level}:${sourceStr} ${message}: ${meta.error}`;
|
|
379
|
+
}
|
|
380
|
+
// For other metadata, format key fields inline (no JSON)
|
|
381
|
+
const importantMeta = [];
|
|
382
|
+
// Handle all common metadata fields
|
|
383
|
+
for (const [key, value] of Object.entries(meta)) {
|
|
384
|
+
// Skip internal winston fields and stack traces
|
|
385
|
+
if (key === "stack" ||
|
|
386
|
+
key === "timestamp" ||
|
|
387
|
+
key === "level" ||
|
|
388
|
+
key === "message")
|
|
389
|
+
continue;
|
|
390
|
+
// Format the value appropriately
|
|
391
|
+
if (value !== undefined && value !== null) {
|
|
392
|
+
importantMeta.push(`${key}=${value}`);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
const metaStr = importantMeta.length > 0 ? ` (${importantMeta.join(", ")})` : "";
|
|
396
|
+
return `[${timestamp}] ${level}:${sourceStr} ${message}${metaStr}`;
|
|
397
|
+
}));
|
|
398
|
+
// File format (structured JSON for machine parsing)
|
|
399
|
+
// Strip ANSI codes from messages before writing to file
|
|
400
|
+
const ansiPattern = new RegExp("\\x1b\\[[0-9;]*m", "g"); // eslint-disable-line no-control-regex
|
|
401
|
+
const stripAnsi = format((info) => {
|
|
402
|
+
if (info.message && typeof info.message === "string") {
|
|
403
|
+
info.message = info.message.replace(ansiPattern, "");
|
|
404
|
+
}
|
|
405
|
+
if (info.source && typeof info.source === "string") {
|
|
406
|
+
info.source = info.source.replace(ansiPattern, "");
|
|
407
|
+
}
|
|
408
|
+
return info;
|
|
409
|
+
});
|
|
410
|
+
const fileFormat = format.combine(stripAnsi(), format.timestamp({ format: "YYYY-MM-DD HH:mm:ss.SSS" }), format.errors({ stack: true }), format.json());
|
|
411
|
+
// Create transports
|
|
412
|
+
const logTransports = [];
|
|
413
|
+
// Console transport
|
|
414
|
+
if (options.console !== false &&
|
|
415
|
+
(process.env.NODE_ENV !== "production" ||
|
|
416
|
+
process.env.LOG_TO_CONSOLE === "true")) {
|
|
417
|
+
logTransports.push(new transports.Console({
|
|
418
|
+
format: consoleFormat,
|
|
419
|
+
level: level,
|
|
420
|
+
}));
|
|
421
|
+
}
|
|
422
|
+
// File transports - flat structure in data/logs (skip in tests)
|
|
423
|
+
if (options.file !== false && process.env.NODE_ENV !== "test") {
|
|
424
|
+
// Daily rotating file transport for all logs
|
|
425
|
+
logTransports.push(new DailyRotateFile({
|
|
426
|
+
filename: path.join(logDir, "app-%DATE%.log"),
|
|
427
|
+
datePattern: "YYYY-MM-DD",
|
|
428
|
+
zippedArchive: options.compress !== false,
|
|
429
|
+
maxSize: options.maxSize || "20m",
|
|
430
|
+
maxFiles: options.maxFiles || "14d",
|
|
431
|
+
format: fileFormat,
|
|
432
|
+
level: level,
|
|
433
|
+
auditFile: path.join(logDir, ".app-audit.json"),
|
|
434
|
+
}));
|
|
435
|
+
// Separate error log file
|
|
436
|
+
logTransports.push(new DailyRotateFile({
|
|
437
|
+
filename: path.join(logDir, "error-%DATE%.log"),
|
|
438
|
+
datePattern: "YYYY-MM-DD",
|
|
439
|
+
zippedArchive: options.compress !== false,
|
|
440
|
+
maxSize: options.maxSize || "20m",
|
|
441
|
+
maxFiles: "30d",
|
|
442
|
+
format: fileFormat,
|
|
443
|
+
level: "error",
|
|
444
|
+
auditFile: path.join(logDir, ".error-audit.json"),
|
|
445
|
+
}));
|
|
446
|
+
}
|
|
447
|
+
// Create the logger
|
|
448
|
+
const logger = winston.createLogger({
|
|
449
|
+
levels: logLevels.levels,
|
|
450
|
+
transports: logTransports.filter(Boolean),
|
|
451
|
+
exitOnError: false,
|
|
452
|
+
defaultMeta: { source: category },
|
|
453
|
+
});
|
|
454
|
+
// Add convenience methods
|
|
455
|
+
logger.logRequest = (req, res, duration) => {
|
|
456
|
+
const logData = {
|
|
457
|
+
method: req.method,
|
|
458
|
+
url: req.url,
|
|
459
|
+
status: res.statusCode,
|
|
460
|
+
duration: `${duration}ms`,
|
|
461
|
+
ip: req.ip,
|
|
462
|
+
userAgent: req.get("user-agent"),
|
|
463
|
+
};
|
|
464
|
+
if (res.statusCode >= 400) {
|
|
465
|
+
logger.error("Request failed", logData);
|
|
466
|
+
}
|
|
467
|
+
else {
|
|
468
|
+
logger.http("Request completed", logData);
|
|
469
|
+
}
|
|
470
|
+
};
|
|
471
|
+
logger.logSimulator = (simulatorId, action, data = {}) => {
|
|
472
|
+
logger.info(`Simulator ${action}`, {
|
|
473
|
+
simulatorId,
|
|
474
|
+
action,
|
|
475
|
+
...data,
|
|
476
|
+
});
|
|
477
|
+
};
|
|
478
|
+
logger.logError = (error, context = {}) => {
|
|
479
|
+
logger.error(error.message, {
|
|
480
|
+
stack: error.stack,
|
|
481
|
+
name: error.name,
|
|
482
|
+
...context,
|
|
483
|
+
});
|
|
484
|
+
};
|
|
485
|
+
this.loggers.set(loggerKey, logger);
|
|
486
|
+
return logger;
|
|
487
|
+
}
|
|
488
|
+
async getLogStats() {
|
|
489
|
+
const stats = {
|
|
490
|
+
totalSize: 0,
|
|
491
|
+
fileCount: 0,
|
|
492
|
+
files: [],
|
|
493
|
+
oldestLog: undefined,
|
|
494
|
+
newestLog: undefined,
|
|
495
|
+
};
|
|
496
|
+
try {
|
|
497
|
+
const files = await fs.readdir(this.logsDir);
|
|
498
|
+
for (const file of files) {
|
|
499
|
+
// Skip directories and non-log files
|
|
500
|
+
if (file === "archive" || file.startsWith("."))
|
|
501
|
+
continue;
|
|
502
|
+
const filePath = path.join(this.logsDir, file);
|
|
503
|
+
const stat = await fs.stat(filePath);
|
|
504
|
+
if (stat.isFile() && (file.endsWith(".log") || file.endsWith(".gz"))) {
|
|
505
|
+
const fileInfo = {
|
|
506
|
+
name: file,
|
|
507
|
+
size: stat.size,
|
|
508
|
+
modified: stat.mtime,
|
|
509
|
+
compressed: file.endsWith(".gz"),
|
|
510
|
+
};
|
|
511
|
+
stats.files.push(fileInfo);
|
|
512
|
+
stats.totalSize += stat.size;
|
|
513
|
+
stats.fileCount++;
|
|
514
|
+
// Track oldest and newest
|
|
515
|
+
if (!stats.oldestLog || stat.mtime < stats.oldestLog) {
|
|
516
|
+
stats.oldestLog = stat.mtime;
|
|
517
|
+
}
|
|
518
|
+
if (!stats.newestLog || stat.mtime > stats.newestLog) {
|
|
519
|
+
stats.newestLog = stat.mtime;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
// Sort files by date (newest first)
|
|
524
|
+
stats.files.sort((a, b) => b.modified.getTime() - a.modified.getTime());
|
|
525
|
+
}
|
|
526
|
+
catch (_error) {
|
|
527
|
+
console.error("Failed to get log stats:", _error);
|
|
528
|
+
}
|
|
529
|
+
return stats;
|
|
530
|
+
}
|
|
531
|
+
async archiveLogs(olderThanDays = 7) {
|
|
532
|
+
const cutoffDate = new Date();
|
|
533
|
+
cutoffDate.setDate(cutoffDate.getDate() - olderThanDays);
|
|
534
|
+
try {
|
|
535
|
+
const files = await fs.readdir(this.logsDir);
|
|
536
|
+
for (const file of files) {
|
|
537
|
+
if (file === "archive" || file.startsWith("."))
|
|
538
|
+
continue;
|
|
539
|
+
const filePath = path.join(this.logsDir, file);
|
|
540
|
+
const stat = await fs.stat(filePath);
|
|
541
|
+
if (stat.isFile() && stat.mtime < cutoffDate) {
|
|
542
|
+
const archivePath = path.join(this.archiveDir, file);
|
|
543
|
+
if (file.endsWith(".log")) {
|
|
544
|
+
// Compress before archiving
|
|
545
|
+
const gzipPath = `${archivePath}.gz`;
|
|
546
|
+
const source = createReadStream(filePath);
|
|
547
|
+
const destination = createWriteStream(gzipPath);
|
|
548
|
+
const gzip = createGzip();
|
|
549
|
+
await pipeline(source, gzip, destination);
|
|
550
|
+
await fs.unlink(filePath);
|
|
551
|
+
}
|
|
552
|
+
else {
|
|
553
|
+
// Already compressed, just move
|
|
554
|
+
await fs.rename(filePath, archivePath);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
catch (_error) {
|
|
560
|
+
console.error("Failed to archive logs:", _error);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
async downloadLogFile(filename) {
|
|
564
|
+
const safeName = path.basename(filename); // Prevent path traversal
|
|
565
|
+
const logPath = path.join(this.logsDir, safeName);
|
|
566
|
+
const archivePath = path.join(this.archiveDir, safeName);
|
|
567
|
+
// Check in main logs directory first
|
|
568
|
+
if (existsSync(logPath)) {
|
|
569
|
+
return createReadStream(logPath);
|
|
570
|
+
}
|
|
571
|
+
// Check in archive
|
|
572
|
+
if (existsSync(archivePath)) {
|
|
573
|
+
return createReadStream(archivePath);
|
|
574
|
+
}
|
|
575
|
+
return null;
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
* Compact logs by archiving old files
|
|
579
|
+
*/
|
|
580
|
+
async compactLogs(daysToKeep = 7) {
|
|
581
|
+
await this.ensureDirectories();
|
|
582
|
+
const cutoffDate = new Date();
|
|
583
|
+
cutoffDate.setDate(cutoffDate.getDate() - daysToKeep);
|
|
584
|
+
let archivedCount = 0;
|
|
585
|
+
let totalSize = 0;
|
|
586
|
+
try {
|
|
587
|
+
const files = await fs.readdir(this.logsDir);
|
|
588
|
+
for (const file of files) {
|
|
589
|
+
if (!file.endsWith(".log"))
|
|
590
|
+
continue;
|
|
591
|
+
const filePath = path.join(this.logsDir, file);
|
|
592
|
+
const stats = await fs.stat(filePath);
|
|
593
|
+
if (stats.mtime < cutoffDate) {
|
|
594
|
+
// Archive the file
|
|
595
|
+
const archivePath = path.join(this.archiveDir, `${file}.gz`);
|
|
596
|
+
await pipeline(createReadStream(filePath), createGzip({ level: 9 }), createWriteStream(archivePath));
|
|
597
|
+
await fs.unlink(filePath);
|
|
598
|
+
archivedCount++;
|
|
599
|
+
totalSize += stats.size;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
// Get updated stats
|
|
603
|
+
const updatedStats = await this.getLogStats();
|
|
604
|
+
const appLogger = this.createLogger("system");
|
|
605
|
+
appLogger.info(`Compacted ${archivedCount} log files (${totalSize} bytes)`);
|
|
606
|
+
return updatedStats;
|
|
607
|
+
}
|
|
608
|
+
catch (_error) {
|
|
609
|
+
const appLogger = this.createLogger("system");
|
|
610
|
+
appLogger.error("Failed to compact logs:", _error);
|
|
611
|
+
throw _error;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* Clean up zero-length files and corrupted archives
|
|
616
|
+
*/
|
|
617
|
+
async cleanupZeroFiles() {
|
|
618
|
+
await this.ensureDirectories();
|
|
619
|
+
let removed = 0;
|
|
620
|
+
const directories = [this.logsDir, this.archiveDir];
|
|
621
|
+
for (const dir of directories) {
|
|
622
|
+
try {
|
|
623
|
+
const files = await fs.readdir(dir);
|
|
624
|
+
for (const file of files) {
|
|
625
|
+
if (file.endsWith(".gz") || file.endsWith(".log")) {
|
|
626
|
+
const filePath = path.join(dir, file);
|
|
627
|
+
const stats = await fs.stat(filePath);
|
|
628
|
+
if (stats.size === 0) {
|
|
629
|
+
await fs.unlink(filePath);
|
|
630
|
+
removed++;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
catch (_error) {
|
|
636
|
+
const appLogger = this.createLogger("system");
|
|
637
|
+
appLogger.warn(`Failed to clean directory ${dir}:`, _error);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
const appLogger = this.createLogger("system");
|
|
641
|
+
appLogger.info(`Cleaned up ${removed} zero-length files`);
|
|
642
|
+
return { removed };
|
|
643
|
+
}
|
|
644
|
+
/**
|
|
645
|
+
* Purge all logs (dangerous operation)
|
|
646
|
+
*/
|
|
647
|
+
async purgeAllLogs() {
|
|
648
|
+
const removeRecursive = async (dirPath) => {
|
|
649
|
+
if (!existsSync(dirPath))
|
|
650
|
+
return;
|
|
651
|
+
const entries = await fs.readdir(dirPath);
|
|
652
|
+
for (const entry of entries) {
|
|
653
|
+
const fullPath = path.join(dirPath, entry);
|
|
654
|
+
const stats = await fs.stat(fullPath);
|
|
655
|
+
if (stats.isDirectory()) {
|
|
656
|
+
await removeRecursive(fullPath);
|
|
657
|
+
await fs.rmdir(fullPath);
|
|
658
|
+
}
|
|
659
|
+
else {
|
|
660
|
+
await fs.unlink(fullPath);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
};
|
|
664
|
+
await removeRecursive(this.logsDir);
|
|
665
|
+
// Recreate directories
|
|
666
|
+
await this.ensureDirectories();
|
|
667
|
+
const appLogger = this.createLogger("system");
|
|
668
|
+
appLogger.warn("All logs have been purged");
|
|
669
|
+
}
|
|
670
|
+
/**
|
|
671
|
+
* Export logs in various formats
|
|
672
|
+
*/
|
|
673
|
+
async exportLogs(options = {}) {
|
|
674
|
+
const { level = "all", format = "txt", startDate, endDate } = options;
|
|
675
|
+
const currentLogFile = path.join(this.logsDir, `app-${new Date().toISOString().split("T")[0]}.log`);
|
|
676
|
+
if (!existsSync(currentLogFile)) {
|
|
677
|
+
throw new Error("No logs found for export");
|
|
678
|
+
}
|
|
679
|
+
const content = await fs.readFile(currentLogFile, "utf8");
|
|
680
|
+
const lines = content.split("\n").filter((line) => line.trim());
|
|
681
|
+
// Filter by level if specified
|
|
682
|
+
let filteredLines = lines;
|
|
683
|
+
if (level !== "all") {
|
|
684
|
+
const levelUpper = level.toUpperCase();
|
|
685
|
+
filteredLines = lines.filter((line) => line.includes(`[${levelUpper}]`) || line.includes(` ${levelUpper} `));
|
|
686
|
+
}
|
|
687
|
+
// Filter by date range if specified
|
|
688
|
+
if (startDate || endDate) {
|
|
689
|
+
filteredLines = filteredLines.filter((line) => {
|
|
690
|
+
const timestampMatch = line.match(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/);
|
|
691
|
+
if (!timestampMatch)
|
|
692
|
+
return true;
|
|
693
|
+
const lineDate = new Date(timestampMatch[0]);
|
|
694
|
+
if (startDate && lineDate < startDate)
|
|
695
|
+
return false;
|
|
696
|
+
if (endDate && lineDate > endDate)
|
|
697
|
+
return false;
|
|
698
|
+
return true;
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
// Format output based on requested format
|
|
702
|
+
switch (format) {
|
|
703
|
+
case "json": {
|
|
704
|
+
const entries = filteredLines.map((line) => {
|
|
705
|
+
try {
|
|
706
|
+
// Try to parse as JSON first
|
|
707
|
+
if (line.trim().startsWith("{")) {
|
|
708
|
+
return JSON.parse(line);
|
|
709
|
+
}
|
|
710
|
+
// Parse text format
|
|
711
|
+
const match = line.match(/^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3})\s+\[(\w+)\]\s+(?:\[([^\]]+)\]\s+)?(.*)$/);
|
|
712
|
+
if (match) {
|
|
713
|
+
const [, timestamp, level, category, message] = match;
|
|
714
|
+
return {
|
|
715
|
+
timestamp,
|
|
716
|
+
level: level.toLowerCase(),
|
|
717
|
+
category: category || "general",
|
|
718
|
+
message,
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
return { message: line };
|
|
722
|
+
}
|
|
723
|
+
catch {
|
|
724
|
+
return { message: line };
|
|
725
|
+
}
|
|
726
|
+
});
|
|
727
|
+
return JSON.stringify(entries, null, 2);
|
|
728
|
+
}
|
|
729
|
+
case "csv": {
|
|
730
|
+
const csv = ["Timestamp,Level,Category,Message"];
|
|
731
|
+
filteredLines.forEach((line) => {
|
|
732
|
+
const match = line.match(/^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3})\s+\[(\w+)\]\s+(?:\[([^\]]+)\]\s+)?(.*)$/);
|
|
733
|
+
if (match) {
|
|
734
|
+
const [, timestamp, level, category, message] = match;
|
|
735
|
+
csv.push(`"${timestamp}","${level}","${category || ""}","${message.replace(/"/g, '""')}"`);
|
|
736
|
+
}
|
|
737
|
+
});
|
|
738
|
+
return csv.join("\n");
|
|
739
|
+
}
|
|
740
|
+
case "txt":
|
|
741
|
+
default:
|
|
742
|
+
return filteredLines.join("\n");
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
/**
|
|
746
|
+
* Get list of all log files including archives
|
|
747
|
+
*/
|
|
748
|
+
async getAllLogFiles() {
|
|
749
|
+
const files = [];
|
|
750
|
+
// Get files from logs directory
|
|
751
|
+
if (existsSync(this.logsDir)) {
|
|
752
|
+
const logFiles = await fs.readdir(this.logsDir);
|
|
753
|
+
for (const file of logFiles) {
|
|
754
|
+
if (file.startsWith("app-") || file.startsWith("error-")) {
|
|
755
|
+
const fullPath = path.join(this.logsDir, file);
|
|
756
|
+
const stats = await fs.stat(fullPath);
|
|
757
|
+
files.push({
|
|
758
|
+
name: file,
|
|
759
|
+
size: stats.size,
|
|
760
|
+
modified: stats.mtime,
|
|
761
|
+
compressed: false,
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
// Get files from archive directory
|
|
767
|
+
if (existsSync(this.archiveDir)) {
|
|
768
|
+
const archiveFiles = await fs.readdir(this.archiveDir);
|
|
769
|
+
for (const file of archiveFiles) {
|
|
770
|
+
const fullPath = path.join(this.archiveDir, file);
|
|
771
|
+
const stats = await fs.stat(fullPath);
|
|
772
|
+
files.push({
|
|
773
|
+
name: `archive/${file}`,
|
|
774
|
+
size: stats.size,
|
|
775
|
+
modified: stats.mtime,
|
|
776
|
+
compressed: file.endsWith(".gz"),
|
|
777
|
+
});
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
// Sort by modified date (newest first)
|
|
781
|
+
files.sort((a, b) => b.modified.getTime() - a.modified.getTime());
|
|
782
|
+
return files;
|
|
783
|
+
}
|
|
784
|
+
/**
|
|
785
|
+
* Get all logger instances (for rotation)
|
|
786
|
+
*/
|
|
787
|
+
getLoggers() {
|
|
788
|
+
return this.loggers;
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
// Export singleton instance and functions
|
|
792
|
+
const logger = new Logger();
|
|
793
|
+
export function createLogger(name) {
|
|
794
|
+
if (!name) {
|
|
795
|
+
return logger.child("App");
|
|
796
|
+
}
|
|
797
|
+
return logger.child(name);
|
|
798
|
+
}
|
|
799
|
+
export function initializeLogger(options) {
|
|
800
|
+
return logger.initialize(options);
|
|
801
|
+
}
|
|
802
|
+
export function configureLogging(options) {
|
|
803
|
+
// For backward compatibility - just trigger initialization
|
|
804
|
+
logger.initialize(options).catch((err) => {
|
|
805
|
+
console.error("Failed to configure logging:", err);
|
|
806
|
+
});
|
|
807
|
+
}
|
|
808
|
+
export async function getLogStats() {
|
|
809
|
+
return logger.getLogStats();
|
|
810
|
+
}
|
|
811
|
+
export async function archiveLogs(olderThanDays) {
|
|
812
|
+
return logger.archiveLogs(olderThanDays);
|
|
813
|
+
}
|
|
814
|
+
export async function downloadLogFile(filename) {
|
|
815
|
+
return logger.downloadLogFile(filename);
|
|
816
|
+
}
|
|
817
|
+
export async function getRecentLogs(limit, level) {
|
|
818
|
+
return logger.getRecentLogs(limit, level);
|
|
819
|
+
}
|
|
820
|
+
export async function clearLogs() {
|
|
821
|
+
return logger.clearLogs();
|
|
822
|
+
}
|
|
823
|
+
// Export both as default and named export
|
|
824
|
+
export default logger;
|
|
825
|
+
export { logger as getLogger };
|
|
826
|
+
//# sourceMappingURL=logger.js.map
|