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