@dev-anywhere/relay 0.9.1 → 0.9.2
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/assets/web/assets/{fvad-DreiJBJr.js → fvad-oSUTPVOe.js} +1 -1
- package/assets/web/assets/index-BVDmlZok.css +41 -0
- package/assets/web/assets/{index-Buv7a7PI.js → index-BeQpXrcf.js} +184 -184
- package/assets/web/assets/{jmuxer.min-BMedIzGm.js → jmuxer.min-BcaOPF7j.js} +1 -1
- package/assets/web/index.html +2 -2
- package/assets/web/sw.js +1 -1
- package/dist/{chunk-JLLMBFAB.js → chunk-RGMU7VLD.js} +214 -27
- package/dist/chunk-RGMU7VLD.js.map +1 -0
- package/dist/client-registration-admission.d.ts +14 -0
- package/dist/client-registration-admission.d.ts.map +1 -0
- package/dist/handlers/client.d.ts +1 -1
- package/dist/handlers/client.d.ts.map +1 -1
- package/dist/handlers/proxy.d.ts +1 -1
- package/dist/handlers/proxy.d.ts.map +1 -1
- package/dist/health.d.ts.map +1 -1
- package/dist/index.js +551 -51
- package/dist/index.js.map +1 -1
- package/dist/registry.d.ts.map +1 -1
- package/dist/server.d.ts +2 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +3 -3
- package/assets/web/assets/index-CTJCtPHc.css +0 -41
- package/dist/chunk-JLLMBFAB.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -3,15 +3,24 @@ import {
|
|
|
3
3
|
RELAY_VERSION,
|
|
4
4
|
createRelayServer,
|
|
5
5
|
parseRelayChaosFromEnv
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-RGMU7VLD.js";
|
|
7
7
|
|
|
8
8
|
// ../../packages/shared/dist/logger.js
|
|
9
|
-
import { lstatSync, mkdirSync, readdirSync, renameSync, statSync, symlinkSync, unlinkSync } from "fs";
|
|
9
|
+
import { lstatSync, linkSync, mkdirSync, readFileSync, readdirSync, renameSync, statSync, symlinkSync, unlinkSync, writeFileSync } from "fs";
|
|
10
|
+
import { randomUUID } from "crypto";
|
|
10
11
|
import { homedir } from "os";
|
|
11
12
|
import { basename, join } from "path";
|
|
12
13
|
import pino from "pino";
|
|
13
14
|
var DEFAULT_LOG_DIR = `${homedir()}/.dev-anywhere/logs`;
|
|
14
15
|
var DEFAULT_LOG_RETENTION = 50;
|
|
16
|
+
var DEFAULT_LOG_RETENTION_BYTES = 256 * 1024 * 1024;
|
|
17
|
+
var DEFAULT_MAX_FILE_BYTES = 16 * 1024 * 1024;
|
|
18
|
+
var DEFAULT_MAX_FILES_PER_RUN = 2;
|
|
19
|
+
var DEFAULT_MAX_RECORD_BYTES = 256 * 1024;
|
|
20
|
+
var DEFAULT_MAX_STDOUT_BUFFER_BYTES = 1024 * 1024;
|
|
21
|
+
var LOG_IO_OPERATION_TIMEOUT_MS = 5e3;
|
|
22
|
+
var LOG_RUN_LEASE_SUFFIX = ".active";
|
|
23
|
+
var LOG_RUN_LEASE_CANDIDATE_MARKER = `${LOG_RUN_LEASE_SUFFIX}.candidate-`;
|
|
15
24
|
var PROCESS_LOG_RUN_ID = sanitizeRunId(`${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}-${process.pid}`);
|
|
16
25
|
function sanitizeRunId(runId) {
|
|
17
26
|
return runId.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
@@ -40,8 +49,137 @@ function resolveRetention(retention) {
|
|
|
40
49
|
return DEFAULT_LOG_RETENTION;
|
|
41
50
|
return Number.isFinite(retention) && retention >= 0 ? Math.floor(retention) : DEFAULT_LOG_RETENTION;
|
|
42
51
|
}
|
|
43
|
-
function
|
|
52
|
+
function resolvePositiveInteger(value, fallback) {
|
|
53
|
+
return value !== void 0 && Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;
|
|
54
|
+
}
|
|
55
|
+
function leasePathForLog(filePath) {
|
|
56
|
+
return `${filePath}${LOG_RUN_LEASE_SUFFIX}`;
|
|
57
|
+
}
|
|
58
|
+
function createLogRunLease(filePath, runId) {
|
|
59
|
+
const lease = {
|
|
60
|
+
version: 1,
|
|
61
|
+
pid: process.pid,
|
|
62
|
+
runId,
|
|
63
|
+
fileName: basename(filePath)
|
|
64
|
+
};
|
|
65
|
+
const leasePath = leasePathForLog(filePath);
|
|
66
|
+
const candidatePath = `${leasePath}.candidate-${process.pid}-${randomUUID()}`;
|
|
67
|
+
let candidateCreated = false;
|
|
68
|
+
try {
|
|
69
|
+
writeFileSync(candidatePath, `${JSON.stringify(lease)}
|
|
70
|
+
`, { flag: "wx" });
|
|
71
|
+
candidateCreated = true;
|
|
72
|
+
linkSync(candidatePath, leasePath);
|
|
73
|
+
} finally {
|
|
74
|
+
if (candidateCreated) {
|
|
75
|
+
try {
|
|
76
|
+
unlinkSync(candidatePath);
|
|
77
|
+
} catch {
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function isProcessAlive(pid) {
|
|
83
|
+
try {
|
|
84
|
+
process.kill(pid, 0);
|
|
85
|
+
return true;
|
|
86
|
+
} catch (error) {
|
|
87
|
+
return error.code !== "ESRCH";
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function readLogRunLease(leasePath, expectedFileName) {
|
|
91
|
+
let raw;
|
|
92
|
+
try {
|
|
93
|
+
raw = readFileSync(leasePath, "utf-8");
|
|
94
|
+
} catch (error) {
|
|
95
|
+
return error.code === "ENOENT" ? { state: "missing" } : { state: "invalid" };
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
const parsed = JSON.parse(raw);
|
|
99
|
+
const pid = parsed.pid;
|
|
100
|
+
if (parsed.version !== 1 || !Number.isSafeInteger(pid) || typeof pid !== "number" || pid <= 0 || typeof parsed.runId !== "string" || parsed.runId.length === 0 || parsed.fileName !== expectedFileName) {
|
|
101
|
+
throw new Error("Invalid log run lease");
|
|
102
|
+
}
|
|
103
|
+
return { state: "valid", lease: parsed };
|
|
104
|
+
} catch {
|
|
105
|
+
return { state: "invalid" };
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function hasLiveLogRunLease(filePath) {
|
|
109
|
+
const leasePath = leasePathForLog(filePath);
|
|
110
|
+
const result = readLogRunLease(leasePath, basename(filePath));
|
|
111
|
+
if (result.state === "missing")
|
|
112
|
+
return false;
|
|
113
|
+
if (result.state === "invalid")
|
|
114
|
+
return true;
|
|
115
|
+
if (isProcessAlive(result.lease.pid))
|
|
116
|
+
return true;
|
|
117
|
+
try {
|
|
118
|
+
unlinkSync(leasePath);
|
|
119
|
+
} catch {
|
|
120
|
+
}
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
function removeOwnedLogRunLease(filePath, runId) {
|
|
124
|
+
const leasePath = leasePathForLog(filePath);
|
|
125
|
+
const result = readLogRunLease(leasePath, basename(filePath));
|
|
126
|
+
if (result.state !== "valid" || result.lease.pid !== process.pid || result.lease.runId !== runId) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
try {
|
|
130
|
+
unlinkSync(leasePath);
|
|
131
|
+
} catch {
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
function parseLeaseEntry(entry, name) {
|
|
135
|
+
const prefix = `${name}-`;
|
|
136
|
+
if (!entry.startsWith(prefix))
|
|
137
|
+
return null;
|
|
138
|
+
if (entry.endsWith(LOG_RUN_LEASE_SUFFIX)) {
|
|
139
|
+
const fileName2 = entry.slice(0, -LOG_RUN_LEASE_SUFFIX.length);
|
|
140
|
+
return fileName2.endsWith(".log") ? { fileName: fileName2, candidate: false } : null;
|
|
141
|
+
}
|
|
142
|
+
const markerIndex = entry.lastIndexOf(LOG_RUN_LEASE_CANDIDATE_MARKER);
|
|
143
|
+
if (markerIndex < 0 || markerIndex + LOG_RUN_LEASE_CANDIDATE_MARKER.length >= entry.length) {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
const fileName = entry.slice(0, markerIndex);
|
|
147
|
+
return fileName.endsWith(".log") ? { fileName, candidate: true } : null;
|
|
148
|
+
}
|
|
149
|
+
function pruneOrphanedLogRunLeases(logDir, name) {
|
|
150
|
+
for (const entry of readdirSync(logDir)) {
|
|
151
|
+
const parsedEntry = parseLeaseEntry(entry, name);
|
|
152
|
+
if (!parsedEntry)
|
|
153
|
+
continue;
|
|
154
|
+
const leasePath = join(logDir, entry);
|
|
155
|
+
const result = readLogRunLease(leasePath, parsedEntry.fileName);
|
|
156
|
+
if (result.state !== "valid" || isProcessAlive(result.lease.pid))
|
|
157
|
+
continue;
|
|
158
|
+
let correspondingLogExists = false;
|
|
159
|
+
try {
|
|
160
|
+
statSync(join(logDir, parsedEntry.fileName));
|
|
161
|
+
correspondingLogExists = true;
|
|
162
|
+
} catch {
|
|
163
|
+
}
|
|
164
|
+
if (!parsedEntry.candidate && correspondingLogExists)
|
|
165
|
+
continue;
|
|
166
|
+
try {
|
|
167
|
+
unlinkSync(leasePath);
|
|
168
|
+
} catch {
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
function removeLogAndLease(filePath) {
|
|
173
|
+
unlinkSync(filePath);
|
|
174
|
+
try {
|
|
175
|
+
unlinkSync(leasePathForLog(filePath));
|
|
176
|
+
} catch {
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
function pruneOldLogs(logDir, name, currentFilePath, retention, retentionBytes) {
|
|
44
180
|
const keep = resolveRetention(retention);
|
|
181
|
+
const byteBudget = resolvePositiveInteger(retentionBytes, DEFAULT_LOG_RETENTION_BYTES);
|
|
182
|
+
pruneOrphanedLogRunLeases(logDir, name);
|
|
45
183
|
if (keep === 0)
|
|
46
184
|
return;
|
|
47
185
|
const currentFileName = basename(currentFilePath);
|
|
@@ -49,39 +187,433 @@ function pruneOldLogs(logDir, name, currentFilePath, retention) {
|
|
|
49
187
|
const candidates = readdirSync(logDir).filter((entry) => entry.startsWith(prefix) && entry.endsWith(".log") && entry !== currentFileName).map((entry) => {
|
|
50
188
|
const path = join(logDir, entry);
|
|
51
189
|
try {
|
|
52
|
-
|
|
190
|
+
const stat = statSync(path);
|
|
191
|
+
return { path, mtimeMs: stat.mtimeMs, size: stat.size };
|
|
53
192
|
} catch {
|
|
54
193
|
return null;
|
|
55
194
|
}
|
|
56
195
|
}).filter((entry) => entry !== null).sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
57
|
-
|
|
196
|
+
const retained = [];
|
|
197
|
+
let retainedBytes = 0;
|
|
198
|
+
for (const candidate of candidates) {
|
|
199
|
+
if (hasLiveLogRunLease(candidate.path)) {
|
|
200
|
+
retained.push(candidate);
|
|
201
|
+
retainedBytes += candidate.size;
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
const withinCount = keep === 0 || retained.length < Math.max(0, keep - 1);
|
|
205
|
+
const withinBytes = retainedBytes + candidate.size <= byteBudget;
|
|
206
|
+
if (withinCount && withinBytes) {
|
|
207
|
+
retained.push(candidate);
|
|
208
|
+
retainedBytes += candidate.size;
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
58
211
|
try {
|
|
59
|
-
|
|
212
|
+
removeLogAndLease(candidate.path);
|
|
60
213
|
} catch {
|
|
61
214
|
}
|
|
62
215
|
}
|
|
63
216
|
}
|
|
217
|
+
function waitForDestinationEvent(destination, event, deadline) {
|
|
218
|
+
if (!destination.on || !destination.off)
|
|
219
|
+
return Promise.resolve(false);
|
|
220
|
+
const addListener = destination.on.bind(destination);
|
|
221
|
+
const removeListener = destination.off.bind(destination);
|
|
222
|
+
const remaining = deadline === void 0 ? void 0 : deadline - Date.now();
|
|
223
|
+
if (remaining !== void 0 && remaining <= 0)
|
|
224
|
+
return Promise.resolve(false);
|
|
225
|
+
return new Promise((resolve) => {
|
|
226
|
+
let settled = false;
|
|
227
|
+
let timer;
|
|
228
|
+
const cleanup = () => {
|
|
229
|
+
if (timer)
|
|
230
|
+
clearTimeout(timer);
|
|
231
|
+
removeListener(event, onEvent);
|
|
232
|
+
removeListener("error", onError);
|
|
233
|
+
};
|
|
234
|
+
const settle = (result) => {
|
|
235
|
+
if (settled)
|
|
236
|
+
return;
|
|
237
|
+
settled = true;
|
|
238
|
+
cleanup();
|
|
239
|
+
resolve(result);
|
|
240
|
+
};
|
|
241
|
+
const onEvent = () => settle(true);
|
|
242
|
+
const onError = () => settle(false);
|
|
243
|
+
addListener(event, onEvent);
|
|
244
|
+
addListener("error", onError);
|
|
245
|
+
if (remaining !== void 0) {
|
|
246
|
+
timer = setTimeout(() => settle(false), remaining);
|
|
247
|
+
timer.unref?.();
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
var BoundedWritableDestination = class {
|
|
252
|
+
destination;
|
|
253
|
+
maxPendingBytes;
|
|
254
|
+
droppedRecords = 0;
|
|
255
|
+
saturated = false;
|
|
256
|
+
constructor(destination, maxPendingBytes) {
|
|
257
|
+
this.destination = destination;
|
|
258
|
+
this.maxPendingBytes = maxPendingBytes;
|
|
259
|
+
}
|
|
260
|
+
write(serialized) {
|
|
261
|
+
if (this.saturated)
|
|
262
|
+
return;
|
|
263
|
+
const bytes = Buffer.byteLength(serialized);
|
|
264
|
+
const pendingBytes = this.destination.writableLength;
|
|
265
|
+
if (bytes > this.maxPendingBytes || typeof pendingBytes !== "number" || !Number.isFinite(pendingBytes)) {
|
|
266
|
+
this.droppedRecords += 1;
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
const droppedDiagnostic = this.droppedRecords > 0 ? `${JSON.stringify({
|
|
270
|
+
level: 40,
|
|
271
|
+
time: Date.now(),
|
|
272
|
+
msg: "Log records were dropped while stdout was backpressured",
|
|
273
|
+
dropped: this.droppedRecords
|
|
274
|
+
})}
|
|
275
|
+
` : null;
|
|
276
|
+
const diagnosticBytes = droppedDiagnostic ? Buffer.byteLength(droppedDiagnostic) : 0;
|
|
277
|
+
if (pendingBytes + diagnosticBytes + bytes > this.maxPendingBytes) {
|
|
278
|
+
this.droppedRecords += 1;
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
try {
|
|
282
|
+
if (droppedDiagnostic)
|
|
283
|
+
this.destination.write(droppedDiagnostic);
|
|
284
|
+
this.destination.write(serialized);
|
|
285
|
+
this.droppedRecords = 0;
|
|
286
|
+
} catch {
|
|
287
|
+
this.saturated = true;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
var BoundedLogDestination = class {
|
|
292
|
+
destination;
|
|
293
|
+
filePath;
|
|
294
|
+
runId;
|
|
295
|
+
logDir;
|
|
296
|
+
name;
|
|
297
|
+
retention;
|
|
298
|
+
retentionBytes;
|
|
299
|
+
maxFileBytes;
|
|
300
|
+
maxFilesPerRun;
|
|
301
|
+
maxRecordBytes;
|
|
302
|
+
currentBytes = 0;
|
|
303
|
+
rotating = false;
|
|
304
|
+
saturated = false;
|
|
305
|
+
droppedDuringRotation = 0;
|
|
306
|
+
pendingRecord = null;
|
|
307
|
+
rotationPromise = null;
|
|
308
|
+
rotationWaiters = /* @__PURE__ */ new Set();
|
|
309
|
+
constructor(destination, filePath, runId, logDir, name, retention, retentionBytes, maxFileBytes, maxFilesPerRun, maxRecordBytes, initialBytes) {
|
|
310
|
+
this.destination = destination;
|
|
311
|
+
this.filePath = filePath;
|
|
312
|
+
this.runId = runId;
|
|
313
|
+
this.logDir = logDir;
|
|
314
|
+
this.name = name;
|
|
315
|
+
this.retention = retention;
|
|
316
|
+
this.retentionBytes = retentionBytes;
|
|
317
|
+
this.maxFileBytes = maxFileBytes;
|
|
318
|
+
this.maxFilesPerRun = maxFilesPerRun;
|
|
319
|
+
this.maxRecordBytes = maxRecordBytes;
|
|
320
|
+
this.currentBytes = initialBytes;
|
|
321
|
+
this.destination.on?.("error", () => {
|
|
322
|
+
this.rotating = false;
|
|
323
|
+
this.saturated = true;
|
|
324
|
+
if (this.destination.fd == null || this.destination.fd < 0) {
|
|
325
|
+
removeOwnedLogRunLease(this.filePath, this.runId);
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
get fd() {
|
|
330
|
+
return this.destination.fd;
|
|
331
|
+
}
|
|
332
|
+
get _writing() {
|
|
333
|
+
return this.destination._writing;
|
|
334
|
+
}
|
|
335
|
+
once(event, cb) {
|
|
336
|
+
this.destination.once?.(event, cb);
|
|
337
|
+
}
|
|
338
|
+
on(event, cb) {
|
|
339
|
+
this.destination.on?.(event, cb);
|
|
340
|
+
}
|
|
341
|
+
off(event, cb) {
|
|
342
|
+
this.destination.off?.(event, cb);
|
|
343
|
+
}
|
|
344
|
+
listenerCount(event) {
|
|
345
|
+
return this.destination.listenerCount?.(event) ?? 0;
|
|
346
|
+
}
|
|
347
|
+
write(serialized) {
|
|
348
|
+
if (this.saturated)
|
|
349
|
+
return;
|
|
350
|
+
const record = this.boundRecord(serialized);
|
|
351
|
+
if (!record)
|
|
352
|
+
return;
|
|
353
|
+
const bytes = Buffer.byteLength(record);
|
|
354
|
+
if (this.rotating) {
|
|
355
|
+
this.droppedDuringRotation += 1;
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
if (this.currentBytes + bytes > this.maxFileBytes) {
|
|
359
|
+
this.startRotation(record);
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
this.writeActiveRecord(record);
|
|
363
|
+
}
|
|
364
|
+
flushSync() {
|
|
365
|
+
this.destination.flushSync?.();
|
|
366
|
+
}
|
|
367
|
+
async waitForRotation(deadline) {
|
|
368
|
+
while (this.rotationPromise) {
|
|
369
|
+
const remaining = deadline - Date.now();
|
|
370
|
+
if (remaining <= 0)
|
|
371
|
+
return false;
|
|
372
|
+
const settled = await new Promise((resolve) => {
|
|
373
|
+
let finished = false;
|
|
374
|
+
const waiter = () => finish(true);
|
|
375
|
+
const timer = setTimeout(() => finish(false), remaining);
|
|
376
|
+
timer.unref?.();
|
|
377
|
+
const finish = (result) => {
|
|
378
|
+
if (finished)
|
|
379
|
+
return;
|
|
380
|
+
finished = true;
|
|
381
|
+
clearTimeout(timer);
|
|
382
|
+
this.rotationWaiters.delete(waiter);
|
|
383
|
+
resolve(result);
|
|
384
|
+
};
|
|
385
|
+
this.rotationWaiters.add(waiter);
|
|
386
|
+
if (!this.rotationPromise)
|
|
387
|
+
finish(true);
|
|
388
|
+
});
|
|
389
|
+
if (!settled)
|
|
390
|
+
return false;
|
|
391
|
+
}
|
|
392
|
+
return true;
|
|
393
|
+
}
|
|
394
|
+
boundRecord(serialized) {
|
|
395
|
+
const bytes = Buffer.byteLength(serialized);
|
|
396
|
+
if (bytes <= this.maxRecordBytes && bytes <= this.maxFileBytes)
|
|
397
|
+
return serialized;
|
|
398
|
+
return this.boundDiagnostic({
|
|
399
|
+
level: 40,
|
|
400
|
+
time: Date.now(),
|
|
401
|
+
msg: "Log entry omitted because it exceeded the configured byte limit",
|
|
402
|
+
originalBytes: bytes
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
boundDiagnostic(value) {
|
|
406
|
+
const serialized = `${JSON.stringify(value)}
|
|
407
|
+
`;
|
|
408
|
+
const bytes = Buffer.byteLength(serialized);
|
|
409
|
+
return bytes <= this.maxRecordBytes && bytes <= this.maxFileBytes ? serialized : null;
|
|
410
|
+
}
|
|
411
|
+
segmentPath(index) {
|
|
412
|
+
return this.filePath.replace(/\.log$/, `.${index}.log`);
|
|
413
|
+
}
|
|
414
|
+
writeActiveRecord(record) {
|
|
415
|
+
const bytes = Buffer.byteLength(record);
|
|
416
|
+
if (!this.destination.write || this.currentBytes + bytes > this.maxFileBytes)
|
|
417
|
+
return false;
|
|
418
|
+
this.currentBytes += bytes;
|
|
419
|
+
try {
|
|
420
|
+
this.destination.write(record);
|
|
421
|
+
return !this.saturated;
|
|
422
|
+
} catch {
|
|
423
|
+
this.saturated = true;
|
|
424
|
+
return false;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
startRotation(pendingRecord) {
|
|
428
|
+
if (this.rotating || this.saturated)
|
|
429
|
+
return;
|
|
430
|
+
this.rotating = true;
|
|
431
|
+
this.pendingRecord = pendingRecord;
|
|
432
|
+
const tracked = Promise.resolve().then(() => this.performRotation()).catch(() => {
|
|
433
|
+
this.saturated = true;
|
|
434
|
+
this.rotating = false;
|
|
435
|
+
this.pendingRecord = null;
|
|
436
|
+
this.droppedDuringRotation = 0;
|
|
437
|
+
}).finally(() => {
|
|
438
|
+
if (this.rotationPromise === tracked)
|
|
439
|
+
this.rotationPromise = null;
|
|
440
|
+
for (const waiter of this.rotationWaiters)
|
|
441
|
+
waiter();
|
|
442
|
+
this.rotationWaiters.clear();
|
|
443
|
+
});
|
|
444
|
+
this.rotationPromise = tracked;
|
|
445
|
+
}
|
|
446
|
+
async performRotation() {
|
|
447
|
+
if (!this.destination.reopen)
|
|
448
|
+
throw new Error("Log destination cannot be reopened");
|
|
449
|
+
const deadline = Date.now() + LOG_IO_OPERATION_TIMEOUT_MS;
|
|
450
|
+
if (this.destination.fd == null || this.destination.fd < 0) {
|
|
451
|
+
if (!await waitForDestinationEvent(this.destination, "ready", deadline)) {
|
|
452
|
+
throw new Error("Log destination did not become ready");
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
if (this.destination._writing) {
|
|
456
|
+
if (!await waitForDestinationEvent(this.destination, "drain", deadline)) {
|
|
457
|
+
throw new Error("Log destination did not drain");
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
this.rotateFiles();
|
|
461
|
+
this.destination.reopen(this.filePath);
|
|
462
|
+
if (!await waitForDestinationEvent(this.destination, "ready", deadline)) {
|
|
463
|
+
throw new Error("Rotated log destination did not become ready");
|
|
464
|
+
}
|
|
465
|
+
pruneOldLogs(this.logDir, this.name, this.filePath, this.retention, this.retentionBytes);
|
|
466
|
+
this.currentBytes = 0;
|
|
467
|
+
const pendingRecord = this.pendingRecord;
|
|
468
|
+
this.pendingRecord = null;
|
|
469
|
+
const dropped = this.droppedDuringRotation;
|
|
470
|
+
this.droppedDuringRotation = 0;
|
|
471
|
+
this.rotating = false;
|
|
472
|
+
if (pendingRecord && !this.writeActiveRecord(pendingRecord))
|
|
473
|
+
return;
|
|
474
|
+
if (dropped === 0 || this.saturated)
|
|
475
|
+
return;
|
|
476
|
+
const diagnostic = this.boundDiagnostic({
|
|
477
|
+
level: 40,
|
|
478
|
+
time: Date.now(),
|
|
479
|
+
msg: "Log records were dropped while rotating the active file",
|
|
480
|
+
dropped
|
|
481
|
+
});
|
|
482
|
+
if (diagnostic && this.currentBytes + Buffer.byteLength(diagnostic) <= this.maxFileBytes) {
|
|
483
|
+
this.writeActiveRecord(diagnostic);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
rotateFiles() {
|
|
487
|
+
if (this.maxFilesPerRun > 1) {
|
|
488
|
+
try {
|
|
489
|
+
unlinkSync(this.segmentPath(this.maxFilesPerRun - 1));
|
|
490
|
+
} catch (error) {
|
|
491
|
+
if (error.code !== "ENOENT")
|
|
492
|
+
throw error;
|
|
493
|
+
}
|
|
494
|
+
for (let index = this.maxFilesPerRun - 2; index >= 1; index -= 1) {
|
|
495
|
+
try {
|
|
496
|
+
renameSync(this.segmentPath(index), this.segmentPath(index + 1));
|
|
497
|
+
} catch (error) {
|
|
498
|
+
if (error.code !== "ENOENT")
|
|
499
|
+
throw error;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
renameSync(this.filePath, this.segmentPath(1));
|
|
503
|
+
} else {
|
|
504
|
+
unlinkSync(this.filePath);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
};
|
|
64
508
|
var loggerMetaMap = /* @__PURE__ */ new WeakMap();
|
|
509
|
+
async function flushDestination(destination, deadline) {
|
|
510
|
+
while (true) {
|
|
511
|
+
if (!await destination.waitForRotation(deadline))
|
|
512
|
+
return;
|
|
513
|
+
if (destination.fd == null || destination.fd < 0) {
|
|
514
|
+
if (!await waitForDestinationEvent(destination, "ready", deadline))
|
|
515
|
+
return;
|
|
516
|
+
continue;
|
|
517
|
+
}
|
|
518
|
+
if (destination._writing) {
|
|
519
|
+
if (!await waitForDestinationEvent(destination, "drain", deadline))
|
|
520
|
+
return;
|
|
521
|
+
continue;
|
|
522
|
+
}
|
|
523
|
+
try {
|
|
524
|
+
destination.flushSync();
|
|
525
|
+
} catch {
|
|
526
|
+
}
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
function resolveFlushTimeout(timeoutMs) {
|
|
531
|
+
return Number.isFinite(timeoutMs) && timeoutMs >= 0 ? Math.floor(timeoutMs) : 200;
|
|
532
|
+
}
|
|
533
|
+
function startFlushOperation(meta, destination) {
|
|
534
|
+
const operation = {
|
|
535
|
+
promise: flushDestination(destination, Date.now() + LOG_IO_OPERATION_TIMEOUT_MS),
|
|
536
|
+
settled: false,
|
|
537
|
+
waiters: /* @__PURE__ */ new Set()
|
|
538
|
+
};
|
|
539
|
+
meta.flushOperation = operation;
|
|
540
|
+
const finish = () => {
|
|
541
|
+
if (operation.settled)
|
|
542
|
+
return;
|
|
543
|
+
operation.settled = true;
|
|
544
|
+
if (meta.flushOperation === operation)
|
|
545
|
+
meta.flushOperation = null;
|
|
546
|
+
for (const waiter of operation.waiters)
|
|
547
|
+
waiter();
|
|
548
|
+
operation.waiters.clear();
|
|
549
|
+
};
|
|
550
|
+
void operation.promise.then(finish, finish);
|
|
551
|
+
return operation;
|
|
552
|
+
}
|
|
553
|
+
function waitForFlushOperation(operation, timeoutMs) {
|
|
554
|
+
if (operation.settled || timeoutMs <= 0)
|
|
555
|
+
return Promise.resolve();
|
|
556
|
+
return new Promise((resolve) => {
|
|
557
|
+
let settled = false;
|
|
558
|
+
const finish = () => {
|
|
559
|
+
if (settled)
|
|
560
|
+
return;
|
|
561
|
+
settled = true;
|
|
562
|
+
clearTimeout(timer);
|
|
563
|
+
operation.waiters.delete(finish);
|
|
564
|
+
resolve();
|
|
565
|
+
};
|
|
566
|
+
const timer = setTimeout(finish, timeoutMs);
|
|
567
|
+
operation.waiters.add(finish);
|
|
568
|
+
if (operation.settled) {
|
|
569
|
+
finish();
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
});
|
|
573
|
+
}
|
|
65
574
|
function buildPinoLogger(options) {
|
|
66
|
-
const { name, level = "info", logDir = DEFAULT_LOG_DIR, retention, stdout = false, silent = false, sync = false } = options;
|
|
575
|
+
const { name, level = "info", logDir = DEFAULT_LOG_DIR, retention, retentionBytes, maxFileBytes: requestedMaxFileBytes, maxFilesPerRun: requestedMaxFilesPerRun, maxRecordBytes: requestedMaxRecordBytes, stdout = false, silent = false, sync = false } = options;
|
|
67
576
|
if (silent) {
|
|
68
577
|
return { logger: pino({ level: "silent" }), destination: null };
|
|
69
578
|
}
|
|
70
579
|
mkdirSync(logDir, { recursive: true });
|
|
71
|
-
const runId = PROCESS_LOG_RUN_ID
|
|
580
|
+
const runId = `${PROCESS_LOG_RUN_ID}-${randomUUID()}`;
|
|
72
581
|
const filePath = join(logDir, `${name}-${runId}.log`);
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
582
|
+
createLogRunLease(filePath, runId);
|
|
583
|
+
try {
|
|
584
|
+
linkLatestLog(logDir, name, filePath, runId);
|
|
585
|
+
pruneOldLogs(logDir, name, filePath, retention, retentionBytes);
|
|
586
|
+
const maxFileBytes = resolvePositiveInteger(requestedMaxFileBytes, DEFAULT_MAX_FILE_BYTES);
|
|
587
|
+
const maxFilesPerRun = resolvePositiveInteger(requestedMaxFilesPerRun, DEFAULT_MAX_FILES_PER_RUN);
|
|
588
|
+
const maxRecordBytes = Math.min(maxFileBytes, resolvePositiveInteger(requestedMaxRecordBytes, DEFAULT_MAX_RECORD_BYTES));
|
|
589
|
+
const sonicDestination = pino.destination({
|
|
590
|
+
dest: filePath,
|
|
591
|
+
sync,
|
|
592
|
+
// Bound data queued while the filesystem is stalled. Dropping diagnostics is preferable to
|
|
593
|
+
// allowing a logging failure to consume the application's heap.
|
|
594
|
+
maxLength: Math.min(maxFileBytes, 1024 * 1024)
|
|
595
|
+
});
|
|
596
|
+
let initialBytes = 0;
|
|
597
|
+
try {
|
|
598
|
+
initialBytes = statSync(filePath).size;
|
|
599
|
+
} catch {
|
|
600
|
+
}
|
|
601
|
+
const destination = new BoundedLogDestination(sonicDestination, filePath, runId, logDir, name, retention, retentionBytes, maxFileBytes, maxFilesPerRun, maxRecordBytes, initialBytes);
|
|
602
|
+
const streams = [{ stream: destination }];
|
|
603
|
+
if (stdout) {
|
|
604
|
+
streams.unshift({
|
|
605
|
+
stream: new BoundedWritableDestination(process.stdout, DEFAULT_MAX_STDOUT_BUFFER_BYTES)
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
return { logger: pino({ level }, pino.multistream(streams)), destination };
|
|
609
|
+
} catch (error) {
|
|
610
|
+
removeOwnedLogRunLease(filePath, runId);
|
|
611
|
+
throw error;
|
|
612
|
+
}
|
|
81
613
|
}
|
|
82
614
|
function createLogger(options) {
|
|
83
615
|
let real = null;
|
|
84
|
-
const meta = { materialized: false, destination: null };
|
|
616
|
+
const meta = { materialized: false, destination: null, flushOperation: null };
|
|
85
617
|
const ensure = () => {
|
|
86
618
|
if (!real) {
|
|
87
619
|
const built = buildPinoLogger(options);
|
|
@@ -120,40 +652,8 @@ async function flushLogger(logger2, timeoutMs = 200) {
|
|
|
120
652
|
const dest = meta.destination;
|
|
121
653
|
if (!dest)
|
|
122
654
|
return;
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
const timer = setTimeout(() => resolve(false), timeoutMs);
|
|
126
|
-
dest.once?.("ready", () => {
|
|
127
|
-
clearTimeout(timer);
|
|
128
|
-
resolve(true);
|
|
129
|
-
});
|
|
130
|
-
dest.once?.("error", () => {
|
|
131
|
-
clearTimeout(timer);
|
|
132
|
-
resolve(false);
|
|
133
|
-
});
|
|
134
|
-
});
|
|
135
|
-
if (!opened)
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
if (dest._writing) {
|
|
139
|
-
const drained = await new Promise((resolve) => {
|
|
140
|
-
const timer = setTimeout(() => resolve(false), timeoutMs);
|
|
141
|
-
dest.once?.("drain", () => {
|
|
142
|
-
clearTimeout(timer);
|
|
143
|
-
resolve(true);
|
|
144
|
-
});
|
|
145
|
-
dest.once?.("error", () => {
|
|
146
|
-
clearTimeout(timer);
|
|
147
|
-
resolve(false);
|
|
148
|
-
});
|
|
149
|
-
});
|
|
150
|
-
if (!drained)
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
153
|
-
try {
|
|
154
|
-
dest.flushSync?.();
|
|
155
|
-
} catch {
|
|
156
|
-
}
|
|
655
|
+
const operation = meta.flushOperation ?? startFlushOperation(meta, dest);
|
|
656
|
+
await waitForFlushOperation(operation, resolveFlushTimeout(timeoutMs));
|
|
157
657
|
}
|
|
158
658
|
|
|
159
659
|
// src/runtime-env.ts
|