@vite-plugin-opencode-assistant/shared 1.0.59 → 1.0.60
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/es/file-log-watcher.d.ts +14 -44
- package/es/file-log-watcher.mjs +154 -170
- package/lib/file-log-watcher.cjs +156 -171
- package/lib/file-log-watcher.d.ts +14 -44
- package/package.json +1 -1
package/es/file-log-watcher.d.ts
CHANGED
|
@@ -4,50 +4,20 @@ export interface FileLogEntry {
|
|
|
4
4
|
timestamp: string;
|
|
5
5
|
source: string;
|
|
6
6
|
}
|
|
7
|
-
export interface
|
|
7
|
+
export interface LogFileOptions {
|
|
8
8
|
name: string;
|
|
9
9
|
filePath: string;
|
|
10
|
-
maxBufferSize?: number;
|
|
11
|
-
watchExisting?: boolean;
|
|
12
10
|
}
|
|
13
|
-
declare
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
private processLogContent;
|
|
27
|
-
private detectLogLevel;
|
|
28
|
-
addEntry(entry: FileLogEntry): void;
|
|
29
|
-
getLogs(options?: {
|
|
30
|
-
level?: FileLogEntry["level"] | FileLogEntry["level"][];
|
|
31
|
-
limit?: number;
|
|
32
|
-
since?: string;
|
|
33
|
-
}): FileLogEntry[];
|
|
34
|
-
clear(): void;
|
|
35
|
-
size(): number;
|
|
36
|
-
setEnabled(enabled: boolean): void;
|
|
37
|
-
isEnabled(): boolean;
|
|
38
|
-
getName(): string;
|
|
39
|
-
getFilePath(): string;
|
|
40
|
-
}
|
|
41
|
-
export declare class ServiceLogWatcher {
|
|
42
|
-
private buffers;
|
|
43
|
-
private projectRoot;
|
|
44
|
-
setProjectRoot(root: string): void;
|
|
45
|
-
addLogFile(options: FileLogBufferOptions): void;
|
|
46
|
-
removeLogFile(name: string): void;
|
|
47
|
-
getBuffer(name: string): FileLogBuffer | undefined;
|
|
48
|
-
getAllBuffers(): Map<string, FileLogBuffer>;
|
|
49
|
-
stopAll(): void;
|
|
50
|
-
getLogFileNames(): string[];
|
|
51
|
-
}
|
|
52
|
-
export declare function getServiceLogWatcher(): ServiceLogWatcher;
|
|
53
|
-
export {};
|
|
11
|
+
export declare function readLogFile(options: LogFileOptions & {
|
|
12
|
+
projectRoot?: string;
|
|
13
|
+
level?: ("info" | "warn" | "error") | ("info" | "warn" | "error")[];
|
|
14
|
+
limit?: number;
|
|
15
|
+
since?: string;
|
|
16
|
+
}): Promise<FileLogEntry[]>;
|
|
17
|
+
export declare function readLogFileTail(options: LogFileOptions & {
|
|
18
|
+
projectRoot?: string;
|
|
19
|
+
lines?: number;
|
|
20
|
+
limit?: number;
|
|
21
|
+
level?: ("info" | "warn" | "error") | ("info" | "warn" | "error")[];
|
|
22
|
+
since?: string;
|
|
23
|
+
}): Promise<FileLogEntry[]>;
|
package/es/file-log-watcher.mjs
CHANGED
|
@@ -1,193 +1,177 @@
|
|
|
1
|
-
var
|
|
2
|
-
|
|
3
|
-
var
|
|
1
|
+
var __async = (__this, __arguments, generator) => {
|
|
2
|
+
return new Promise((resolve, reject) => {
|
|
3
|
+
var fulfilled = (value) => {
|
|
4
|
+
try {
|
|
5
|
+
step(generator.next(value));
|
|
6
|
+
} catch (e) {
|
|
7
|
+
reject(e);
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
var rejected = (value) => {
|
|
11
|
+
try {
|
|
12
|
+
step(generator.throw(value));
|
|
13
|
+
} catch (e) {
|
|
14
|
+
reject(e);
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
|
|
18
|
+
step((generator = generator.apply(__this, __arguments)).next());
|
|
19
|
+
});
|
|
20
|
+
};
|
|
4
21
|
import fs from "fs";
|
|
5
22
|
import path from "path";
|
|
6
23
|
import { createLogger } from "./logger.mjs";
|
|
7
|
-
const log = createLogger("
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
__publicField(this, "watcher", null);
|
|
16
|
-
__publicField(this, "enabled", false);
|
|
17
|
-
var _a;
|
|
18
|
-
this.name = options.name;
|
|
19
|
-
this.filePath = options.filePath;
|
|
20
|
-
this.maxSize = (_a = options.maxBufferSize) != null ? _a : 200;
|
|
21
|
-
this.enabled = true;
|
|
24
|
+
const log = createLogger("FileLogReader");
|
|
25
|
+
function detectLogLevel(line) {
|
|
26
|
+
const lowerLine = line.toLowerCase();
|
|
27
|
+
if (lowerLine.includes("error") || lowerLine.includes("err") || lowerLine.includes("fatal")) {
|
|
28
|
+
return "error";
|
|
29
|
+
}
|
|
30
|
+
if (lowerLine.includes("warn") || lowerLine.includes("warning")) {
|
|
31
|
+
return "warn";
|
|
22
32
|
}
|
|
23
|
-
|
|
24
|
-
|
|
33
|
+
return "info";
|
|
34
|
+
}
|
|
35
|
+
function parseLogTimestamp(line) {
|
|
36
|
+
const timestampPatterns = [
|
|
37
|
+
/(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z?)/,
|
|
38
|
+
/(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})/,
|
|
39
|
+
/(\[([^\]]+)\])/
|
|
40
|
+
];
|
|
41
|
+
for (const pattern of timestampPatterns) {
|
|
42
|
+
const match = line.match(pattern);
|
|
43
|
+
if (match) {
|
|
44
|
+
const timestampStr = match[1];
|
|
45
|
+
const date = new Date(timestampStr);
|
|
46
|
+
if (!isNaN(date.getTime())) {
|
|
47
|
+
return date.toISOString();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
function readLogFile(options) {
|
|
54
|
+
return __async(this, null, function* () {
|
|
55
|
+
const { name, filePath, projectRoot, level, limit, since } = options;
|
|
56
|
+
const resolvedPath = resolvePath(filePath, projectRoot);
|
|
25
57
|
if (!fs.existsSync(resolvedPath)) {
|
|
26
58
|
log.debug(`Log file does not exist: ${resolvedPath}`);
|
|
27
|
-
return;
|
|
59
|
+
return [];
|
|
28
60
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
61
|
+
try {
|
|
62
|
+
const content = yield fs.promises.readFile(resolvedPath, "utf-8");
|
|
63
|
+
const lines = content.split("\n").filter((line) => line.trim());
|
|
64
|
+
const entries = [];
|
|
65
|
+
const sinceDate = since ? new Date(since) : null;
|
|
66
|
+
for (const line of lines) {
|
|
67
|
+
const entry = {
|
|
68
|
+
level: detectLogLevel(line),
|
|
69
|
+
message: line,
|
|
70
|
+
timestamp: parseLogTimestamp(line) || (/* @__PURE__ */ new Date()).toISOString(),
|
|
71
|
+
source: `file:${name}`
|
|
72
|
+
};
|
|
73
|
+
if (sinceDate && new Date(entry.timestamp) < sinceDate) {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (level) {
|
|
77
|
+
const levels = Array.isArray(level) ? level : [level];
|
|
78
|
+
if (!levels.includes(entry.level)) {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
entries.push(entry);
|
|
34
83
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
if (this.watcher) {
|
|
43
|
-
this.watcher.close();
|
|
44
|
-
this.watcher = null;
|
|
45
|
-
log.debug(`Stopped watching log file: ${this.filePath}`);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
resolvePath(projectRoot) {
|
|
49
|
-
if (path.isAbsolute(this.filePath)) {
|
|
50
|
-
return this.filePath;
|
|
84
|
+
if (limit && limit > 0) {
|
|
85
|
+
return entries.slice(-limit);
|
|
86
|
+
}
|
|
87
|
+
return entries;
|
|
88
|
+
} catch (err) {
|
|
89
|
+
log.error(`Error reading log file ${resolvedPath}`, { error: err });
|
|
90
|
+
return [];
|
|
51
91
|
}
|
|
52
|
-
|
|
53
|
-
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
function readLogFileTail(options) {
|
|
95
|
+
return __async(this, null, function* () {
|
|
96
|
+
const { name, filePath, projectRoot, lines = 200, limit, level, since } = options;
|
|
97
|
+
const resolvedPath = resolvePath(filePath, projectRoot);
|
|
98
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
99
|
+
log.debug(`Log file does not exist: ${resolvedPath}`);
|
|
100
|
+
return [];
|
|
54
101
|
}
|
|
55
|
-
return path.resolve(process.cwd(), this.filePath);
|
|
56
|
-
}
|
|
57
|
-
readNewLogs(filePath) {
|
|
58
102
|
try {
|
|
59
|
-
const stat = fs.statSync(
|
|
60
|
-
|
|
61
|
-
|
|
103
|
+
const stat = fs.statSync(resolvedPath);
|
|
104
|
+
const fd = fs.openSync(resolvedPath, "r");
|
|
105
|
+
const chunkSize = 16 * 1024;
|
|
106
|
+
let position = stat.size;
|
|
107
|
+
let buffer = Buffer.alloc(0);
|
|
108
|
+
const lineCount = 0;
|
|
109
|
+
while (position > 0 && lineCount <= lines) {
|
|
110
|
+
const readSize = Math.min(chunkSize, position);
|
|
111
|
+
position -= readSize;
|
|
112
|
+
const chunk = Buffer.alloc(readSize);
|
|
113
|
+
fs.readSync(fd, chunk, 0, readSize, position);
|
|
114
|
+
buffer = Buffer.concat([chunk, buffer]);
|
|
115
|
+
const newLineCount = buffer.filter((byte) => byte === 10).length;
|
|
116
|
+
if (newLineCount >= lines) {
|
|
117
|
+
const linesArray = buffer.toString("utf-8").split("\n");
|
|
118
|
+
const excessLines = newLineCount - lines;
|
|
119
|
+
let charsToRemove = 0;
|
|
120
|
+
let count = 0;
|
|
121
|
+
for (let i = 0; i < linesArray.length; i++) {
|
|
122
|
+
count += linesArray[i].length + 1;
|
|
123
|
+
if (count > excessLines) {
|
|
124
|
+
charsToRemove = linesArray.slice(0, i + 1).join("\n").length + 1;
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
buffer = buffer.slice(charsToRemove);
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
62
131
|
}
|
|
63
|
-
const fd = fs.openSync(filePath, "r");
|
|
64
|
-
const buffer = Buffer.alloc(stat.size - this.lastPosition);
|
|
65
|
-
fs.readSync(fd, buffer, 0, buffer.length, this.lastPosition);
|
|
66
132
|
fs.closeSync(fd);
|
|
67
|
-
this.lastPosition = stat.size;
|
|
68
133
|
const content = buffer.toString("utf-8").trim();
|
|
69
|
-
|
|
70
|
-
|
|
134
|
+
const logLines = content.split("\n").filter((line) => line.trim());
|
|
135
|
+
const entries = [];
|
|
136
|
+
const sinceDate = since ? new Date(since) : null;
|
|
137
|
+
for (const line of logLines) {
|
|
138
|
+
const entry = {
|
|
139
|
+
level: detectLogLevel(line),
|
|
140
|
+
message: line,
|
|
141
|
+
timestamp: parseLogTimestamp(line) || (/* @__PURE__ */ new Date()).toISOString(),
|
|
142
|
+
source: `file:${name}`
|
|
143
|
+
};
|
|
144
|
+
if (sinceDate && new Date(entry.timestamp) < sinceDate) {
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
if (level) {
|
|
148
|
+
const levels = Array.isArray(level) ? level : [level];
|
|
149
|
+
if (!levels.includes(entry.level)) {
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
entries.push(entry);
|
|
71
154
|
}
|
|
155
|
+
if (limit && limit > 0) {
|
|
156
|
+
return entries.slice(-limit);
|
|
157
|
+
}
|
|
158
|
+
return entries;
|
|
72
159
|
} catch (err) {
|
|
73
|
-
log.error(`Error reading log file ${
|
|
74
|
-
|
|
75
|
-
}
|
|
76
|
-
processLogContent(content) {
|
|
77
|
-
const lines = content.split("\n");
|
|
78
|
-
for (const line of lines) {
|
|
79
|
-
if (!line.trim()) continue;
|
|
80
|
-
this.addEntry({
|
|
81
|
-
level: this.detectLogLevel(line),
|
|
82
|
-
message: line,
|
|
83
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
84
|
-
source: `file:${this.name}`
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
detectLogLevel(line) {
|
|
89
|
-
const lowerLine = line.toLowerCase();
|
|
90
|
-
if (lowerLine.includes("error") || lowerLine.includes("err") || lowerLine.includes("fatal")) {
|
|
91
|
-
return "error";
|
|
92
|
-
}
|
|
93
|
-
if (lowerLine.includes("warn") || lowerLine.includes("warning")) {
|
|
94
|
-
return "warn";
|
|
160
|
+
log.error(`Error reading log file ${resolvedPath}`, { error: err });
|
|
161
|
+
return [];
|
|
95
162
|
}
|
|
96
|
-
|
|
97
|
-
}
|
|
98
|
-
addEntry(entry) {
|
|
99
|
-
if (!this.enabled) return;
|
|
100
|
-
if (this.buffer.length >= this.maxSize) {
|
|
101
|
-
this.buffer.shift();
|
|
102
|
-
}
|
|
103
|
-
this.buffer.push(entry);
|
|
104
|
-
}
|
|
105
|
-
getLogs(options = {}) {
|
|
106
|
-
let logs = [...this.buffer];
|
|
107
|
-
if (options.level) {
|
|
108
|
-
const levels = Array.isArray(options.level) ? options.level : [options.level];
|
|
109
|
-
logs = logs.filter((log2) => levels.includes(log2.level));
|
|
110
|
-
}
|
|
111
|
-
if (options.since) {
|
|
112
|
-
const sinceDate = new Date(options.since);
|
|
113
|
-
logs = logs.filter((log2) => new Date(log2.timestamp) >= sinceDate);
|
|
114
|
-
}
|
|
115
|
-
if (options.limit && options.limit > 0) {
|
|
116
|
-
logs = logs.slice(-options.limit);
|
|
117
|
-
}
|
|
118
|
-
return logs;
|
|
119
|
-
}
|
|
120
|
-
clear() {
|
|
121
|
-
this.buffer = [];
|
|
122
|
-
}
|
|
123
|
-
size() {
|
|
124
|
-
return this.buffer.length;
|
|
125
|
-
}
|
|
126
|
-
setEnabled(enabled) {
|
|
127
|
-
this.enabled = enabled;
|
|
128
|
-
}
|
|
129
|
-
isEnabled() {
|
|
130
|
-
return this.enabled;
|
|
131
|
-
}
|
|
132
|
-
getName() {
|
|
133
|
-
return this.name;
|
|
134
|
-
}
|
|
135
|
-
getFilePath() {
|
|
136
|
-
return this.filePath;
|
|
137
|
-
}
|
|
163
|
+
});
|
|
138
164
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
__publicField(this, "projectRoot", null);
|
|
143
|
-
}
|
|
144
|
-
setProjectRoot(root) {
|
|
145
|
-
this.projectRoot = root;
|
|
146
|
-
}
|
|
147
|
-
addLogFile(options) {
|
|
148
|
-
var _a;
|
|
149
|
-
if (this.buffers.has(options.name)) {
|
|
150
|
-
log.warn(`Log file "${options.name}" already exists, skipping`);
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
153
|
-
const buffer = new FileLogBuffer(options);
|
|
154
|
-
buffer.start((_a = this.projectRoot) != null ? _a : void 0);
|
|
155
|
-
this.buffers.set(options.name, buffer);
|
|
156
|
-
log.info(`Added log file watcher: ${options.name} -> ${options.filePath}`);
|
|
157
|
-
}
|
|
158
|
-
removeLogFile(name) {
|
|
159
|
-
const buffer = this.buffers.get(name);
|
|
160
|
-
if (buffer) {
|
|
161
|
-
buffer.stop();
|
|
162
|
-
this.buffers.delete(name);
|
|
163
|
-
log.info(`Removed log file watcher: ${name}`);
|
|
164
|
-
}
|
|
165
|
+
function resolvePath(filePath, projectRoot) {
|
|
166
|
+
if (path.isAbsolute(filePath)) {
|
|
167
|
+
return filePath;
|
|
165
168
|
}
|
|
166
|
-
|
|
167
|
-
return
|
|
168
|
-
}
|
|
169
|
-
getAllBuffers() {
|
|
170
|
-
return this.buffers;
|
|
171
|
-
}
|
|
172
|
-
stopAll() {
|
|
173
|
-
for (const [name, buffer] of this.buffers) {
|
|
174
|
-
buffer.stop();
|
|
175
|
-
log.debug(`Stopped log file watcher: ${name}`);
|
|
176
|
-
}
|
|
177
|
-
this.buffers.clear();
|
|
178
|
-
}
|
|
179
|
-
getLogFileNames() {
|
|
180
|
-
return Array.from(this.buffers.keys());
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
let globalWatcher = null;
|
|
184
|
-
function getServiceLogWatcher() {
|
|
185
|
-
if (!globalWatcher) {
|
|
186
|
-
globalWatcher = new ServiceLogWatcher();
|
|
169
|
+
if (projectRoot) {
|
|
170
|
+
return path.resolve(projectRoot, filePath);
|
|
187
171
|
}
|
|
188
|
-
return
|
|
172
|
+
return path.resolve(process.cwd(), filePath);
|
|
189
173
|
}
|
|
190
174
|
export {
|
|
191
|
-
|
|
192
|
-
|
|
175
|
+
readLogFile,
|
|
176
|
+
readLogFileTail
|
|
193
177
|
};
|
package/lib/file-log-watcher.cjs
CHANGED
|
@@ -4,7 +4,6 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
4
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
5
|
var __getProtoOf = Object.getPrototypeOf;
|
|
6
6
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
-
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
8
7
|
var __export = (target, all) => {
|
|
9
8
|
for (var name in all)
|
|
10
9
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
@@ -26,201 +25,187 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
26
25
|
mod
|
|
27
26
|
));
|
|
28
27
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
-
var
|
|
28
|
+
var __async = (__this, __arguments, generator) => {
|
|
29
|
+
return new Promise((resolve, reject) => {
|
|
30
|
+
var fulfilled = (value) => {
|
|
31
|
+
try {
|
|
32
|
+
step(generator.next(value));
|
|
33
|
+
} catch (e) {
|
|
34
|
+
reject(e);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
var rejected = (value) => {
|
|
38
|
+
try {
|
|
39
|
+
step(generator.throw(value));
|
|
40
|
+
} catch (e) {
|
|
41
|
+
reject(e);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
|
|
45
|
+
step((generator = generator.apply(__this, __arguments)).next());
|
|
46
|
+
});
|
|
47
|
+
};
|
|
30
48
|
var file_log_watcher_exports = {};
|
|
31
49
|
__export(file_log_watcher_exports, {
|
|
32
|
-
|
|
33
|
-
|
|
50
|
+
readLogFile: () => readLogFile,
|
|
51
|
+
readLogFileTail: () => readLogFileTail
|
|
34
52
|
});
|
|
35
53
|
module.exports = __toCommonJS(file_log_watcher_exports);
|
|
36
54
|
var import_fs = __toESM(require("fs"));
|
|
37
55
|
var import_path = __toESM(require("path"));
|
|
38
56
|
var import_logger = require("./logger.cjs");
|
|
39
|
-
const log = (0, import_logger.createLogger)("
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
__publicField(this, "watcher", null);
|
|
48
|
-
__publicField(this, "enabled", false);
|
|
49
|
-
var _a;
|
|
50
|
-
this.name = options.name;
|
|
51
|
-
this.filePath = options.filePath;
|
|
52
|
-
this.maxSize = (_a = options.maxBufferSize) != null ? _a : 200;
|
|
53
|
-
this.enabled = true;
|
|
57
|
+
const log = (0, import_logger.createLogger)("FileLogReader");
|
|
58
|
+
function detectLogLevel(line) {
|
|
59
|
+
const lowerLine = line.toLowerCase();
|
|
60
|
+
if (lowerLine.includes("error") || lowerLine.includes("err") || lowerLine.includes("fatal")) {
|
|
61
|
+
return "error";
|
|
62
|
+
}
|
|
63
|
+
if (lowerLine.includes("warn") || lowerLine.includes("warning")) {
|
|
64
|
+
return "warn";
|
|
54
65
|
}
|
|
55
|
-
|
|
56
|
-
|
|
66
|
+
return "info";
|
|
67
|
+
}
|
|
68
|
+
function parseLogTimestamp(line) {
|
|
69
|
+
const timestampPatterns = [
|
|
70
|
+
/(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z?)/,
|
|
71
|
+
/(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})/,
|
|
72
|
+
/(\[([^\]]+)\])/
|
|
73
|
+
];
|
|
74
|
+
for (const pattern of timestampPatterns) {
|
|
75
|
+
const match = line.match(pattern);
|
|
76
|
+
if (match) {
|
|
77
|
+
const timestampStr = match[1];
|
|
78
|
+
const date = new Date(timestampStr);
|
|
79
|
+
if (!isNaN(date.getTime())) {
|
|
80
|
+
return date.toISOString();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
function readLogFile(options) {
|
|
87
|
+
return __async(this, null, function* () {
|
|
88
|
+
const { name, filePath, projectRoot, level, limit, since } = options;
|
|
89
|
+
const resolvedPath = resolvePath(filePath, projectRoot);
|
|
57
90
|
if (!import_fs.default.existsSync(resolvedPath)) {
|
|
58
91
|
log.debug(`Log file does not exist: ${resolvedPath}`);
|
|
59
|
-
return;
|
|
92
|
+
return [];
|
|
60
93
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
94
|
+
try {
|
|
95
|
+
const content = yield import_fs.default.promises.readFile(resolvedPath, "utf-8");
|
|
96
|
+
const lines = content.split("\n").filter((line) => line.trim());
|
|
97
|
+
const entries = [];
|
|
98
|
+
const sinceDate = since ? new Date(since) : null;
|
|
99
|
+
for (const line of lines) {
|
|
100
|
+
const entry = {
|
|
101
|
+
level: detectLogLevel(line),
|
|
102
|
+
message: line,
|
|
103
|
+
timestamp: parseLogTimestamp(line) || (/* @__PURE__ */ new Date()).toISOString(),
|
|
104
|
+
source: `file:${name}`
|
|
105
|
+
};
|
|
106
|
+
if (sinceDate && new Date(entry.timestamp) < sinceDate) {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (level) {
|
|
110
|
+
const levels = Array.isArray(level) ? level : [level];
|
|
111
|
+
if (!levels.includes(entry.level)) {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
entries.push(entry);
|
|
66
116
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
if (this.watcher) {
|
|
75
|
-
this.watcher.close();
|
|
76
|
-
this.watcher = null;
|
|
77
|
-
log.debug(`Stopped watching log file: ${this.filePath}`);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
resolvePath(projectRoot) {
|
|
81
|
-
if (import_path.default.isAbsolute(this.filePath)) {
|
|
82
|
-
return this.filePath;
|
|
117
|
+
if (limit && limit > 0) {
|
|
118
|
+
return entries.slice(-limit);
|
|
119
|
+
}
|
|
120
|
+
return entries;
|
|
121
|
+
} catch (err) {
|
|
122
|
+
log.error(`Error reading log file ${resolvedPath}`, { error: err });
|
|
123
|
+
return [];
|
|
83
124
|
}
|
|
84
|
-
|
|
85
|
-
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
function readLogFileTail(options) {
|
|
128
|
+
return __async(this, null, function* () {
|
|
129
|
+
const { name, filePath, projectRoot, lines = 200, limit, level, since } = options;
|
|
130
|
+
const resolvedPath = resolvePath(filePath, projectRoot);
|
|
131
|
+
if (!import_fs.default.existsSync(resolvedPath)) {
|
|
132
|
+
log.debug(`Log file does not exist: ${resolvedPath}`);
|
|
133
|
+
return [];
|
|
86
134
|
}
|
|
87
|
-
return import_path.default.resolve(process.cwd(), this.filePath);
|
|
88
|
-
}
|
|
89
|
-
readNewLogs(filePath) {
|
|
90
135
|
try {
|
|
91
|
-
const stat = import_fs.default.statSync(
|
|
92
|
-
|
|
93
|
-
|
|
136
|
+
const stat = import_fs.default.statSync(resolvedPath);
|
|
137
|
+
const fd = import_fs.default.openSync(resolvedPath, "r");
|
|
138
|
+
const chunkSize = 16 * 1024;
|
|
139
|
+
let position = stat.size;
|
|
140
|
+
let buffer = Buffer.alloc(0);
|
|
141
|
+
const lineCount = 0;
|
|
142
|
+
while (position > 0 && lineCount <= lines) {
|
|
143
|
+
const readSize = Math.min(chunkSize, position);
|
|
144
|
+
position -= readSize;
|
|
145
|
+
const chunk = Buffer.alloc(readSize);
|
|
146
|
+
import_fs.default.readSync(fd, chunk, 0, readSize, position);
|
|
147
|
+
buffer = Buffer.concat([chunk, buffer]);
|
|
148
|
+
const newLineCount = buffer.filter((byte) => byte === 10).length;
|
|
149
|
+
if (newLineCount >= lines) {
|
|
150
|
+
const linesArray = buffer.toString("utf-8").split("\n");
|
|
151
|
+
const excessLines = newLineCount - lines;
|
|
152
|
+
let charsToRemove = 0;
|
|
153
|
+
let count = 0;
|
|
154
|
+
for (let i = 0; i < linesArray.length; i++) {
|
|
155
|
+
count += linesArray[i].length + 1;
|
|
156
|
+
if (count > excessLines) {
|
|
157
|
+
charsToRemove = linesArray.slice(0, i + 1).join("\n").length + 1;
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
buffer = buffer.slice(charsToRemove);
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
94
164
|
}
|
|
95
|
-
const fd = import_fs.default.openSync(filePath, "r");
|
|
96
|
-
const buffer = Buffer.alloc(stat.size - this.lastPosition);
|
|
97
|
-
import_fs.default.readSync(fd, buffer, 0, buffer.length, this.lastPosition);
|
|
98
165
|
import_fs.default.closeSync(fd);
|
|
99
|
-
this.lastPosition = stat.size;
|
|
100
166
|
const content = buffer.toString("utf-8").trim();
|
|
101
|
-
|
|
102
|
-
|
|
167
|
+
const logLines = content.split("\n").filter((line) => line.trim());
|
|
168
|
+
const entries = [];
|
|
169
|
+
const sinceDate = since ? new Date(since) : null;
|
|
170
|
+
for (const line of logLines) {
|
|
171
|
+
const entry = {
|
|
172
|
+
level: detectLogLevel(line),
|
|
173
|
+
message: line,
|
|
174
|
+
timestamp: parseLogTimestamp(line) || (/* @__PURE__ */ new Date()).toISOString(),
|
|
175
|
+
source: `file:${name}`
|
|
176
|
+
};
|
|
177
|
+
if (sinceDate && new Date(entry.timestamp) < sinceDate) {
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
if (level) {
|
|
181
|
+
const levels = Array.isArray(level) ? level : [level];
|
|
182
|
+
if (!levels.includes(entry.level)) {
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
entries.push(entry);
|
|
103
187
|
}
|
|
188
|
+
if (limit && limit > 0) {
|
|
189
|
+
return entries.slice(-limit);
|
|
190
|
+
}
|
|
191
|
+
return entries;
|
|
104
192
|
} catch (err) {
|
|
105
|
-
log.error(`Error reading log file ${
|
|
106
|
-
|
|
107
|
-
}
|
|
108
|
-
processLogContent(content) {
|
|
109
|
-
const lines = content.split("\n");
|
|
110
|
-
for (const line of lines) {
|
|
111
|
-
if (!line.trim()) continue;
|
|
112
|
-
this.addEntry({
|
|
113
|
-
level: this.detectLogLevel(line),
|
|
114
|
-
message: line,
|
|
115
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
116
|
-
source: `file:${this.name}`
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
detectLogLevel(line) {
|
|
121
|
-
const lowerLine = line.toLowerCase();
|
|
122
|
-
if (lowerLine.includes("error") || lowerLine.includes("err") || lowerLine.includes("fatal")) {
|
|
123
|
-
return "error";
|
|
124
|
-
}
|
|
125
|
-
if (lowerLine.includes("warn") || lowerLine.includes("warning")) {
|
|
126
|
-
return "warn";
|
|
193
|
+
log.error(`Error reading log file ${resolvedPath}`, { error: err });
|
|
194
|
+
return [];
|
|
127
195
|
}
|
|
128
|
-
|
|
129
|
-
}
|
|
130
|
-
addEntry(entry) {
|
|
131
|
-
if (!this.enabled) return;
|
|
132
|
-
if (this.buffer.length >= this.maxSize) {
|
|
133
|
-
this.buffer.shift();
|
|
134
|
-
}
|
|
135
|
-
this.buffer.push(entry);
|
|
136
|
-
}
|
|
137
|
-
getLogs(options = {}) {
|
|
138
|
-
let logs = [...this.buffer];
|
|
139
|
-
if (options.level) {
|
|
140
|
-
const levels = Array.isArray(options.level) ? options.level : [options.level];
|
|
141
|
-
logs = logs.filter((log2) => levels.includes(log2.level));
|
|
142
|
-
}
|
|
143
|
-
if (options.since) {
|
|
144
|
-
const sinceDate = new Date(options.since);
|
|
145
|
-
logs = logs.filter((log2) => new Date(log2.timestamp) >= sinceDate);
|
|
146
|
-
}
|
|
147
|
-
if (options.limit && options.limit > 0) {
|
|
148
|
-
logs = logs.slice(-options.limit);
|
|
149
|
-
}
|
|
150
|
-
return logs;
|
|
151
|
-
}
|
|
152
|
-
clear() {
|
|
153
|
-
this.buffer = [];
|
|
154
|
-
}
|
|
155
|
-
size() {
|
|
156
|
-
return this.buffer.length;
|
|
157
|
-
}
|
|
158
|
-
setEnabled(enabled) {
|
|
159
|
-
this.enabled = enabled;
|
|
160
|
-
}
|
|
161
|
-
isEnabled() {
|
|
162
|
-
return this.enabled;
|
|
163
|
-
}
|
|
164
|
-
getName() {
|
|
165
|
-
return this.name;
|
|
166
|
-
}
|
|
167
|
-
getFilePath() {
|
|
168
|
-
return this.filePath;
|
|
169
|
-
}
|
|
196
|
+
});
|
|
170
197
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
__publicField(this, "projectRoot", null);
|
|
175
|
-
}
|
|
176
|
-
setProjectRoot(root) {
|
|
177
|
-
this.projectRoot = root;
|
|
178
|
-
}
|
|
179
|
-
addLogFile(options) {
|
|
180
|
-
var _a;
|
|
181
|
-
if (this.buffers.has(options.name)) {
|
|
182
|
-
log.warn(`Log file "${options.name}" already exists, skipping`);
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
185
|
-
const buffer = new FileLogBuffer(options);
|
|
186
|
-
buffer.start((_a = this.projectRoot) != null ? _a : void 0);
|
|
187
|
-
this.buffers.set(options.name, buffer);
|
|
188
|
-
log.info(`Added log file watcher: ${options.name} -> ${options.filePath}`);
|
|
189
|
-
}
|
|
190
|
-
removeLogFile(name) {
|
|
191
|
-
const buffer = this.buffers.get(name);
|
|
192
|
-
if (buffer) {
|
|
193
|
-
buffer.stop();
|
|
194
|
-
this.buffers.delete(name);
|
|
195
|
-
log.info(`Removed log file watcher: ${name}`);
|
|
196
|
-
}
|
|
198
|
+
function resolvePath(filePath, projectRoot) {
|
|
199
|
+
if (import_path.default.isAbsolute(filePath)) {
|
|
200
|
+
return filePath;
|
|
197
201
|
}
|
|
198
|
-
|
|
199
|
-
return
|
|
200
|
-
}
|
|
201
|
-
getAllBuffers() {
|
|
202
|
-
return this.buffers;
|
|
203
|
-
}
|
|
204
|
-
stopAll() {
|
|
205
|
-
for (const [name, buffer] of this.buffers) {
|
|
206
|
-
buffer.stop();
|
|
207
|
-
log.debug(`Stopped log file watcher: ${name}`);
|
|
208
|
-
}
|
|
209
|
-
this.buffers.clear();
|
|
210
|
-
}
|
|
211
|
-
getLogFileNames() {
|
|
212
|
-
return Array.from(this.buffers.keys());
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
let globalWatcher = null;
|
|
216
|
-
function getServiceLogWatcher() {
|
|
217
|
-
if (!globalWatcher) {
|
|
218
|
-
globalWatcher = new ServiceLogWatcher();
|
|
202
|
+
if (projectRoot) {
|
|
203
|
+
return import_path.default.resolve(projectRoot, filePath);
|
|
219
204
|
}
|
|
220
|
-
return
|
|
205
|
+
return import_path.default.resolve(process.cwd(), filePath);
|
|
221
206
|
}
|
|
222
207
|
// Annotate the CommonJS export names for ESM import in node:
|
|
223
208
|
0 && (module.exports = {
|
|
224
|
-
|
|
225
|
-
|
|
209
|
+
readLogFile,
|
|
210
|
+
readLogFileTail
|
|
226
211
|
});
|
|
@@ -4,50 +4,20 @@ export interface FileLogEntry {
|
|
|
4
4
|
timestamp: string;
|
|
5
5
|
source: string;
|
|
6
6
|
}
|
|
7
|
-
export interface
|
|
7
|
+
export interface LogFileOptions {
|
|
8
8
|
name: string;
|
|
9
9
|
filePath: string;
|
|
10
|
-
maxBufferSize?: number;
|
|
11
|
-
watchExisting?: boolean;
|
|
12
10
|
}
|
|
13
|
-
declare
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
private processLogContent;
|
|
27
|
-
private detectLogLevel;
|
|
28
|
-
addEntry(entry: FileLogEntry): void;
|
|
29
|
-
getLogs(options?: {
|
|
30
|
-
level?: FileLogEntry["level"] | FileLogEntry["level"][];
|
|
31
|
-
limit?: number;
|
|
32
|
-
since?: string;
|
|
33
|
-
}): FileLogEntry[];
|
|
34
|
-
clear(): void;
|
|
35
|
-
size(): number;
|
|
36
|
-
setEnabled(enabled: boolean): void;
|
|
37
|
-
isEnabled(): boolean;
|
|
38
|
-
getName(): string;
|
|
39
|
-
getFilePath(): string;
|
|
40
|
-
}
|
|
41
|
-
export declare class ServiceLogWatcher {
|
|
42
|
-
private buffers;
|
|
43
|
-
private projectRoot;
|
|
44
|
-
setProjectRoot(root: string): void;
|
|
45
|
-
addLogFile(options: FileLogBufferOptions): void;
|
|
46
|
-
removeLogFile(name: string): void;
|
|
47
|
-
getBuffer(name: string): FileLogBuffer | undefined;
|
|
48
|
-
getAllBuffers(): Map<string, FileLogBuffer>;
|
|
49
|
-
stopAll(): void;
|
|
50
|
-
getLogFileNames(): string[];
|
|
51
|
-
}
|
|
52
|
-
export declare function getServiceLogWatcher(): ServiceLogWatcher;
|
|
53
|
-
export {};
|
|
11
|
+
export declare function readLogFile(options: LogFileOptions & {
|
|
12
|
+
projectRoot?: string;
|
|
13
|
+
level?: ("info" | "warn" | "error") | ("info" | "warn" | "error")[];
|
|
14
|
+
limit?: number;
|
|
15
|
+
since?: string;
|
|
16
|
+
}): Promise<FileLogEntry[]>;
|
|
17
|
+
export declare function readLogFileTail(options: LogFileOptions & {
|
|
18
|
+
projectRoot?: string;
|
|
19
|
+
lines?: number;
|
|
20
|
+
limit?: number;
|
|
21
|
+
level?: ("info" | "warn" | "error") | ("info" | "warn" | "error")[];
|
|
22
|
+
since?: string;
|
|
23
|
+
}): Promise<FileLogEntry[]>;
|