@masuidrive/procman 0.1.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 (315) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +149 -0
  3. package/dist/src/cli/commands/clear-log.d.ts +10 -0
  4. package/dist/src/cli/commands/clear-log.d.ts.map +1 -0
  5. package/dist/src/cli/commands/clear-log.js +77 -0
  6. package/dist/src/cli/commands/clear-log.js.map +1 -0
  7. package/dist/src/cli/commands/exit.d.ts +10 -0
  8. package/dist/src/cli/commands/exit.d.ts.map +1 -0
  9. package/dist/src/cli/commands/exit.js +65 -0
  10. package/dist/src/cli/commands/exit.js.map +1 -0
  11. package/dist/src/cli/commands/help.d.ts +10 -0
  12. package/dist/src/cli/commands/help.d.ts.map +1 -0
  13. package/dist/src/cli/commands/help.js +340 -0
  14. package/dist/src/cli/commands/help.js.map +1 -0
  15. package/dist/src/cli/commands/list.d.ts +11 -0
  16. package/dist/src/cli/commands/list.d.ts.map +1 -0
  17. package/dist/src/cli/commands/list.js +130 -0
  18. package/dist/src/cli/commands/list.js.map +1 -0
  19. package/dist/src/cli/commands/load.d.ts +11 -0
  20. package/dist/src/cli/commands/load.d.ts.map +1 -0
  21. package/dist/src/cli/commands/load.js +250 -0
  22. package/dist/src/cli/commands/load.js.map +1 -0
  23. package/dist/src/cli/commands/log.d.ts +18 -0
  24. package/dist/src/cli/commands/log.d.ts.map +1 -0
  25. package/dist/src/cli/commands/log.js +282 -0
  26. package/dist/src/cli/commands/log.js.map +1 -0
  27. package/dist/src/cli/commands/restart.d.ts +11 -0
  28. package/dist/src/cli/commands/restart.d.ts.map +1 -0
  29. package/dist/src/cli/commands/restart.js +99 -0
  30. package/dist/src/cli/commands/restart.js.map +1 -0
  31. package/dist/src/cli/commands/start.d.ts +11 -0
  32. package/dist/src/cli/commands/start.d.ts.map +1 -0
  33. package/dist/src/cli/commands/start.js +105 -0
  34. package/dist/src/cli/commands/start.js.map +1 -0
  35. package/dist/src/cli/commands/stop.d.ts +12 -0
  36. package/dist/src/cli/commands/stop.d.ts.map +1 -0
  37. package/dist/src/cli/commands/stop.js +105 -0
  38. package/dist/src/cli/commands/stop.js.map +1 -0
  39. package/dist/src/cli/index.d.ts +3 -0
  40. package/dist/src/cli/index.d.ts.map +1 -0
  41. package/dist/src/cli/index.js +28 -0
  42. package/dist/src/cli/index.js.map +1 -0
  43. package/dist/src/cli/parser.d.ts +14 -0
  44. package/dist/src/cli/parser.d.ts.map +1 -0
  45. package/dist/src/cli/parser.js +131 -0
  46. package/dist/src/cli/parser.js.map +1 -0
  47. package/dist/src/cli/signal-handler.d.ts +51 -0
  48. package/dist/src/cli/signal-handler.d.ts.map +1 -0
  49. package/dist/src/cli/signal-handler.js +129 -0
  50. package/dist/src/cli/signal-handler.js.map +1 -0
  51. package/dist/src/cli/utils/error-handler.d.ts +39 -0
  52. package/dist/src/cli/utils/error-handler.d.ts.map +1 -0
  53. package/dist/src/cli/utils/error-handler.js +200 -0
  54. package/dist/src/cli/utils/error-handler.js.map +1 -0
  55. package/dist/src/cli/utils/error-utils.d.ts +16 -0
  56. package/dist/src/cli/utils/error-utils.d.ts.map +1 -0
  57. package/dist/src/cli/utils/error-utils.js +48 -0
  58. package/dist/src/cli/utils/error-utils.js.map +1 -0
  59. package/dist/src/cli/utils/ipc-client.d.ts +80 -0
  60. package/dist/src/cli/utils/ipc-client.d.ts.map +1 -0
  61. package/dist/src/cli/utils/ipc-client.js +275 -0
  62. package/dist/src/cli/utils/ipc-client.js.map +1 -0
  63. package/dist/src/config/config-loader.d.ts +74 -0
  64. package/dist/src/config/config-loader.d.ts.map +1 -0
  65. package/dist/src/config/config-loader.js +229 -0
  66. package/dist/src/config/config-loader.js.map +1 -0
  67. package/dist/src/config/config-normalizer.d.ts +135 -0
  68. package/dist/src/config/config-normalizer.d.ts.map +1 -0
  69. package/dist/src/config/config-normalizer.js +309 -0
  70. package/dist/src/config/config-normalizer.js.map +1 -0
  71. package/dist/src/config/config-reporter.d.ts +183 -0
  72. package/dist/src/config/config-reporter.d.ts.map +1 -0
  73. package/dist/src/config/config-reporter.js +311 -0
  74. package/dist/src/config/config-reporter.js.map +1 -0
  75. package/dist/src/config/config-validator.d.ts +163 -0
  76. package/dist/src/config/config-validator.d.ts.map +1 -0
  77. package/dist/src/config/config-validator.js +489 -0
  78. package/dist/src/config/config-validator.js.map +1 -0
  79. package/dist/src/config/config-watcher.d.ts +161 -0
  80. package/dist/src/config/config-watcher.d.ts.map +1 -0
  81. package/dist/src/config/config-watcher.js +245 -0
  82. package/dist/src/config/config-watcher.js.map +1 -0
  83. package/dist/src/config/index.d.ts +36 -0
  84. package/dist/src/config/index.d.ts.map +1 -0
  85. package/dist/src/config/index.js +41 -0
  86. package/dist/src/config/index.js.map +1 -0
  87. package/dist/src/config/secure-config-loader.d.ts +77 -0
  88. package/dist/src/config/secure-config-loader.d.ts.map +1 -0
  89. package/dist/src/config/secure-config-loader.js +326 -0
  90. package/dist/src/config/secure-config-loader.js.map +1 -0
  91. package/dist/src/daemon/base-socket-connection.d.ts +66 -0
  92. package/dist/src/daemon/base-socket-connection.d.ts.map +1 -0
  93. package/dist/src/daemon/base-socket-connection.js +192 -0
  94. package/dist/src/daemon/base-socket-connection.js.map +1 -0
  95. package/dist/src/daemon/component-manager.d.ts +180 -0
  96. package/dist/src/daemon/component-manager.d.ts.map +1 -0
  97. package/dist/src/daemon/component-manager.js +794 -0
  98. package/dist/src/daemon/component-manager.js.map +1 -0
  99. package/dist/src/daemon/crash-recovery.d.ts +71 -0
  100. package/dist/src/daemon/crash-recovery.d.ts.map +1 -0
  101. package/dist/src/daemon/crash-recovery.js +282 -0
  102. package/dist/src/daemon/crash-recovery.js.map +1 -0
  103. package/dist/src/daemon/daemon-main.d.ts +18 -0
  104. package/dist/src/daemon/daemon-main.d.ts.map +1 -0
  105. package/dist/src/daemon/daemon-main.js +160 -0
  106. package/dist/src/daemon/daemon-main.js.map +1 -0
  107. package/dist/src/daemon/daemon-state-manager.d.ts +111 -0
  108. package/dist/src/daemon/daemon-state-manager.d.ts.map +1 -0
  109. package/dist/src/daemon/daemon-state-manager.js +194 -0
  110. package/dist/src/daemon/daemon-state-manager.js.map +1 -0
  111. package/dist/src/daemon/data-directory.d.ts +51 -0
  112. package/dist/src/daemon/data-directory.d.ts.map +1 -0
  113. package/dist/src/daemon/data-directory.js +136 -0
  114. package/dist/src/daemon/data-directory.js.map +1 -0
  115. package/dist/src/daemon/index.d.ts +20 -0
  116. package/dist/src/daemon/index.d.ts.map +1 -0
  117. package/dist/src/daemon/index.js +24 -0
  118. package/dist/src/daemon/index.js.map +1 -0
  119. package/dist/src/daemon/ipc-client-base.d.ts +153 -0
  120. package/dist/src/daemon/ipc-client-base.d.ts.map +1 -0
  121. package/dist/src/daemon/ipc-client-base.js +476 -0
  122. package/dist/src/daemon/ipc-client-base.js.map +1 -0
  123. package/dist/src/daemon/ipc-command-handler.d.ts +107 -0
  124. package/dist/src/daemon/ipc-command-handler.d.ts.map +1 -0
  125. package/dist/src/daemon/ipc-command-handler.js +483 -0
  126. package/dist/src/daemon/ipc-command-handler.js.map +1 -0
  127. package/dist/src/daemon/ipc-factory.d.ts +92 -0
  128. package/dist/src/daemon/ipc-factory.d.ts.map +1 -0
  129. package/dist/src/daemon/ipc-factory.js +210 -0
  130. package/dist/src/daemon/ipc-factory.js.map +1 -0
  131. package/dist/src/daemon/ipc-server-base.d.ts +158 -0
  132. package/dist/src/daemon/ipc-server-base.d.ts.map +1 -0
  133. package/dist/src/daemon/ipc-server-base.js +491 -0
  134. package/dist/src/daemon/ipc-server-base.js.map +1 -0
  135. package/dist/src/daemon/message-protocol.d.ts +132 -0
  136. package/dist/src/daemon/message-protocol.d.ts.map +1 -0
  137. package/dist/src/daemon/message-protocol.js +252 -0
  138. package/dist/src/daemon/message-protocol.js.map +1 -0
  139. package/dist/src/daemon/named-pipe-client.d.ts +61 -0
  140. package/dist/src/daemon/named-pipe-client.d.ts.map +1 -0
  141. package/dist/src/daemon/named-pipe-client.js +221 -0
  142. package/dist/src/daemon/named-pipe-client.js.map +1 -0
  143. package/dist/src/daemon/named-pipe-server.d.ts +40 -0
  144. package/dist/src/daemon/named-pipe-server.d.ts.map +1 -0
  145. package/dist/src/daemon/named-pipe-server.js +102 -0
  146. package/dist/src/daemon/named-pipe-server.js.map +1 -0
  147. package/dist/src/daemon/orphan-detector.d.ts +66 -0
  148. package/dist/src/daemon/orphan-detector.d.ts.map +1 -0
  149. package/dist/src/daemon/orphan-detector.js +208 -0
  150. package/dist/src/daemon/orphan-detector.js.map +1 -0
  151. package/dist/src/daemon/pid-manager.d.ts +49 -0
  152. package/dist/src/daemon/pid-manager.d.ts.map +1 -0
  153. package/dist/src/daemon/pid-manager.js +110 -0
  154. package/dist/src/daemon/pid-manager.js.map +1 -0
  155. package/dist/src/daemon/procman-daemon.d.ts +188 -0
  156. package/dist/src/daemon/procman-daemon.d.ts.map +1 -0
  157. package/dist/src/daemon/procman-daemon.js +802 -0
  158. package/dist/src/daemon/procman-daemon.js.map +1 -0
  159. package/dist/src/daemon/reconnection-system.d.ts +113 -0
  160. package/dist/src/daemon/reconnection-system.d.ts.map +1 -0
  161. package/dist/src/daemon/reconnection-system.js +223 -0
  162. package/dist/src/daemon/reconnection-system.js.map +1 -0
  163. package/dist/src/daemon/resource-manager.d.ts +204 -0
  164. package/dist/src/daemon/resource-manager.d.ts.map +1 -0
  165. package/dist/src/daemon/resource-manager.js +423 -0
  166. package/dist/src/daemon/resource-manager.js.map +1 -0
  167. package/dist/src/daemon/signal-handler.d.ts +58 -0
  168. package/dist/src/daemon/signal-handler.d.ts.map +1 -0
  169. package/dist/src/daemon/signal-handler.js +142 -0
  170. package/dist/src/daemon/signal-handler.js.map +1 -0
  171. package/dist/src/daemon/simple-resource-manager.d.ts +95 -0
  172. package/dist/src/daemon/simple-resource-manager.d.ts.map +1 -0
  173. package/dist/src/daemon/simple-resource-manager.js +180 -0
  174. package/dist/src/daemon/simple-resource-manager.js.map +1 -0
  175. package/dist/src/daemon/unix-socket-client.d.ts +69 -0
  176. package/dist/src/daemon/unix-socket-client.d.ts.map +1 -0
  177. package/dist/src/daemon/unix-socket-client.js +313 -0
  178. package/dist/src/daemon/unix-socket-client.js.map +1 -0
  179. package/dist/src/daemon/unix-socket-server.d.ts +61 -0
  180. package/dist/src/daemon/unix-socket-server.d.ts.map +1 -0
  181. package/dist/src/daemon/unix-socket-server.js +262 -0
  182. package/dist/src/daemon/unix-socket-server.js.map +1 -0
  183. package/dist/src/daemon/zombie-reaper.d.ts +83 -0
  184. package/dist/src/daemon/zombie-reaper.d.ts.map +1 -0
  185. package/dist/src/daemon/zombie-reaper.js +278 -0
  186. package/dist/src/daemon/zombie-reaper.js.map +1 -0
  187. package/dist/src/process-manager/index.d.ts +13 -0
  188. package/dist/src/process-manager/index.d.ts.map +1 -0
  189. package/dist/src/process-manager/index.js +11 -0
  190. package/dist/src/process-manager/index.js.map +1 -0
  191. package/dist/src/process-manager/interfaces/index.d.ts +31 -0
  192. package/dist/src/process-manager/interfaces/index.d.ts.map +1 -0
  193. package/dist/src/process-manager/interfaces/index.js +14 -0
  194. package/dist/src/process-manager/interfaces/index.js.map +1 -0
  195. package/dist/src/process-manager/interfaces/process-group.d.ts +200 -0
  196. package/dist/src/process-manager/interfaces/process-group.d.ts.map +1 -0
  197. package/dist/src/process-manager/interfaces/process-group.js +10 -0
  198. package/dist/src/process-manager/interfaces/process-group.js.map +1 -0
  199. package/dist/src/process-manager/interfaces/process-lifecycle.d.ts +97 -0
  200. package/dist/src/process-manager/interfaces/process-lifecycle.d.ts.map +1 -0
  201. package/dist/src/process-manager/interfaces/process-lifecycle.js +10 -0
  202. package/dist/src/process-manager/interfaces/process-lifecycle.js.map +1 -0
  203. package/dist/src/process-manager/interfaces/process-monitor.d.ts +118 -0
  204. package/dist/src/process-manager/interfaces/process-monitor.d.ts.map +1 -0
  205. package/dist/src/process-manager/interfaces/process-monitor.js +10 -0
  206. package/dist/src/process-manager/interfaces/process-monitor.js.map +1 -0
  207. package/dist/src/process-manager/interfaces/process-persistence.d.ts +125 -0
  208. package/dist/src/process-manager/interfaces/process-persistence.d.ts.map +1 -0
  209. package/dist/src/process-manager/interfaces/process-persistence.js +10 -0
  210. package/dist/src/process-manager/interfaces/process-persistence.js.map +1 -0
  211. package/dist/src/process-manager/managed-process-info.d.ts +307 -0
  212. package/dist/src/process-manager/managed-process-info.d.ts.map +1 -0
  213. package/dist/src/process-manager/managed-process-info.js +650 -0
  214. package/dist/src/process-manager/managed-process-info.js.map +1 -0
  215. package/dist/src/process-manager/process-group-manager.d.ts +103 -0
  216. package/dist/src/process-manager/process-group-manager.d.ts.map +1 -0
  217. package/dist/src/process-manager/process-group-manager.js +400 -0
  218. package/dist/src/process-manager/process-group-manager.js.map +1 -0
  219. package/dist/src/process-manager/process-lifecycle-manager.d.ts +68 -0
  220. package/dist/src/process-manager/process-lifecycle-manager.d.ts.map +1 -0
  221. package/dist/src/process-manager/process-lifecycle-manager.js +372 -0
  222. package/dist/src/process-manager/process-lifecycle-manager.js.map +1 -0
  223. package/dist/src/process-manager/process-manager.d.ts +296 -0
  224. package/dist/src/process-manager/process-manager.d.ts.map +1 -0
  225. package/dist/src/process-manager/process-manager.js +659 -0
  226. package/dist/src/process-manager/process-manager.js.map +1 -0
  227. package/dist/src/process-manager/process-monitor.d.ts +95 -0
  228. package/dist/src/process-manager/process-monitor.d.ts.map +1 -0
  229. package/dist/src/process-manager/process-monitor.js +357 -0
  230. package/dist/src/process-manager/process-monitor.js.map +1 -0
  231. package/dist/src/process-manager/process-persistence.d.ts +68 -0
  232. package/dist/src/process-manager/process-persistence.d.ts.map +1 -0
  233. package/dist/src/process-manager/process-persistence.js +364 -0
  234. package/dist/src/process-manager/process-persistence.js.map +1 -0
  235. package/dist/src/process-manager/utils/mutex.d.ts +82 -0
  236. package/dist/src/process-manager/utils/mutex.d.ts.map +1 -0
  237. package/dist/src/process-manager/utils/mutex.js +172 -0
  238. package/dist/src/process-manager/utils/mutex.js.map +1 -0
  239. package/dist/src/services/index.d.ts +8 -0
  240. package/dist/src/services/index.d.ts.map +1 -0
  241. package/dist/src/services/index.js +8 -0
  242. package/dist/src/services/index.js.map +1 -0
  243. package/dist/src/services/log-manager-config.d.ts +65 -0
  244. package/dist/src/services/log-manager-config.d.ts.map +1 -0
  245. package/dist/src/services/log-manager-config.js +145 -0
  246. package/dist/src/services/log-manager-config.js.map +1 -0
  247. package/dist/src/services/log-manager-factory.d.ts +134 -0
  248. package/dist/src/services/log-manager-factory.d.ts.map +1 -0
  249. package/dist/src/services/log-manager-factory.js +203 -0
  250. package/dist/src/services/log-manager-factory.js.map +1 -0
  251. package/dist/src/services/log-manager.d.ts +170 -0
  252. package/dist/src/services/log-manager.d.ts.map +1 -0
  253. package/dist/src/services/log-manager.js +1199 -0
  254. package/dist/src/services/log-manager.js.map +1 -0
  255. package/dist/src/services/log-path-validator.d.ts +121 -0
  256. package/dist/src/services/log-path-validator.d.ts.map +1 -0
  257. package/dist/src/services/log-path-validator.js +302 -0
  258. package/dist/src/services/log-path-validator.js.map +1 -0
  259. package/dist/src/services/process-log-integration.d.ts +62 -0
  260. package/dist/src/services/process-log-integration.d.ts.map +1 -0
  261. package/dist/src/services/process-log-integration.js +157 -0
  262. package/dist/src/services/process-log-integration.js.map +1 -0
  263. package/dist/src/shared/config.d.ts +67 -0
  264. package/dist/src/shared/config.d.ts.map +1 -0
  265. package/dist/src/shared/config.js +92 -0
  266. package/dist/src/shared/config.js.map +1 -0
  267. package/dist/src/shared/constants-streaming.d.ts +39 -0
  268. package/dist/src/shared/constants-streaming.d.ts.map +1 -0
  269. package/dist/src/shared/constants-streaming.js +39 -0
  270. package/dist/src/shared/constants-streaming.js.map +1 -0
  271. package/dist/src/shared/constants.d.ts +60 -0
  272. package/dist/src/shared/constants.d.ts.map +1 -0
  273. package/dist/src/shared/constants.js +71 -0
  274. package/dist/src/shared/constants.js.map +1 -0
  275. package/dist/src/shared/errors.d.ts +70 -0
  276. package/dist/src/shared/errors.d.ts.map +1 -0
  277. package/dist/src/shared/errors.js +101 -0
  278. package/dist/src/shared/errors.js.map +1 -0
  279. package/dist/src/shared/index.d.ts +14 -0
  280. package/dist/src/shared/index.d.ts.map +1 -0
  281. package/dist/src/shared/index.js +22 -0
  282. package/dist/src/shared/index.js.map +1 -0
  283. package/dist/src/shared/ipc.d.ts +402 -0
  284. package/dist/src/shared/ipc.d.ts.map +1 -0
  285. package/dist/src/shared/ipc.js +85 -0
  286. package/dist/src/shared/ipc.js.map +1 -0
  287. package/dist/src/shared/logger.d.ts +38 -0
  288. package/dist/src/shared/logger.d.ts.map +1 -0
  289. package/dist/src/shared/logger.js +139 -0
  290. package/dist/src/shared/logger.js.map +1 -0
  291. package/dist/src/shared/logs.d.ts +53 -0
  292. package/dist/src/shared/logs.d.ts.map +1 -0
  293. package/dist/src/shared/logs.js +26 -0
  294. package/dist/src/shared/logs.js.map +1 -0
  295. package/dist/src/shared/process.d.ts +102 -0
  296. package/dist/src/shared/process.d.ts.map +1 -0
  297. package/dist/src/shared/process.js +55 -0
  298. package/dist/src/shared/process.js.map +1 -0
  299. package/dist/src/shared/types.d.ts +15 -0
  300. package/dist/src/shared/types.d.ts.map +1 -0
  301. package/dist/src/shared/types.js +8 -0
  302. package/dist/src/shared/types.js.map +1 -0
  303. package/dist/src/types/index.d.ts +19 -0
  304. package/dist/src/types/index.d.ts.map +1 -0
  305. package/dist/src/types/index.js +5 -0
  306. package/dist/src/types/index.js.map +1 -0
  307. package/dist/src/utils/event-cleanup.d.ts +30 -0
  308. package/dist/src/utils/event-cleanup.d.ts.map +1 -0
  309. package/dist/src/utils/event-cleanup.js +32 -0
  310. package/dist/src/utils/event-cleanup.js.map +1 -0
  311. package/dist/src/utils/memory/memory-monitor.d.ts +126 -0
  312. package/dist/src/utils/memory/memory-monitor.d.ts.map +1 -0
  313. package/dist/src/utils/memory/memory-monitor.js +246 -0
  314. package/dist/src/utils/memory/memory-monitor.js.map +1 -0
  315. package/package.json +74 -0
