@devskeo/ward-agent 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/config.d.ts +2 -0
- package/dist/cli/config.d.ts.map +1 -0
- package/dist/cli/config.js +30 -0
- package/dist/cli/config.js.map +1 -0
- package/dist/cli/pipe.d.ts +2 -0
- package/dist/cli/pipe.d.ts.map +1 -0
- package/dist/cli/pipe.js +53 -0
- package/dist/cli/pipe.js.map +1 -0
- package/dist/cli/service.d.ts +10 -0
- package/dist/cli/service.d.ts.map +1 -0
- package/dist/cli/service.js +134 -0
- package/dist/cli/service.js.map +1 -0
- package/dist/cli/start.d.ts +6 -0
- package/dist/cli/start.d.ts.map +1 -0
- package/dist/cli/start.js +160 -0
- package/dist/cli/start.js.map +1 -0
- package/dist/cli/status.d.ts +2 -0
- package/dist/cli/status.d.ts.map +1 -0
- package/dist/cli/status.js +56 -0
- package/dist/cli/status.js.map +1 -0
- package/dist/cli/stop.d.ts +2 -0
- package/dist/cli/stop.d.ts.map +1 -0
- package/dist/cli/stop.js +106 -0
- package/dist/cli/stop.js.map +1 -0
- package/dist/cli/systemd.d.ts +3 -0
- package/dist/cli/systemd.d.ts.map +1 -0
- package/dist/cli/systemd.js +84 -0
- package/dist/cli/systemd.js.map +1 -0
- package/dist/config/AgentConfig.d.ts +30 -0
- package/dist/config/AgentConfig.d.ts.map +1 -0
- package/dist/config/AgentConfig.js +155 -0
- package/dist/config/AgentConfig.js.map +1 -0
- package/dist/config/ServiceConfig.d.ts +30 -0
- package/dist/config/ServiceConfig.d.ts.map +1 -0
- package/dist/config/ServiceConfig.js +4 -0
- package/dist/config/ServiceConfig.js.map +1 -0
- package/dist/daemon.d.ts +2 -0
- package/dist/daemon.d.ts.map +1 -0
- package/dist/daemon.js +300 -0
- package/dist/daemon.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +94 -0
- package/dist/index.js.map +1 -0
- package/dist/logs/LogForwarder.d.ts +22 -0
- package/dist/logs/LogForwarder.d.ts.map +1 -0
- package/dist/logs/LogForwarder.js +83 -0
- package/dist/logs/LogForwarder.js.map +1 -0
- package/dist/logs/LogWatcher.d.ts +15 -0
- package/dist/logs/LogWatcher.d.ts.map +1 -0
- package/dist/logs/LogWatcher.js +111 -0
- package/dist/logs/LogWatcher.js.map +1 -0
- package/dist/logs/ServiceWatcher.d.ts +32 -0
- package/dist/logs/ServiceWatcher.d.ts.map +1 -0
- package/dist/logs/ServiceWatcher.js +294 -0
- package/dist/logs/ServiceWatcher.js.map +1 -0
- package/dist/metrics/CpuCollector.d.ts +9 -0
- package/dist/metrics/CpuCollector.d.ts.map +1 -0
- package/dist/metrics/CpuCollector.js +24 -0
- package/dist/metrics/CpuCollector.js.map +1 -0
- package/dist/metrics/DiskCollector.d.ts +11 -0
- package/dist/metrics/DiskCollector.d.ts.map +1 -0
- package/dist/metrics/DiskCollector.js +51 -0
- package/dist/metrics/DiskCollector.js.map +1 -0
- package/dist/metrics/IpCollector.d.ts +13 -0
- package/dist/metrics/IpCollector.d.ts.map +1 -0
- package/dist/metrics/IpCollector.js +34 -0
- package/dist/metrics/IpCollector.js.map +1 -0
- package/dist/metrics/MemoryCollector.d.ts +12 -0
- package/dist/metrics/MemoryCollector.d.ts.map +1 -0
- package/dist/metrics/MemoryCollector.js +27 -0
- package/dist/metrics/MemoryCollector.js.map +1 -0
- package/dist/metrics/NetworkCollector.d.ts +9 -0
- package/dist/metrics/NetworkCollector.d.ts.map +1 -0
- package/dist/metrics/NetworkCollector.js +24 -0
- package/dist/metrics/NetworkCollector.js.map +1 -0
- package/dist/metrics/ProcessCollector.d.ts +16 -0
- package/dist/metrics/ProcessCollector.d.ts.map +1 -0
- package/dist/metrics/ProcessCollector.js +34 -0
- package/dist/metrics/ProcessCollector.js.map +1 -0
- package/dist/start-dev.d.ts +2 -0
- package/dist/start-dev.d.ts.map +1 -0
- package/dist/start-dev.js +205 -0
- package/dist/start-dev.js.map +1 -0
- package/dist/start-self.d.ts +2 -0
- package/dist/start-self.d.ts.map +1 -0
- package/dist/start-self.js +271 -0
- package/dist/start-self.js.map +1 -0
- package/dist/transport/HttpClient.d.ts +53 -0
- package/dist/transport/HttpClient.d.ts.map +1 -0
- package/dist/transport/HttpClient.js +172 -0
- package/dist/transport/HttpClient.js.map +1 -0
- package/dist/transport/Queue.d.ts +29 -0
- package/dist/transport/Queue.d.ts.map +1 -0
- package/dist/transport/Queue.js +74 -0
- package/dist/transport/Queue.js.map +1 -0
- package/dist/transport/ReconnectManager.d.ts +14 -0
- package/dist/transport/ReconnectManager.d.ts.map +1 -0
- package/dist/transport/ReconnectManager.js +59 -0
- package/dist/transport/ReconnectManager.js.map +1 -0
- package/dist/types/index.d.ts +35 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +3 -0
- package/dist/types/index.js.map +1 -0
- package/package.json +41 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
// Ward 에이전트 CLI 진입점
|
|
5
|
+
const commander_1 = require("commander");
|
|
6
|
+
const start_js_1 = require("./cli/start.js");
|
|
7
|
+
const stop_js_1 = require("./cli/stop.js");
|
|
8
|
+
const status_js_1 = require("./cli/status.js");
|
|
9
|
+
const config_js_1 = require("./cli/config.js");
|
|
10
|
+
const service_js_1 = require("./cli/service.js");
|
|
11
|
+
const pipe_js_1 = require("./cli/pipe.js");
|
|
12
|
+
const AgentConfig_js_1 = require("./config/AgentConfig.js");
|
|
13
|
+
const program = new commander_1.Command();
|
|
14
|
+
program
|
|
15
|
+
.name('ward')
|
|
16
|
+
.description('Ward 서버 모니터링 에이전트')
|
|
17
|
+
.version('0.1.0');
|
|
18
|
+
// ward start [serverUrl] - 에이전트 시작
|
|
19
|
+
program
|
|
20
|
+
.command('start [serverUrl]')
|
|
21
|
+
.description('에이전트를 시작합니다. 처음 실행 시 서버 URL을 지정하세요.')
|
|
22
|
+
.option('--name <groupName>', '서버 그룹명 (클러스터 환경에서 서버를 그룹화)')
|
|
23
|
+
.action(async (serverUrl, options) => {
|
|
24
|
+
if (!serverUrl) {
|
|
25
|
+
// 기존 config에서 URL 로드
|
|
26
|
+
const config = (0, AgentConfig_js_1.loadConfig)();
|
|
27
|
+
if (!config?.server?.url) {
|
|
28
|
+
console.error('서버 URL이 필요합니다. 예: ward start https://ward.example.com');
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
serverUrl = config.server.url;
|
|
32
|
+
}
|
|
33
|
+
await (0, start_js_1.start)(serverUrl, options ?? {});
|
|
34
|
+
});
|
|
35
|
+
// ward stop - 에이전트 중지
|
|
36
|
+
program
|
|
37
|
+
.command('stop')
|
|
38
|
+
.description('에이전트를 중지하고 서버에서 등록을 해제합니다')
|
|
39
|
+
.action(async () => {
|
|
40
|
+
await (0, stop_js_1.stop)();
|
|
41
|
+
});
|
|
42
|
+
// ward status - 에이전트 상태 확인
|
|
43
|
+
program
|
|
44
|
+
.command('status')
|
|
45
|
+
.description('에이전트 실행 상태를 확인합니다')
|
|
46
|
+
.action(() => {
|
|
47
|
+
(0, status_js_1.status)();
|
|
48
|
+
});
|
|
49
|
+
// ward config - 설정 관리
|
|
50
|
+
const configCmd = program
|
|
51
|
+
.command('config')
|
|
52
|
+
.description('에이전트 설정을 관리합니다');
|
|
53
|
+
configCmd
|
|
54
|
+
.command('show')
|
|
55
|
+
.description('현재 설정을 출력합니다')
|
|
56
|
+
.action(() => {
|
|
57
|
+
(0, config_js_1.configShow)();
|
|
58
|
+
});
|
|
59
|
+
// ward service - 로그 수집 서비스 관리
|
|
60
|
+
const serviceCmd = program
|
|
61
|
+
.command('service')
|
|
62
|
+
.description('로그 수집 서비스 관리');
|
|
63
|
+
serviceCmd
|
|
64
|
+
.command('add <name>')
|
|
65
|
+
.description('로그 수집 서비스 등록')
|
|
66
|
+
.option('--log <path>', '감시할 로그 파일 경로 (여러 번 사용 가능)', (v, prev) => [...(prev ?? []), v], [])
|
|
67
|
+
.option('--exec <command>', '실행할 명령어 (stdout/stderr 수집)')
|
|
68
|
+
.option('--cwd <dir>', '명령어 실행 디렉토리 (--exec와 함께 사용)')
|
|
69
|
+
.option('--journal <unit>', 'systemd 유닛 이름 (예: nginx.service)')
|
|
70
|
+
.option('--docker <container>', '도커 컨테이너 이름')
|
|
71
|
+
.action(async (name, options) => {
|
|
72
|
+
await (0, service_js_1.serviceAdd)(name, options);
|
|
73
|
+
});
|
|
74
|
+
serviceCmd
|
|
75
|
+
.command('remove <name>')
|
|
76
|
+
.description('서비스 제거')
|
|
77
|
+
.action(async (name) => {
|
|
78
|
+
await (0, service_js_1.serviceRemove)(name);
|
|
79
|
+
});
|
|
80
|
+
serviceCmd
|
|
81
|
+
.command('list')
|
|
82
|
+
.description('등록된 서비스 목록')
|
|
83
|
+
.action(() => {
|
|
84
|
+
(0, service_js_1.serviceList)();
|
|
85
|
+
});
|
|
86
|
+
// ward pipe <name> - stdin을 로그로 전송
|
|
87
|
+
program
|
|
88
|
+
.command('pipe <serviceName>')
|
|
89
|
+
.description('stdin을 지정한 서비스 이름으로 로그 전송\n예: node app.js 2>&1 | ward pipe my-api')
|
|
90
|
+
.action(async (serviceName) => {
|
|
91
|
+
await (0, pipe_js_1.pipe)(serviceName);
|
|
92
|
+
});
|
|
93
|
+
program.parse(process.argv);
|
|
94
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { HttpClient } from '../transport/HttpClient.js';
|
|
2
|
+
export interface LogForwarderOptions {
|
|
3
|
+
client: HttpClient;
|
|
4
|
+
batchSize?: number;
|
|
5
|
+
flushIntervalMs?: number;
|
|
6
|
+
}
|
|
7
|
+
export declare class LogForwarder {
|
|
8
|
+
private readonly client;
|
|
9
|
+
private readonly batchSize;
|
|
10
|
+
private readonly flushIntervalMs;
|
|
11
|
+
private readonly queue;
|
|
12
|
+
private buffer;
|
|
13
|
+
private flushTimer;
|
|
14
|
+
constructor(options: LogForwarderOptions);
|
|
15
|
+
start(): void;
|
|
16
|
+
stop(): Promise<void>;
|
|
17
|
+
addLog(source: string, line: string, level?: string): void;
|
|
18
|
+
get bufferSize(): number;
|
|
19
|
+
private _flush;
|
|
20
|
+
private _retryQueue;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=LogForwarder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LogForwarder.d.ts","sourceRoot":"","sources":["../../src/logs/LogForwarder.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAIxD,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,UAAU,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAGD,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAa;IACpC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAQ;IAC9B,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,UAAU,CAA+C;gBAErD,OAAO,EAAE,mBAAmB;IAQxC,KAAK,IAAI,IAAI;IASP,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAU3B,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,SAAS,GAAG,IAAI;IAiB1D,IAAI,UAAU,IAAI,MAAM,CAEvB;YAGa,MAAM;YAkBN,WAAW;CAY1B"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LogForwarder = void 0;
|
|
4
|
+
const Queue_js_1 = require("../transport/Queue.js");
|
|
5
|
+
// 로그 라인을 버퍼에 쌓다가 배치로 서버에 전송
|
|
6
|
+
class LogForwarder {
|
|
7
|
+
client;
|
|
8
|
+
batchSize;
|
|
9
|
+
flushIntervalMs;
|
|
10
|
+
queue;
|
|
11
|
+
buffer = [];
|
|
12
|
+
flushTimer = null;
|
|
13
|
+
constructor(options) {
|
|
14
|
+
this.client = options.client;
|
|
15
|
+
this.batchSize = options.batchSize ?? 100;
|
|
16
|
+
this.flushIntervalMs = options.flushIntervalMs ?? 5000;
|
|
17
|
+
this.queue = new Queue_js_1.Queue({ maxSize: 10000, maxRetries: 3 });
|
|
18
|
+
}
|
|
19
|
+
// 포워더 시작 (주기적 플러시 타이머 등록)
|
|
20
|
+
start() {
|
|
21
|
+
if (this.flushTimer)
|
|
22
|
+
return;
|
|
23
|
+
this.flushTimer = setInterval(() => {
|
|
24
|
+
void this._flush();
|
|
25
|
+
}, this.flushIntervalMs);
|
|
26
|
+
}
|
|
27
|
+
// 포워더 중단
|
|
28
|
+
async stop() {
|
|
29
|
+
if (this.flushTimer) {
|
|
30
|
+
clearInterval(this.flushTimer);
|
|
31
|
+
this.flushTimer = null;
|
|
32
|
+
}
|
|
33
|
+
// 남은 버퍼 전송 시도
|
|
34
|
+
await this._flush();
|
|
35
|
+
}
|
|
36
|
+
// 로그 라인 추가
|
|
37
|
+
addLog(source, line, level = 'info') {
|
|
38
|
+
const entry = {
|
|
39
|
+
source,
|
|
40
|
+
level,
|
|
41
|
+
message: line,
|
|
42
|
+
loggedAt: new Date().toISOString(),
|
|
43
|
+
};
|
|
44
|
+
this.buffer.push(entry);
|
|
45
|
+
// 배치 크기 도달 시 즉시 전송
|
|
46
|
+
if (this.buffer.length >= this.batchSize) {
|
|
47
|
+
void this._flush();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// 현재 버퍼 크기 (테스트용)
|
|
51
|
+
get bufferSize() {
|
|
52
|
+
return this.buffer.length;
|
|
53
|
+
}
|
|
54
|
+
// 내부: 버퍼를 서버로 전송
|
|
55
|
+
async _flush() {
|
|
56
|
+
if (this.buffer.length === 0)
|
|
57
|
+
return;
|
|
58
|
+
const batch = this.buffer.splice(0, this.batchSize);
|
|
59
|
+
// 먼저 큐에 쌓인 실패 항목 재전송 시도
|
|
60
|
+
await this._retryQueue();
|
|
61
|
+
const result = await this.client.post('/api/agent/logs', { logs: batch });
|
|
62
|
+
if (!result.success) {
|
|
63
|
+
// 전송 실패 시 큐에 버퍼링
|
|
64
|
+
this.queue.enqueue('/api/agent/logs', { logs: batch });
|
|
65
|
+
console.error('[LogForwarder] 로그 전송 실패, 큐에 저장:', result.error);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// 내부: 큐에 쌓인 항목 재전송
|
|
69
|
+
async _retryQueue() {
|
|
70
|
+
if (this.queue.isEmpty)
|
|
71
|
+
return;
|
|
72
|
+
const items = this.queue.dequeueAll();
|
|
73
|
+
for (const item of items) {
|
|
74
|
+
const result = await this.client.post(item.path, item.data);
|
|
75
|
+
if (!result.success) {
|
|
76
|
+
// 재시도 횟수 초과 시 폐기
|
|
77
|
+
this.queue.requeueItem(item);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
exports.LogForwarder = LogForwarder;
|
|
83
|
+
//# sourceMappingURL=LogForwarder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LogForwarder.js","sourceRoot":"","sources":["../../src/logs/LogForwarder.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
export interface LogWatcherEvents {
|
|
3
|
+
line: (source: string, line: string) => void;
|
|
4
|
+
error: (err: Error) => void;
|
|
5
|
+
}
|
|
6
|
+
export declare class LogWatcher extends EventEmitter {
|
|
7
|
+
private watchedFiles;
|
|
8
|
+
watch(filePath: string, source: string): void;
|
|
9
|
+
unwatch(filePath: string): void;
|
|
10
|
+
unwatchAll(): void;
|
|
11
|
+
private _startWatcher;
|
|
12
|
+
private _handleFileChange;
|
|
13
|
+
getWatchedSources(): string[];
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=LogWatcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LogWatcher.d.ts","sourceRoot":"","sources":["../../src/logs/LogWatcher.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAGtC,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7C,KAAK,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,CAAC;CAC7B;AAWD,qBAAa,UAAW,SAAQ,YAAY;IAC1C,OAAO,CAAC,YAAY,CAAuC;IAG3D,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;IA4B7C,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAU/B,UAAU,IAAI,IAAI;IAQlB,OAAO,CAAC,aAAa;IAoBrB,OAAO,CAAC,iBAAiB;IAwCzB,iBAAiB,IAAI,MAAM,EAAE;CAG9B"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.LogWatcher = void 0;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const events_1 = require("events");
|
|
10
|
+
// fs.watch + 파일 포인터 방식으로 tail -f 구현
|
|
11
|
+
class LogWatcher extends events_1.EventEmitter {
|
|
12
|
+
watchedFiles = new Map();
|
|
13
|
+
// 로그 파일 감시 등록
|
|
14
|
+
watch(filePath, source) {
|
|
15
|
+
const absPath = path_1.default.resolve(filePath);
|
|
16
|
+
if (this.watchedFiles.has(absPath)) {
|
|
17
|
+
return; // 이미 감시 중
|
|
18
|
+
}
|
|
19
|
+
const entry = {
|
|
20
|
+
filePath: absPath,
|
|
21
|
+
source,
|
|
22
|
+
position: 0,
|
|
23
|
+
watcher: null,
|
|
24
|
+
};
|
|
25
|
+
// 파일이 존재하면 끝 위치부터 읽기 시작 (기존 내용 무시)
|
|
26
|
+
try {
|
|
27
|
+
const stat = fs_1.default.statSync(absPath);
|
|
28
|
+
entry.position = stat.size;
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
// 파일이 없으면 0부터 시작 (파일 생성 대기)
|
|
32
|
+
entry.position = 0;
|
|
33
|
+
}
|
|
34
|
+
this.watchedFiles.set(absPath, entry);
|
|
35
|
+
this._startWatcher(entry);
|
|
36
|
+
}
|
|
37
|
+
// 특정 파일 감시 중단
|
|
38
|
+
unwatch(filePath) {
|
|
39
|
+
const absPath = path_1.default.resolve(filePath);
|
|
40
|
+
const entry = this.watchedFiles.get(absPath);
|
|
41
|
+
if (entry) {
|
|
42
|
+
entry.watcher?.close();
|
|
43
|
+
this.watchedFiles.delete(absPath);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// 모든 감시 중단
|
|
47
|
+
unwatchAll() {
|
|
48
|
+
for (const [, entry] of this.watchedFiles) {
|
|
49
|
+
entry.watcher?.close();
|
|
50
|
+
}
|
|
51
|
+
this.watchedFiles.clear();
|
|
52
|
+
}
|
|
53
|
+
// 내부: fs.FSWatcher 시작
|
|
54
|
+
_startWatcher(entry) {
|
|
55
|
+
try {
|
|
56
|
+
const watcher = fs_1.default.watch(entry.filePath, { persistent: false }, (eventType) => {
|
|
57
|
+
if (eventType === 'change' || eventType === 'rename') {
|
|
58
|
+
this._handleFileChange(entry);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
watcher.on('error', (err) => {
|
|
62
|
+
this.emit('error', err);
|
|
63
|
+
});
|
|
64
|
+
entry.watcher = watcher;
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// 파일이 없으면 감시를 시작할 수 없음 — 정상 상황
|
|
68
|
+
entry.watcher = null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// 내부: 파일 변경 처리
|
|
72
|
+
_handleFileChange(entry) {
|
|
73
|
+
try {
|
|
74
|
+
const stat = fs_1.default.statSync(entry.filePath);
|
|
75
|
+
// 파일 로테이션 감지: 파일 크기가 현재 포인터보다 작아지면 처음부터 다시 읽기
|
|
76
|
+
if (stat.size < entry.position) {
|
|
77
|
+
entry.position = 0;
|
|
78
|
+
}
|
|
79
|
+
if (stat.size <= entry.position) {
|
|
80
|
+
return; // 새 내용 없음
|
|
81
|
+
}
|
|
82
|
+
// 새로 추가된 부분만 읽기
|
|
83
|
+
const fd = fs_1.default.openSync(entry.filePath, 'r');
|
|
84
|
+
const length = stat.size - entry.position;
|
|
85
|
+
const buffer = Buffer.alloc(length);
|
|
86
|
+
fs_1.default.readSync(fd, buffer, 0, length, entry.position);
|
|
87
|
+
fs_1.default.closeSync(fd);
|
|
88
|
+
entry.position = stat.size;
|
|
89
|
+
// 라인 단위로 분리하여 이벤트 발생
|
|
90
|
+
const text = buffer.toString('utf8');
|
|
91
|
+
const lines = text.split('\n');
|
|
92
|
+
for (const line of lines) {
|
|
93
|
+
const trimmed = line.trimEnd();
|
|
94
|
+
if (trimmed.length > 0) {
|
|
95
|
+
this.emit('line', entry.source, trimmed);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
if (err instanceof Error) {
|
|
101
|
+
this.emit('error', err);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// 감시 중인 파일 목록 반환 (테스트용)
|
|
106
|
+
getWatchedSources() {
|
|
107
|
+
return Array.from(this.watchedFiles.values()).map((e) => e.source);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
exports.LogWatcher = LogWatcher;
|
|
111
|
+
//# sourceMappingURL=LogWatcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LogWatcher.js","sourceRoot":"","sources":["../../src/logs/LogWatcher.ts"],"names":[],"mappings":";;;;;;AAAA,4CAAoB;AACpB,gDAAwB;AACxB,mCAAsC;AAgBtC,oCAAoC;AACpC,MAAa,UAAW,SAAQ,qBAAY;IAClC,YAAY,GAA6B,IAAI,GAAG,EAAE,CAAC;IAE3D,cAAc;IACd,KAAK,CAAC,QAAgB,EAAE,MAAc;QACpC,MAAM,OAAO,GAAG,cAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAEvC,IAAI,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;YACnC,OAAO,CAAC,UAAU;QACpB,CAAC;QAED,MAAM,KAAK,GAAgB;YACzB,QAAQ,EAAE,OAAO;YACjB,MAAM;YACN,QAAQ,EAAE,CAAC;YACX,OAAO,EAAE,IAAI;SACd,CAAC;QAEF,mCAAmC;QACnC,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,YAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;YAClC,KAAK,CAAC,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC;QAC7B,CAAC;QAAC,MAAM,CAAC;YACP,4BAA4B;YAC5B,KAAK,CAAC,QAAQ,GAAG,CAAC,CAAC;QACrB,CAAC;QAED,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QACtC,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;IAC5B,CAAC;IAED,cAAc;IACd,OAAO,CAAC,QAAgB;QACtB,MAAM,OAAO,GAAG,cAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QACvC,MAAM,KAAK,GAAG,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAC7C,IAAI,KAAK,EAAE,CAAC;YACV,KAAK,CAAC,OAAO,EAAE,KAAK,EAAE,CAAC;YACvB,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QACpC,CAAC;IACH,CAAC;IAED,WAAW;IACX,UAAU;QACR,KAAK,MAAM,CAAC,EAAE,KAAK,CAAC,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;YAC1C,KAAK,CAAC,OAAO,EAAE,KAAK,EAAE,CAAC;QACzB,CAAC;QACD,IAAI,CAAC,YAAY,CAAC,KAAK,EAAE,CAAC;IAC5B,CAAC;IAED,sBAAsB;IACd,aAAa,CAAC,KAAkB;QACtC,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,YAAE,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,EAAE,CAAC,SAAS,EAAE,EAAE;gBAC5E,IAAI,SAAS,KAAK,QAAQ,IAAI,SAAS,KAAK,QAAQ,EAAE,CAAC;oBACrD,IAAI,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAC;gBAChC,CAAC;YACH,CAAC,CAAC,CAAC;YAEH,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;gBAC1B,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;YAC1B,CAAC,CAAC,CAAC;YAEH,KAAK,CAAC,OAAO,GAAG,OAAO,CAAC;QAC1B,CAAC;QAAC,MAAM,CAAC;YACP,+BAA+B;YAC/B,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC;QACvB,CAAC;IACH,CAAC;IAED,eAAe;IACP,iBAAiB,CAAC,KAAkB;QAC1C,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,YAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;YAEzC,8CAA8C;YAC9C,IAAI,IAAI,CAAC,IAAI,GAAG,KAAK,CAAC,QAAQ,EAAE,CAAC;gBAC/B,KAAK,CAAC,QAAQ,GAAG,CAAC,CAAC;YACrB,CAAC;YAED,IAAI,IAAI,CAAC,IAAI,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;gBAChC,OAAO,CAAC,UAAU;YACpB,CAAC;YAED,gBAAgB;YAChB,MAAM,EAAE,GAAG,YAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;YAC5C,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,GAAG,KAAK,CAAC,QAAQ,CAAC;YAC1C,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;YACpC,YAAE,CAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC;YACnD,YAAE,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;YAEjB,KAAK,CAAC,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC;YAE3B,qBAAqB;YACrB,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;YACrC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAE/B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;gBACzB,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;gBAC/B,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBACvB,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;gBAC3C,CAAC;YACH,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,GAAG,YAAY,KAAK,EAAE,CAAC;gBACzB,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;YAC1B,CAAC;QACH,CAAC;IACH,CAAC;IAED,wBAAwB;IACxB,iBAAiB;QACf,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;IACrE,CAAC;CACF;AAjHD,gCAiHC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
import type { ServiceConfig } from '../config/ServiceConfig.js';
|
|
3
|
+
/**
|
|
4
|
+
* file / exec / journal / docker / pipe 방식으로
|
|
5
|
+
* 서비스 로그를 수집해 'line' 이벤트를 발생시킨다.
|
|
6
|
+
*/
|
|
7
|
+
export declare class ServiceWatcher extends EventEmitter {
|
|
8
|
+
private entries;
|
|
9
|
+
private lineBuffers;
|
|
10
|
+
watch(config: ServiceConfig): void;
|
|
11
|
+
unwatch(name: string): void;
|
|
12
|
+
unwatchAll(): void;
|
|
13
|
+
unwatchAllAndWait(timeoutMs?: number): Promise<void>;
|
|
14
|
+
getWatchedNames(): string[];
|
|
15
|
+
private _watchFile;
|
|
16
|
+
private _startFileWatcher;
|
|
17
|
+
private _handleFileChange;
|
|
18
|
+
private _watchExec;
|
|
19
|
+
private _spawnExec;
|
|
20
|
+
private _watchJournal;
|
|
21
|
+
private _watchDocker;
|
|
22
|
+
private _watchPipe;
|
|
23
|
+
restart(name: string): void;
|
|
24
|
+
getServiceStatus(name: string): {
|
|
25
|
+
status: 'running' | 'stopped' | 'unknown';
|
|
26
|
+
pid?: number;
|
|
27
|
+
restartCount: number;
|
|
28
|
+
startedAt?: Date;
|
|
29
|
+
};
|
|
30
|
+
private _emitLines;
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=ServiceWatcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ServiceWatcher.d.ts","sourceRoot":"","sources":["../../src/logs/ServiceWatcher.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAGtC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AA0BhE;;;GAGG;AACH,qBAAa,cAAe,SAAQ,YAAY;IAC9C,OAAO,CAAC,OAAO,CAAsC;IACrD,OAAO,CAAC,WAAW,CAAkB;IAGrC,KAAK,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI;IAuBlC,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAiC3B,UAAU,IAAI,IAAI;IAOZ,iBAAiB,CAAC,SAAS,SAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IA0BxD,eAAe,IAAI,MAAM,EAAE;IAM3B,OAAO,CAAC,UAAU;IAqBlB,OAAO,CAAC,iBAAiB;IAczB,OAAO,CAAC,iBAAiB;IAuBzB,OAAO,CAAC,UAAU;IAOlB,OAAO,CAAC,UAAU;IAiDlB,OAAO,CAAC,aAAa;IAerB,OAAO,CAAC,YAAY;IAepB,OAAO,CAAC,UAAU;IAclB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAe3B,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG;QAAE,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,IAAI,CAAA;KAAE;IAkBnI,OAAO,CAAC,UAAU;CAenB"}
|