@@ -0,0 +1,1199 @@
1
+ /**
2
+ * Log Manager - ログ管理システム
3
+ *
4
+ * アプリケーションプロセスのstdout/stderrを集約し、JSONL形式でログを管理する。
5
+ * リアルタイムログストリーミング機能とファイル管理を提供する。
6
+ * ログストリーミングはEventEmitterパターンで実装し、new-log イベントを発行する。
7
+ */
8
+ /* global NodeJS */
9
+ import { EventEmitter } from 'events';
10
+ import * as fs from 'fs';
11
+ import { promises as fsPromises } from 'fs';
12
+ import * as path from 'path';
13
+ import * as os from 'os';
14
+ import { DEFAULT_LOG_MANAGER_CONFIG, } from './log-manager-config.js';
15
+ import { LogPathValidator, SecureLogPathBuilder, } from './log-path-validator.js';
16
+ import { LOG_STREAM_EVENTS } from '../shared/constants-streaming.js';
17
+ import { EventCleanupHelper } from '../utils/event-cleanup.js';
18
+ // =============================================================================
19
+ // Constants
20
+ // =============================================================================
21
+ /** 特殊文字のエスケープ対象 */
22
+ // eslint-disable-next-line no-control-regex
23
+ const ESCAPE_CHARS = /[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g;
24
+ // =============================================================================
25
+ // FileManager Class
26
+ // =============================================================================
27
+ /**
28
+ * FileManager - ファイル操作の専門クラス(単一責任原則)
29
+ */
30
+ class FileManager {
31
+ config;
32
+ constructor(config = DEFAULT_LOG_MANAGER_CONFIG) {
33
+ this.config = config;
34
+ }
35
+ /**
36
+ * ファイルの存在確認・作成
37
+ */
38
+ ensureFileExists(filePath) {
39
+ try {
40
+ // ディレクトリの確認・作成
41
+ const dir = path.dirname(filePath);
42
+ if (!fs.existsSync(dir)) {
43
+ fs.mkdirSync(dir, {
44
+ recursive: true,
45
+ mode: this.config.file.directoryMode,
46
+ });
47
+ }
48
+ // ファイルが存在しない場合は空ファイルを作成
49
+ if (!fs.existsSync(filePath)) {
50
+ fs.writeFileSync(filePath, '', { mode: this.config.file.fileMode });
51
+ }
52
+ }
53
+ catch (error) {
54
+ console.warn(`[FileManager] Failed to ensure file exists ${filePath}: ${error.message}`);
55
+ }
56
+ }
57
+ /**
58
+ * ログディレクトリの確認・作成
59
+ */
60
+ ensureLogDirectory(logDir) {
61
+ if (!fs.existsSync(logDir)) {
62
+ fs.mkdirSync(logDir, {
63
+ recursive: true,
64
+ mode: this.config.file.directoryMode,
65
+ });
66
+ }
67
+ }
68
+ /**
69
+ * ファイルの末尾から指定行数を効率的に読み込み
70
+ */
71
+ async readTailLines(filePath, maxLines) {
72
+ const stat = fs.statSync(filePath);
73
+ const fileSize = stat.size;
74
+ if (fileSize === 0) {
75
+ return [];
76
+ }
77
+ // 小さなファイルの場合は全体を読み込み
78
+ if (fileSize < this.config.performance.smallFileThreshold) {
79
+ const content = fs.readFileSync(filePath, 'utf8');
80
+ return content
81
+ .split('\n')
82
+ .filter((line) => line.trim())
83
+ .slice(-maxLines);
84
+ }
85
+ // 大きなファイルの場合は末尾から効率的に読み込み
86
+ return this.readTailLinesFromLargeFile(filePath, maxLines, fileSize);
87
+ }
88
+ /**
89
+ * 大きなファイルから末尾行数を読み込み(関数分割)
90
+ */
91
+ readTailLinesFromLargeFile(filePath, maxLines, fileSize) {
92
+ const fd = fs.openSync(filePath, 'r');
93
+ try {
94
+ return this.readLinesBackward(fd, fileSize, maxLines);
95
+ }
96
+ finally {
97
+ fs.closeSync(fd);
98
+ }
99
+ }
100
+ /**
101
+ * ファイルを後方から読み込んで行を取得(10行以下に分割)
102
+ */
103
+ readLinesBackward(fd, fileSize, maxLines) {
104
+ let position = fileSize;
105
+ let lines = [];
106
+ let buffer = '';
107
+ while (position > 0 && lines.length < maxLines) {
108
+ const chunkResult = this.readChunkBackward(fd, position);
109
+ position = chunkResult.newPosition;
110
+ buffer = chunkResult.chunk + buffer;
111
+ lines = this.processBufferLines(buffer, lines, maxLines);
112
+ buffer = this.extractPartialLine(buffer);
113
+ }
114
+ return lines.slice(-maxLines);
115
+ }
116
+ /**
117
+ * 後方からチャンクを読み込み(10行以下)
118
+ */
119
+ readChunkBackward(fd, position) {
120
+ const chunkSize = this.config.file.chunkSize;
121
+ const readSize = Math.min(chunkSize, position);
122
+ const newPosition = position - readSize;
123
+ const chunk = Buffer.alloc(readSize);
124
+ fs.readSync(fd, chunk, 0, readSize, newPosition);
125
+ return {
126
+ chunk: chunk.toString('utf8'),
127
+ newPosition,
128
+ };
129
+ }
130
+ /**
131
+ * バッファの行を処理(10行以下)
132
+ */
133
+ processBufferLines(buffer, currentLines, maxLines) {
134
+ const lineArray = buffer.split('\n');
135
+ const lines = [...currentLines];
136
+ // 完全な行を先頭に追加(逆順)
137
+ for (let i = lineArray.length - 2; i >= 0; i--) {
138
+ const line = lineArray[i].trim();
139
+ if (line && lines.length < maxLines) {
140
+ lines.unshift(line);
141
+ }
142
+ }
143
+ return lines;
144
+ }
145
+ /**
146
+ * 部分的な行を抽出(10行以下)
147
+ */
148
+ extractPartialLine(buffer) {
149
+ const lineArray = buffer.split('\n');
150
+ return lineArray.shift() || '';
151
+ }
152
+ /**
153
+ * リトライ機能付きファイル書き込み
154
+ */
155
+ async writeToFileWithRetry(filePath, content, maxRetries) {
156
+ let lastError = null;
157
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
158
+ try {
159
+ await fsPromises.appendFile(filePath, content, { encoding: 'utf8' });
160
+ return; // 成功した場合はリターン
161
+ }
162
+ catch (error) {
163
+ lastError = error;
164
+ if (attempt === maxRetries) {
165
+ // 最後の試行で失敗した場合はエラーログを出力
166
+ console.error(`[FileManager] Failed to write to log file ${filePath} after ${maxRetries} attempts: ${lastError.message}`);
167
+ // エラーを伝播(上位でハンドリング可能にする)
168
+ throw lastError;
169
+ }
170
+ else {
171
+ // リトライ前の短い待機(設定値 * 試行回数)
172
+ await new Promise((resolve) => setTimeout(resolve, this.config.file.retryBaseDelay * attempt));
173
+ }
174
+ }
175
+ }
176
+ }
177
+ }
178
+ // =============================================================================
179
+ // LogPreprocessor Class
180
+ // =============================================================================
181
+ /**
182
+ * LogPreprocessor - ログ前処理の専門クラス(単一責任原則)
183
+ */
184
+ class LogPreprocessor {
185
+ config;
186
+ constructor(config = DEFAULT_LOG_MANAGER_CONFIG) {
187
+ this.config = config;
188
+ }
189
+ /**
190
+ * ログメッセージの前処理
191
+ */
192
+ preprocessMessage(message) {
193
+ let processed = message;
194
+ // サイズ制限の適用
195
+ processed = this.applySizeLimit(processed);
196
+ // 特殊文字のエスケープ
197
+ processed = this.escapeSpecialChars(processed);
198
+ // エンコーディング問題の修正
199
+ processed = this.fixEncodingIssues(processed);
200
+ // 改行文字の正規化とトリム
201
+ processed = this.normalizeAndTrim(processed);
202
+ return processed;
203
+ }
204
+ /**
205
+ * サイズ制限の適用
206
+ */
207
+ applySizeLimit(message) {
208
+ if (message.length > this.config.maxLogMessageSize) {
209
+ return message.substring(0, this.config.maxLogMessageSize - 3) + '...';
210
+ }
211
+ return message;
212
+ }
213
+ /**
214
+ * 特殊文字のエスケープ(制御文字の削除)
215
+ */
216
+ escapeSpecialChars(message) {
217
+ return message.replace(ESCAPE_CHARS, '');
218
+ }
219
+ /**
220
+ * エンコーディング問題の修正
221
+ */
222
+ fixEncodingIssues(message) {
223
+ try {
224
+ // Buffer経由でUTF-8として解釈し直す
225
+ const buffer = Buffer.from(message, 'utf8');
226
+ return buffer.toString('utf8');
227
+ }
228
+ catch {
229
+ // エラーが発生した場合はASCII文字のみを保持
230
+ return message.replace(/[^\x20-\x7E]/g, '?');
231
+ }
232
+ }
233
+ /**
234
+ * 改行文字の正規化とトリム
235
+ */
236
+ normalizeAndTrim(message) {
237
+ // 改行文字の正規化
238
+ const normalized = message.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
239
+ // トリム(前後の空白を削除)
240
+ return normalized.trim();
241
+ }
242
+ }
243
+ // =============================================================================
244
+ // LogLevelStrategy Classes
245
+ // =============================================================================
246
+ /**
247
+ * DefaultLogLevelStrategy - デフォルトのログレベル判定戦略(Strategy パターン)
248
+ */
249
+ class DefaultLogLevelStrategy {
250
+ /**
251
+ * ログレベルの判定
252
+ */
253
+ determineLevel(type, message) {
254
+ // stderr は基本的に warn 以上
255
+ if (type === 'stderr') {
256
+ return this.determineStderrLevel(message);
257
+ }
258
+ // stdout でもエラー関連のキーワードがあればエラー扱い
259
+ return this.determineStdoutLevel(message);
260
+ }
261
+ /**
262
+ * stderr のログレベル判定
263
+ */
264
+ determineStderrLevel(message) {
265
+ const lowerMessage = message.toLowerCase();
266
+ if (lowerMessage.includes('error') || lowerMessage.includes('fatal')) {
267
+ return 'error';
268
+ }
269
+ return 'warn';
270
+ }
271
+ /**
272
+ * stdout のログレベル判定
273
+ */
274
+ determineStdoutLevel(message) {
275
+ const lowerMessage = message.toLowerCase();
276
+ if (lowerMessage.includes('error') || lowerMessage.includes('fatal')) {
277
+ return 'error';
278
+ }
279
+ if (lowerMessage.includes('warn') || lowerMessage.includes('warning')) {
280
+ return 'warn';
281
+ }
282
+ return 'info';
283
+ }
284
+ }
285
+ // =============================================================================
286
+ // EventManager Class
287
+ // =============================================================================
288
+ /**
289
+ * EventManager - イベント発行の専門クラス(単一責任原則)
290
+ */
291
+ class EventManager {
292
+ eventEmitter;
293
+ constructor(eventEmitter) {
294
+ this.eventEmitter = eventEmitter;
295
+ }
296
+ /**
297
+ * ログイベントの発行
298
+ */
299
+ emitLogEvent(logEntry) {
300
+ this.eventEmitter.emit('log', logEntry);
301
+ // ストリーミング用の新しいログイベントも発行
302
+ this.eventEmitter.emit(LOG_STREAM_EVENTS.NEW_LOG, logEntry);
303
+ }
304
+ /**
305
+ * ファイル変更イベントの発行
306
+ */
307
+ emitFileChangeEvent(data) {
308
+ this.eventEmitter.emit('fileChange', data);
309
+ }
310
+ /**
311
+ * ディスク容量エラーイベントの発行
312
+ */
313
+ emitDiskSpaceErrorEvent(data) {
314
+ this.eventEmitter.emit('diskSpaceError', data);
315
+ }
316
+ }
317
+ // =============================================================================
318
+ // LogBuffer Class
319
+ // =============================================================================
320
+ /**
321
+ * LogBuffer - 高頻度書き込み時のバッファリング管理
322
+ */
323
+ class LogBuffer {
324
+ onFlush;
325
+ config;
326
+ buffer = [];
327
+ bufferSize = 0;
328
+ flushTimer = null;
329
+ isBackpressured = false;
330
+ constructor(onFlush, config = DEFAULT_LOG_MANAGER_CONFIG) {
331
+ this.onFlush = onFlush;
332
+ this.config = config;
333
+ }
334
+ /**
335
+ * ログエントリをバッファに追加
336
+ */
337
+ addEntry(entry) {
338
+ // バックプレッシャー制御
339
+ if (this.isBackpressured) {
340
+ return false;
341
+ }
342
+ const entrySize = JSON.stringify(entry.logEntry).length;
343
+ // バッファサイズ制限チェック
344
+ if (this.bufferSize + entrySize > this.config.buffer.maxSize) {
345
+ this.flush();
346
+ }
347
+ this.buffer.push(entry);
348
+ this.bufferSize += entrySize;
349
+ // バックプレッシャー制御
350
+ if (this.bufferSize > this.config.buffer.backpressureThreshold) {
351
+ this.isBackpressured = true;
352
+ }
353
+ // 定期フラッシュタイマー設定
354
+ if (!this.flushTimer) {
355
+ this.flushTimer = setTimeout(async () => {
356
+ await this.flush();
357
+ }, this.config.buffer.flushInterval);
358
+ }
359
+ return true;
360
+ }
361
+ /**
362
+ * バッファを強制フラッシュ
363
+ */
364
+ async flush() {
365
+ if (this.buffer.length === 0) {
366
+ return;
367
+ }
368
+ const entriesToFlush = [...this.buffer];
369
+ this.buffer = [];
370
+ this.bufferSize = 0;
371
+ this.isBackpressured = false;
372
+ if (this.flushTimer) {
373
+ clearTimeout(this.flushTimer);
374
+ this.flushTimer = null;
375
+ }
376
+ try {
377
+ await this.onFlush(entriesToFlush);
378
+ }
379
+ catch (error) {
380
+ console.error(`[LogBuffer] Failed to flush entries: ${error.message}`);
381
+ // 重要:失敗したエントリは失われるが、バッファが詰まるのを防ぐ
382
+ }
383
+ }
384
+ /**
385
+ * バッファクリーンアップ
386
+ */
387
+ async cleanup() {
388
+ await this.flush();
389
+ if (this.flushTimer) {
390
+ clearTimeout(this.flushTimer);
391
+ this.flushTimer = null;
392
+ }
393
+ }
394
+ /**
395
+ * バッファ統計情報
396
+ */
397
+ getStats() {
398
+ return {
399
+ entryCount: this.buffer.length,
400
+ bufferSize: this.bufferSize,
401
+ isBackpressured: this.isBackpressured,
402
+ };
403
+ }
404
+ }
405
+ /**
406
+ * AppLogger - 個別アプリケーションのログ管理(リファクタリング後)
407
+ */
408
+ class AppLogger {
409
+ logDir;
410
+ eventEmitter;
411
+ managerConfig;
412
+ logInfo;
413
+ watchers = [];
414
+ logBuffer;
415
+ partialLines = new Map();
416
+ // 依存性注入されたサービスクラス
417
+ fileManager;
418
+ logPreprocessor;
419
+ logLevelStrategy;
420
+ eventManager;
421
+ constructor(appName, config, logDir, eventEmitter, managerConfig = DEFAULT_LOG_MANAGER_CONFIG,
422
+ // 依存性注入(テスト時にはモックを注入可能)
423
+ fileManager, logPreprocessor, logLevelStrategy, eventManager) {
424
+ this.logDir = logDir;
425
+ this.eventEmitter = eventEmitter;
426
+ this.managerConfig = managerConfig;
427
+ this.logInfo = this.initializeLogInfo(appName, config);
428
+ // 依存性注入の実装(DI コンテナパターン)
429
+ this.fileManager = fileManager || new FileManager(this.managerConfig);
430
+ this.logPreprocessor =
431
+ logPreprocessor || new LogPreprocessor(this.managerConfig);
432
+ this.logLevelStrategy = logLevelStrategy || new DefaultLogLevelStrategy();
433
+ this.eventManager = eventManager || new EventManager(this.eventEmitter);
434
+ this.ensureLogFiles();
435
+ this.logBuffer = new LogBuffer(async (entries) => await this.flushBufferedEntries(entries), this.managerConfig);
436
+ }
437
+ /**
438
+ * ログ情報の初期化
439
+ */
440
+ initializeLogInfo(appName, config) {
441
+ const namespace = config.namespace || 'default';
442
+ // ログファイルパスの決定(仕様に従った優先順位)
443
+ let logFile = null;
444
+ let outFile = null;
445
+ let errorFile = null;
446
+ if (config.outFile && config.errorFile) {
447
+ // 1. out_file + error_file が指定時 → これらを使用、log_fileは無視
448
+ outFile = config.outFile;
449
+ errorFile = config.errorFile;
450
+ }
451
+ else if (config.logFile) {
452
+ // 2. log_file のみ指定時 → stdout/stderrを同一ファイルに出力
453
+ logFile = config.logFile;
454
+ }
455
+ else {
456
+ // 3. 未指定時 → デフォルトパス
457
+ logFile = path.join(this.logDir, `${appName}.jsonl`);
458
+ }
459
+ return {
460
+ appName,
461
+ namespace,
462
+ logFile,
463
+ outFile,
464
+ errorFile,
465
+ streams: {},
466
+ };
467
+ }
468
+ /**
469
+ * ログファイルの事前作成(FileManagerに委譲)
470
+ */
471
+ ensureLogFiles() {
472
+ // 統合ログファイル
473
+ if (this.logInfo.logFile) {
474
+ this.fileManager.ensureFileExists(this.logInfo.logFile);
475
+ }
476
+ // stdout専用ファイル
477
+ if (this.logInfo.outFile) {
478
+ this.fileManager.ensureFileExists(this.logInfo.outFile);
479
+ }
480
+ // stderr専用ファイル
481
+ if (this.logInfo.errorFile) {
482
+ this.fileManager.ensureFileExists(this.logInfo.errorFile);
483
+ }
484
+ }
485
+ /**
486
+ * プロセス出力のキャプチャとログ記録
487
+ * ProcessLifecycleManagerからの stdout/stderr データを処理
488
+ */
489
+ captureProcessOutput(type, data) {
490
+ // 既存の部分的な行を取得
491
+ const existingPartial = this.partialLines.get(type) || '';
492
+ const fullData = existingPartial + data;
493
+ // 改行で分割
494
+ const lines = fullData.split('\n');
495
+ // 最後の要素は部分的な行か空文字列
496
+ const partialLine = lines.pop() || '';
497
+ this.partialLines.set(type, partialLine);
498
+ // 完全な行を処理
499
+ for (const line of lines) {
500
+ if (line.length > 0) {
501
+ this.writeLogWithPreprocessing(type, line);
502
+ }
503
+ }
504
+ }
505
+ /**
506
+ * ログエントリの前処理を含む書き込み(10行以下に分割)
507
+ */
508
+ writeLogWithPreprocessing(type, message) {
509
+ const processedMessage = this.logPreprocessor.preprocessMessage(message);
510
+ const logEntry = this.createLogEntry(type, processedMessage);
511
+ const targetFiles = this.determineTargetFiles(type);
512
+ this.addToBufferAndEmitEvent(logEntry, targetFiles);
513
+ }
514
+ /**
515
+ * ログエントリの作成(10行以下)
516
+ */
517
+ createLogEntry(type, processedMessage) {
518
+ return {
519
+ timestamp: Date.now(),
520
+ level: this.logLevelStrategy.determineLevel(type, processedMessage),
521
+ message: processedMessage,
522
+ app: this.logInfo.appName,
523
+ namespace: this.logInfo.namespace,
524
+ type,
525
+ };
526
+ }
527
+ /**
528
+ * バッファ追加とイベント発行(10行以下)
529
+ */
530
+ addToBufferAndEmitEvent(logEntry, targetFiles) {
531
+ const bufferedEntry = {
532
+ logEntry,
533
+ targetFiles,
534
+ timestamp: Date.now(),
535
+ };
536
+ const added = this.logBuffer.addEntry(bufferedEntry);
537
+ this.handleBufferBackpressure(added);
538
+ this.eventManager.emitLogEvent(logEntry);
539
+ }
540
+ /**
541
+ * バッファバックプレッシャーの処理(10行以下)
542
+ */
543
+ handleBufferBackpressure(added) {
544
+ if (!added) {
545
+ const warningMessage = `Log buffer backpressure active for app: ${this.logInfo.appName}`;
546
+ console.warn(`[AppLogger] ${warningMessage}`);
547
+ // バックプレッシャーイベントを発行
548
+ this.eventEmitter.emit('bufferWarning', {
549
+ message: warningMessage,
550
+ appName: this.logInfo.appName,
551
+ timestamp: Date.now(),
552
+ });
553
+ }
554
+ }
555
+ /**
556
+ * 書き込み対象ファイルの決定(関数分割)
557
+ */
558
+ determineTargetFiles(type) {
559
+ const targetFiles = [];
560
+ if (this.logInfo.logFile) {
561
+ targetFiles.push(this.logInfo.logFile);
562
+ }
563
+ if (type === 'stdout' && this.logInfo.outFile) {
564
+ targetFiles.push(this.logInfo.outFile);
565
+ }
566
+ else if (type === 'stderr' && this.logInfo.errorFile) {
567
+ targetFiles.push(this.logInfo.errorFile);
568
+ }
569
+ return targetFiles;
570
+ }
571
+ /**
572
+ * ログエントリの書き込み(互換性のため維持)
573
+ */
574
+ writeLog(type, message) {
575
+ this.writeLogWithPreprocessing(type, message);
576
+ }
577
+ // preprocessLogMessage は LogPreprocessor に移動済み
578
+ /**
579
+ * バッファされたエントリをファイルに書き込み(20行以下に分割)
580
+ */
581
+ async flushBufferedEntries(entries) {
582
+ // ファイル別にエントリをグループ化
583
+ const fileEntries = this.groupEntriesByFile(entries);
584
+ // ファイル別に並列書き込み
585
+ const writePromises = this.createWritePromises(fileEntries);
586
+ // 全ての書き込みの完了を待機
587
+ await this.executeParallelWrites(writePromises);
588
+ }
589
+ /**
590
+ * エントリをファイル別にグループ化(関数分割)
591
+ */
592
+ groupEntriesByFile(entries) {
593
+ const fileEntries = new Map();
594
+ for (const bufferedEntry of entries) {
595
+ for (const filePath of bufferedEntry.targetFiles) {
596
+ if (!fileEntries.has(filePath)) {
597
+ fileEntries.set(filePath, []);
598
+ }
599
+ fileEntries.get(filePath).push(bufferedEntry.logEntry);
600
+ }
601
+ }
602
+ return fileEntries;
603
+ }
604
+ /**
605
+ * 書き込みPromise配列を作成(関数分割)
606
+ */
607
+ createWritePromises(fileEntries) {
608
+ const writePromises = [];
609
+ for (const [filePath, logEntries] of fileEntries) {
610
+ const logLines = logEntries
611
+ .map((entry) => JSON.stringify(entry) + '\n')
612
+ .join('');
613
+ const writePromise = this.writeToFileWithRetryAndHandleError(filePath, logLines);
614
+ writePromises.push(writePromise);
615
+ }
616
+ return writePromises;
617
+ }
618
+ /**
619
+ * 並列書き込みを実行(関数分割)
620
+ */
621
+ async executeParallelWrites(writePromises) {
622
+ try {
623
+ await Promise.allSettled(writePromises);
624
+ }
625
+ catch (error) {
626
+ // Promise.allSettledは例外を投げないが、念のため
627
+ console.error(`[AppLogger] Unexpected error during batch write: ${error.message}`);
628
+ }
629
+ }
630
+ /**
631
+ * リトライ機能付きファイル書き込みとエラーハンドリング
632
+ */
633
+ async writeToFileWithRetryAndHandleError(filePath, content) {
634
+ try {
635
+ // FileManager に委譲
636
+ await this.fileManager.writeToFileWithRetry(filePath, content, this.managerConfig.file.maxRetries);
637
+ }
638
+ catch (error) {
639
+ const lastError = error;
640
+ // ディスク容量不足の場合は特別な処理
641
+ if (lastError.message.includes('ENOSPC')) {
642
+ this.handleDiskSpaceError(filePath);
643
+ }
644
+ else {
645
+ // その他のエラー(権限エラーなど)もイベント発行
646
+ this.eventEmitter.emit('error', {
647
+ message: lastError.message,
648
+ filePath,
649
+ appName: this.logInfo.appName,
650
+ timestamp: Date.now(),
651
+ error: lastError,
652
+ });
653
+ }
654
+ // エラーを再スロー(上位でハンドリングできるように)
655
+ // throw lastError;
656
+ }
657
+ }
658
+ /**
659
+ * ディスク容量不足エラーの処理(EventManagerに委譲)
660
+ */
661
+ handleDiskSpaceError(filePath) {
662
+ const errorMessage = `Disk space error detected for ${filePath}. Consider implementing log rotation or cleanup.`;
663
+ console.error(`[AppLogger] ${errorMessage}`);
664
+ // イベントとして通知(EventManagerに委譲)
665
+ this.eventManager.emitDiskSpaceErrorEvent({
666
+ filePath,
667
+ appName: this.logInfo.appName,
668
+ timestamp: Date.now(),
669
+ message: errorMessage,
670
+ });
671
+ }
672
+ // determineLogLevel は LogLevelStrategy に移動済み
673
+ /**
674
+ * ファイル監視の開始
675
+ */
676
+ async startWatching() {
677
+ // 既存の監視を停止
678
+ this.stopWatching();
679
+ const filesToWatch = [];
680
+ if (this.logInfo.logFile) {
681
+ filesToWatch.push({ file: this.logInfo.logFile, type: 'combined' });
682
+ }
683
+ if (this.logInfo.outFile) {
684
+ filesToWatch.push({ file: this.logInfo.outFile, type: 'stdout' });
685
+ }
686
+ if (this.logInfo.errorFile) {
687
+ filesToWatch.push({ file: this.logInfo.errorFile, type: 'stderr' });
688
+ }
689
+ for (const { file, type } of filesToWatch) {
690
+ if (fs.existsSync(file)) {
691
+ try {
692
+ const watcher = fs.watch(file, (eventType) => {
693
+ if (eventType === 'change') {
694
+ // EventManager に委譲
695
+ this.eventManager.emitFileChangeEvent({
696
+ appName: this.logInfo.appName,
697
+ file,
698
+ type,
699
+ });
700
+ }
701
+ });
702
+ this.watchers.push(watcher);
703
+ }
704
+ catch (error) {
705
+ console.warn(`[AppLogger] Failed to watch ${file}: ${error.message}`);
706
+ }
707
+ }
708
+ }
709
+ }
710
+ /**
711
+ * ファイル監視の停止
712
+ */
713
+ stopWatching() {
714
+ for (const watcher of this.watchers) {
715
+ watcher.close();
716
+ }
717
+ this.watchers = [];
718
+ }
719
+ /**
720
+ * ログの読み込み(20行以下に分割)
721
+ */
722
+ async readLogs(options = {}) {
723
+ const { lines = 100 } = options;
724
+ // ログファイルの選択
725
+ const logFile = this.selectLogFile(options.type);
726
+ if (!logFile || !fs.existsSync(logFile)) {
727
+ return [];
728
+ }
729
+ // ファイルからログエントリを読み込み・解析
730
+ return this.readAndParseLogEntries(logFile, lines, options);
731
+ }
732
+ /**
733
+ * ログファイルの選択(関数分割)
734
+ */
735
+ selectLogFile(type) {
736
+ if (type && type.length === 1) {
737
+ switch (type[0]) {
738
+ case 'stdout':
739
+ return this.logInfo.outFile || this.logInfo.logFile;
740
+ case 'stderr':
741
+ return this.logInfo.errorFile || this.logInfo.logFile;
742
+ default:
743
+ return this.logInfo.logFile;
744
+ }
745
+ }
746
+ return this.logInfo.logFile;
747
+ }
748
+ /**
749
+ * ログエントリの読み込みと解析(関数分割)
750
+ */
751
+ async readAndParseLogEntries(logFile, lines, options) {
752
+ try {
753
+ // FileManager に委譲
754
+ const recentLines = await this.fileManager.readTailLines(logFile, lines);
755
+ return this.parseLogLines(recentLines, options);
756
+ }
757
+ catch (error) {
758
+ console.error(`[AppLogger] Failed to read log file ${logFile}: ${error.message}`);
759
+ return [];
760
+ }
761
+ }
762
+ /**
763
+ * ログ行の解析とフィルタリング(関数分割)
764
+ */
765
+ parseLogLines(lines, options) {
766
+ const logEntries = [];
767
+ for (const line of lines) {
768
+ try {
769
+ const entry = JSON.parse(line);
770
+ // フィルタリング適用
771
+ if (this.matchesFilter(entry, options)) {
772
+ logEntries.push(entry);
773
+ }
774
+ }
775
+ catch {
776
+ console.warn(`[AppLogger] Failed to parse log line: ${line}`);
777
+ }
778
+ }
779
+ return logEntries;
780
+ }
781
+ // readTailLines は FileManager に移動済み
782
+ /**
783
+ * ログエントリがフィルタ条件に一致するかチェック
784
+ */
785
+ matchesFilter(entry, options) {
786
+ // レベルフィルタ
787
+ if (options.level && !options.level.includes(entry.level)) {
788
+ return false;
789
+ }
790
+ // タイプフィルタ
791
+ if (options.type && !options.type.includes(entry.type)) {
792
+ return false;
793
+ }
794
+ // 時間範囲フィルタ
795
+ if (options.since && entry.timestamp < options.since) {
796
+ return false;
797
+ }
798
+ if (options.until && entry.timestamp > options.until) {
799
+ return false;
800
+ }
801
+ return true;
802
+ }
803
+ /**
804
+ * ログのクリア
805
+ */
806
+ async clearLogs() {
807
+ const filesToClear = [];
808
+ if (this.logInfo.logFile) {
809
+ filesToClear.push(this.logInfo.logFile);
810
+ }
811
+ if (this.logInfo.outFile) {
812
+ filesToClear.push(this.logInfo.outFile);
813
+ }
814
+ if (this.logInfo.errorFile) {
815
+ filesToClear.push(this.logInfo.errorFile);
816
+ }
817
+ for (const file of filesToClear) {
818
+ if (fs.existsSync(file)) {
819
+ fs.writeFileSync(file, '');
820
+ }
821
+ }
822
+ }
823
+ /**
824
+ * 残りの部分的な行を強制フラッシュ
825
+ */
826
+ flushPartialLines() {
827
+ for (const [type, partialLine] of this.partialLines) {
828
+ if (partialLine.trim().length > 0) {
829
+ this.writeLogWithPreprocessing(type, partialLine);
830
+ }
831
+ }
832
+ this.partialLines.clear();
833
+ }
834
+ /**
835
+ * バッファ統計情報の取得
836
+ */
837
+ getBufferStats() {
838
+ return this.logBuffer.getStats();
839
+ }
840
+ /**
841
+ * バッファを強制フラッシュ
842
+ */
843
+ async flushBuffer() {
844
+ await this.logBuffer.flush();
845
+ }
846
+ /**
847
+ * リソースのクリーンアップ
848
+ */
849
+ async close() {
850
+ // 残りの部分的な行をフラッシュ
851
+ this.flushPartialLines();
852
+ // バッファをクリーンアップ(フラッシュ含む)
853
+ await this.logBuffer.cleanup();
854
+ // ファイル監視停止
855
+ this.stopWatching();
856
+ // ストリーム管理は不要
857
+ this.logInfo.streams = {};
858
+ }
859
+ /**
860
+ * ログ統計情報の取得
861
+ */
862
+ getLogStats() {
863
+ const stats = {};
864
+ try {
865
+ if (this.logInfo.logFile && fs.existsSync(this.logInfo.logFile)) {
866
+ const stat = fs.statSync(this.logInfo.logFile);
867
+ stats.logFileSize = stat.size;
868
+ stats.lastModified = stat.mtime.getTime();
869
+ }
870
+ if (this.logInfo.outFile && fs.existsSync(this.logInfo.outFile)) {
871
+ const stat = fs.statSync(this.logInfo.outFile);
872
+ stats.outFileSize = stat.size;
873
+ }
874
+ if (this.logInfo.errorFile && fs.existsSync(this.logInfo.errorFile)) {
875
+ const stat = fs.statSync(this.logInfo.errorFile);
876
+ stats.errorFileSize = stat.size;
877
+ }
878
+ }
879
+ catch (error) {
880
+ console.warn(`[AppLogger] Failed to get stats: ${error.message}`);
881
+ }
882
+ return stats;
883
+ }
884
+ }
885
+ // =============================================================================
886
+ // LogManager Class
887
+ // =============================================================================
888
+ /**
889
+ * LogManager - ログ管理システムのメインクラス
890
+ */
891
+ export class LogManager extends EventEmitter {
892
+ logDir;
893
+ appLoggers = new Map();
894
+ fileManager;
895
+ config;
896
+ pathValidator;
897
+ securePathBuilder;
898
+ // Add EventCleanupHelper for proper listener cleanup
899
+ cleanup = new EventCleanupHelper();
900
+ constructor(logDir, fileManager, config = DEFAULT_LOG_MANAGER_CONFIG) {
901
+ super();
902
+ // Enhanced HOME detection: process.env.HOME || os.homedir()
903
+ const homeDir = process.env.HOME || os.homedir();
904
+ if (!homeDir && !logDir) {
905
+ throw new Error('Unable to determine home directory for log path');
906
+ }
907
+ this.logDir =
908
+ logDir || path.join(homeDir, '.masuidrive-procman', 'app-logs');
909
+ this.config = config;
910
+ this.fileManager = fileManager || new FileManager(this.config);
911
+ // No additional initialization needed for EventCleanupHelper
912
+ // パストラバーサル対策の初期化(テスト環境では緩い検証にする)
913
+ const pathValidationConfig = process.env.NODE_ENV === 'test'
914
+ ? { forbiddenPatterns: [/\0/] } // テスト環境ではnull byteのみ禁止
915
+ : undefined;
916
+ this.pathValidator = new LogPathValidator(this.logDir, pathValidationConfig);
917
+ this.securePathBuilder = new SecureLogPathBuilder(this.pathValidator);
918
+ this.ensureLogDirectory();
919
+ }
920
+ /**
921
+ * Private method to register and track listeners
922
+ */
923
+ registerListener(emitter, event, listener) {
924
+ this.cleanup.track(emitter, event, listener);
925
+ }
926
+ /**
927
+ * ログディレクトリの確認・作成(FileManagerに委譲)
928
+ */
929
+ ensureLogDirectory() {
930
+ this.fileManager.ensureLogDirectory(this.logDir);
931
+ }
932
+ /**
933
+ * Sanitize invalid app names to prevent errors
934
+ */
935
+ sanitizeAppName(appName) {
936
+ if (typeof appName !== 'string' || appName.trim() === '') {
937
+ return 'unknown-app';
938
+ }
939
+ // Remove invalid characters and limit length
940
+ const sanitized = appName
941
+ .replace(/[/\\<>:"|?*\0]/g, '_')
942
+ .replace(/^\.+|\.+$/g, '') // Remove leading/trailing dots
943
+ .substring(0, 64)
944
+ .trim();
945
+ return sanitized || 'sanitized-app';
946
+ }
947
+ /**
948
+ * アプリケーションのログ設定(セキュリティ検証付き)
949
+ */
950
+ setupAppLogs(appName, config = {}) {
951
+ // t_wada boundary principle: gracefully handle invalid inputs
952
+ if (appName == null) {
953
+ // Silently ignore null/undefined inputs as per test expectation
954
+ return;
955
+ }
956
+ // アプリケーション名のセキュリティ検証
957
+ let validatedAppName;
958
+ try {
959
+ validatedAppName = this.pathValidator.validateApplicationName(appName);
960
+ }
961
+ catch {
962
+ // Gracefully handle invalid app names by sanitizing them
963
+ validatedAppName = this.sanitizeAppName(appName);
964
+ }
965
+ // カスタムパスのセキュリティ検証
966
+ const secureConfig = this.validateAndSecureConfig(config);
967
+ // 既存のログ設定があれば閉じる
968
+ if (this.appLoggers.has(validatedAppName)) {
969
+ const existingLogger = this.appLoggers.get(validatedAppName);
970
+ if (existingLogger) {
971
+ existingLogger.close();
972
+ }
973
+ }
974
+ // 新しいAppLoggerを作成
975
+ const appLogger = new AppLogger(validatedAppName, secureConfig, this.logDir, this, this.config);
976
+ this.appLoggers.set(validatedAppName, appLogger);
977
+ }
978
+ /**
979
+ * プロセス出力のキャプチャとログ記録
980
+ * ProcessManagerからのstdout/stderrイベント処理
981
+ */
982
+ captureProcessOutput(appName, type, data) {
983
+ const appLogger = this.appLoggers.get(appName);
984
+ if (!appLogger) {
985
+ console.warn(`[LogManager] No log configuration for app: ${appName}`);
986
+ return;
987
+ }
988
+ appLogger.captureProcessOutput(type, data);
989
+ }
990
+ /**
991
+ * ログエントリの書き込み(互換性のため維持)
992
+ */
993
+ writeLog(appName, type, message) {
994
+ // t_wada boundary principle: gracefully handle invalid inputs
995
+ if (appName == null || type == null || message == null) {
996
+ // Silently ignore null/undefined inputs as per test expectation
997
+ return;
998
+ }
999
+ const appLogger = this.appLoggers.get(appName);
1000
+ if (!appLogger) {
1001
+ console.warn(`[LogManager] No log configuration for app: ${appName}`);
1002
+ return;
1003
+ }
1004
+ appLogger.writeLog(type, message);
1005
+ }
1006
+ /**
1007
+ * ファイル監視の開始
1008
+ */
1009
+ async startWatching(appName) {
1010
+ const appLogger = this.appLoggers.get(appName);
1011
+ if (!appLogger) {
1012
+ throw new Error(`No log configuration for app: ${appName}`);
1013
+ }
1014
+ await appLogger.startWatching();
1015
+ }
1016
+ /**
1017
+ * ファイル監視の停止
1018
+ */
1019
+ stopWatching(appName) {
1020
+ const appLogger = this.appLoggers.get(appName);
1021
+ if (appLogger) {
1022
+ appLogger.stopWatching();
1023
+ }
1024
+ }
1025
+ /**
1026
+ * ログの読み込み
1027
+ */
1028
+ async readLogs(appName, options = {}) {
1029
+ const appLogger = this.appLoggers.get(appName);
1030
+ if (!appLogger) {
1031
+ throw new Error(`No log configuration for app: ${appName}`);
1032
+ }
1033
+ return appLogger.readLogs(options);
1034
+ }
1035
+ /**
1036
+ * ログのクリア
1037
+ */
1038
+ async clearLogs(appName) {
1039
+ const appLogger = this.appLoggers.get(appName);
1040
+ if (!appLogger) {
1041
+ throw new Error(`No log configuration for app: ${appName}`);
1042
+ }
1043
+ await appLogger.clearLogs();
1044
+ }
1045
+ /**
1046
+ * ログ統計情報の取得
1047
+ */
1048
+ getLogStats(appName) {
1049
+ const appLogger = this.appLoggers.get(appName);
1050
+ if (!appLogger) {
1051
+ throw new Error(`No log configuration for app: ${appName}`);
1052
+ }
1053
+ return appLogger.getLogStats();
1054
+ }
1055
+ /**
1056
+ * バッファ統計情報の取得
1057
+ */
1058
+ getBufferStats(appName) {
1059
+ const appLogger = this.appLoggers.get(appName);
1060
+ if (!appLogger) {
1061
+ throw new Error(`No log configuration for app: ${appName}`);
1062
+ }
1063
+ return appLogger.getBufferStats();
1064
+ }
1065
+ /**
1066
+ * 全アプリケーションのバッファ統計情報の取得
1067
+ */
1068
+ getAllBufferStats() {
1069
+ const stats = {};
1070
+ for (const [appName, appLogger] of this.appLoggers) {
1071
+ stats[appName] = appLogger.getBufferStats();
1072
+ }
1073
+ return stats;
1074
+ }
1075
+ /**
1076
+ * 指定アプリケーションのバッファを強制フラッシュ
1077
+ */
1078
+ async flushBuffer(appName) {
1079
+ const appLogger = this.appLoggers.get(appName);
1080
+ if (!appLogger) {
1081
+ throw new Error(`No log configuration for app: ${appName}`);
1082
+ }
1083
+ // 型安全にバッファフラッシュ
1084
+ await appLogger.flushBuffer();
1085
+ // 部分的な行もフラッシュ
1086
+ appLogger.flushPartialLines();
1087
+ }
1088
+ /**
1089
+ * 全アプリケーションのバッファを強制フラッシュ
1090
+ */
1091
+ async flushAllBuffers() {
1092
+ const flushPromises = [];
1093
+ for (const appName of this.appLoggers.keys()) {
1094
+ const flushPromise = this.flushBuffer(appName).catch((error) => {
1095
+ console.warn(`[LogManager] Failed to flush buffer for app ${appName}: ${error.message}`);
1096
+ });
1097
+ flushPromises.push(flushPromise);
1098
+ }
1099
+ // 全てのフラッシュ操作を並列実行
1100
+ await Promise.allSettled(flushPromises);
1101
+ }
1102
+ /**
1103
+ * 全てのリソースのクリーンアップ
1104
+ */
1105
+ async close() {
1106
+ // Clean up all tracked listeners
1107
+ await this.cleanup.dispose();
1108
+ // Note: Don't log here as it can cause issues during shutdown
1109
+ // console.log(`[LogManager] Cleaned up ${cleanedUpListeners} managed listeners`);
1110
+ // Clean up app loggers
1111
+ for (const appLogger of this.appLoggers.values()) {
1112
+ await appLogger.close();
1113
+ }
1114
+ this.appLoggers.clear();
1115
+ // Clean up our own listeners (this should be empty after EventCleanupHelper cleanup)
1116
+ this.removeAllListeners();
1117
+ }
1118
+ /**
1119
+ * 設定のセキュリティ検証とパス正規化
1120
+ */
1121
+ validateAndSecureConfig(config) {
1122
+ const secureConfig = { ...config };
1123
+ // カスタムパスがある場合は検証
1124
+ if (config.logFile) {
1125
+ secureConfig.logFile = this.pathValidator.validateLogFilePath(config.logFile);
1126
+ }
1127
+ if (config.outFile) {
1128
+ secureConfig.outFile = this.pathValidator.validateLogFilePath(config.outFile);
1129
+ }
1130
+ if (config.errorFile) {
1131
+ secureConfig.errorFile = this.pathValidator.validateLogFilePath(config.errorFile);
1132
+ }
1133
+ return secureConfig;
1134
+ }
1135
+ /**
1136
+ * 登録されているアプリケーション一覧の取得
1137
+ */
1138
+ getAppNames() {
1139
+ return Array.from(this.appLoggers.keys());
1140
+ }
1141
+ /**
1142
+ * ログの人間向けフォーマット
1143
+ */
1144
+ formatLogForHuman(logEntry) {
1145
+ const timestamp = new Date(logEntry.timestamp);
1146
+ const timeStr = timestamp.toISOString().substring(11, 19); // HH:mm:ss
1147
+ const dateStr = timestamp.toISOString().substring(2, 10); // YY-MM-DD
1148
+ return `[${logEntry.app}] ${dateStr} ${timeStr} > ${logEntry.message}`;
1149
+ }
1150
+ /**
1151
+ * ログストリーミングの開始
1152
+ * 新しいログエントリが追加されるたびにリスナーを呼び出す
1153
+ *
1154
+ * Note: This method now tracks listeners but doesn't use the EventCleanupHelper
1155
+ * because it returns a cleanup function that the caller must use.
1156
+ * The caller is responsible for calling the cleanup function.
1157
+ */
1158
+ startLogStream(appName, listener) {
1159
+ // フィルタリング関数の作成
1160
+ // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
1161
+ const filterListener = (logEntry) => {
1162
+ // appNameが指定されている場合はフィルタリング
1163
+ if (appName && logEntry.app !== appName) {
1164
+ return;
1165
+ }
1166
+ listener(logEntry);
1167
+ };
1168
+ // new-logイベントのリスナーを登録
1169
+ this.on(LOG_STREAM_EVENTS.NEW_LOG, filterListener);
1170
+ // クリーンアップ関数を返す
1171
+ return () => {
1172
+ this.off(LOG_STREAM_EVENTS.NEW_LOG, filterListener);
1173
+ };
1174
+ }
1175
+ /**
1176
+ * ログストリーミング状態を取得
1177
+ */
1178
+ isStreamingActive() {
1179
+ // EventEmitterのリスナー数をチェック
1180
+ const listeners = this.listenerCount(LOG_STREAM_EVENTS.NEW_LOG);
1181
+ return listeners > 0;
1182
+ }
1183
+ /**
1184
+ * Get statistics about listener management
1185
+ */
1186
+ getListenerStats() {
1187
+ const ownEvents = this.eventNames();
1188
+ let ownListenersCount = 0;
1189
+ for (const event of ownEvents) {
1190
+ ownListenersCount += this.listenerCount(event);
1191
+ }
1192
+ return {
1193
+ managedListeners: this.cleanup.getListenerCount(),
1194
+ ownListeners: ownListenersCount,
1195
+ streamingListeners: this.listenerCount(LOG_STREAM_EVENTS.NEW_LOG),
1196
+ };
1197
+ }
1198
+ }
1199
+ //# sourceMappingURL=log-manager.js.map