@blockrun/franklin 3.15.19 → 3.15.20
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/commands/logs.js +28 -19
- package/dist/logger.js +47 -0
- package/package.json +1 -1
package/dist/commands/logs.js
CHANGED
|
@@ -3,17 +3,25 @@ import path from 'node:path';
|
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import { BLOCKRUN_DIR } from '../config.js';
|
|
5
5
|
const LOG_FILE = path.join(BLOCKRUN_DIR, 'franklin-debug.log');
|
|
6
|
+
const ARCHIVE_LOG_FILE = path.join(BLOCKRUN_DIR, 'franklin-debug.log.1');
|
|
6
7
|
const LEGACY_LOG_FILE = path.join(BLOCKRUN_DIR, 'runcode-debug.log');
|
|
7
|
-
const MAX_LOG_SIZE = 10 * 1024 * 1024; // 10MB auto-rotate threshold
|
|
8
8
|
export function logsCommand(options) {
|
|
9
9
|
if (options.clear) {
|
|
10
|
+
let cleared = false;
|
|
10
11
|
try {
|
|
11
12
|
fs.unlinkSync(LOG_FILE);
|
|
12
|
-
|
|
13
|
+
cleared = true;
|
|
13
14
|
}
|
|
14
|
-
catch {
|
|
15
|
-
|
|
15
|
+
catch { /* may not exist */ }
|
|
16
|
+
try {
|
|
17
|
+
fs.unlinkSync(ARCHIVE_LOG_FILE);
|
|
18
|
+
cleared = true;
|
|
16
19
|
}
|
|
20
|
+
catch { /* may not exist */ }
|
|
21
|
+
if (cleared)
|
|
22
|
+
console.log(chalk.green('Logs cleared.'));
|
|
23
|
+
else
|
|
24
|
+
console.log(chalk.dim('No log file to clear.'));
|
|
17
25
|
return;
|
|
18
26
|
}
|
|
19
27
|
// Migrate legacy log file
|
|
@@ -23,23 +31,17 @@ export function logsCommand(options) {
|
|
|
23
31
|
}
|
|
24
32
|
catch { /* best effort */ }
|
|
25
33
|
}
|
|
26
|
-
|
|
34
|
+
// Logger now self-rotates on write (in src/logger.ts). The previous
|
|
35
|
+
// in-place "slice off the first half" rotation here was destructive
|
|
36
|
+
// — every invocation that crossed 10 MB silently dropped half the
|
|
37
|
+
// history. With self-rotation in place this command no longer needs
|
|
38
|
+
// to mutate the file at all; it just stitches the archive + live
|
|
39
|
+
// log for display.
|
|
40
|
+
if (!fs.existsSync(LOG_FILE) && !fs.existsSync(ARCHIVE_LOG_FILE)) {
|
|
27
41
|
console.log(chalk.dim('No logs yet. Start franklin with --debug to enable logging:'));
|
|
28
42
|
console.log(chalk.bold(' franklin start --debug'));
|
|
29
43
|
return;
|
|
30
44
|
}
|
|
31
|
-
// Auto-rotate: if file is over threshold, keep only last half
|
|
32
|
-
try {
|
|
33
|
-
const stat = fs.statSync(LOG_FILE);
|
|
34
|
-
if (stat.size > MAX_LOG_SIZE) {
|
|
35
|
-
const content = fs.readFileSync(LOG_FILE, 'utf-8');
|
|
36
|
-
const lines = content.split('\n');
|
|
37
|
-
const half = lines.slice(Math.floor(lines.length / 2));
|
|
38
|
-
fs.writeFileSync(LOG_FILE, half.join('\n'));
|
|
39
|
-
console.log(chalk.dim(`(Rotated log — was ${(stat.size / 1024 / 1024).toFixed(1)}MB)`));
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
catch { /* ignore rotation errors */ }
|
|
43
45
|
const parsed = parseInt(options.lines || '50', 10);
|
|
44
46
|
const tailLines = isNaN(parsed) ? 50 : Math.max(1, Math.min(10000, parsed));
|
|
45
47
|
if (options.follow) {
|
|
@@ -78,8 +80,15 @@ export function logsCommand(options) {
|
|
|
78
80
|
}
|
|
79
81
|
function printLastLines(n) {
|
|
80
82
|
try {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
+
// Logger self-rotates to franklin-debug.log.1 when the live log
|
|
84
|
+
// crosses 10MB. Stitch the archive on first so requests for "last N"
|
|
85
|
+
// can span the rotation boundary — without this, immediately after
|
|
86
|
+
// a rotation `franklin logs --lines 1000` would show only whatever
|
|
87
|
+
// lines have been written since rotation, even though the archive
|
|
88
|
+
// is sitting right next to it.
|
|
89
|
+
const archive = fs.existsSync(ARCHIVE_LOG_FILE) ? fs.readFileSync(ARCHIVE_LOG_FILE, 'utf-8') : '';
|
|
90
|
+
const live = fs.existsSync(LOG_FILE) ? fs.readFileSync(LOG_FILE, 'utf-8') : '';
|
|
91
|
+
const lines = (archive + live).split('\n').filter(Boolean);
|
|
83
92
|
const start = Math.max(0, lines.length - n);
|
|
84
93
|
const slice = lines.slice(start);
|
|
85
94
|
if (start > 0) {
|
package/dist/logger.js
CHANGED
|
@@ -14,6 +14,20 @@ import fs from 'node:fs';
|
|
|
14
14
|
import path from 'node:path';
|
|
15
15
|
import { BLOCKRUN_DIR } from './config.js';
|
|
16
16
|
const LOG_FILE = path.join(BLOCKRUN_DIR, 'franklin-debug.log');
|
|
17
|
+
const ARCHIVE_FILE = path.join(BLOCKRUN_DIR, 'franklin-debug.log.1');
|
|
18
|
+
// Self-rotation threshold. When the live log crosses this size on a
|
|
19
|
+
// write, rename it to franklin-debug.log.1 (overwriting any previous
|
|
20
|
+
// archive) and start fresh. Non-destructive: one full archive of the
|
|
21
|
+
// most recent ROTATE_AT_BYTES is always retained, so users can still
|
|
22
|
+
// read history across the rotation. Earlier behavior (only triggered
|
|
23
|
+
// by `franklin logs`, sliced the file in half in-place) lost history
|
|
24
|
+
// outright and only ran if the user happened to invoke `franklin logs`.
|
|
25
|
+
const ROTATE_AT_BYTES = 10 * 1024 * 1024; // 10 MB
|
|
26
|
+
// Probe every N writes to amortize the stat() — average debug entry is
|
|
27
|
+
// ~80 bytes, so 1000 writes (~80 KB worth) between checks keeps the
|
|
28
|
+
// overhead negligible while still catching a runaway log within seconds.
|
|
29
|
+
const ROTATE_PROBE_EVERY_N_WRITES = 1000;
|
|
30
|
+
let writesSinceRotateProbe = 0;
|
|
17
31
|
// Strip ANSI escapes + carriage returns so the log stays grep-able.
|
|
18
32
|
const ANSI_RE = /\x1b\[[0-9;]*m|\x1b\][^\x07]*\x07|\r/g;
|
|
19
33
|
let debugMode = false;
|
|
@@ -36,9 +50,42 @@ function ensureDir() {
|
|
|
36
50
|
}
|
|
37
51
|
catch { /* readonly mount / disk full — keep trying so a remount recovers */ }
|
|
38
52
|
}
|
|
53
|
+
function maybeRotate() {
|
|
54
|
+
try {
|
|
55
|
+
if (!fs.existsSync(LOG_FILE))
|
|
56
|
+
return;
|
|
57
|
+
const { size } = fs.statSync(LOG_FILE);
|
|
58
|
+
if (size < ROTATE_AT_BYTES)
|
|
59
|
+
return;
|
|
60
|
+
// renameSync overwrites an existing target on POSIX, which is what
|
|
61
|
+
// we want — single archive, always the most recent rotation. On
|
|
62
|
+
// Windows the rename can EEXIST; in that case unlink the archive
|
|
63
|
+
// first and retry. If even that fails, fall through silently rather
|
|
64
|
+
// than leaving the log file in a half-rotated state.
|
|
65
|
+
try {
|
|
66
|
+
fs.renameSync(LOG_FILE, ARCHIVE_FILE);
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
try {
|
|
70
|
+
fs.unlinkSync(ARCHIVE_FILE);
|
|
71
|
+
}
|
|
72
|
+
catch { /* may not exist */ }
|
|
73
|
+
try {
|
|
74
|
+
fs.renameSync(LOG_FILE, ARCHIVE_FILE);
|
|
75
|
+
}
|
|
76
|
+
catch { /* give up */ }
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch { /* best effort */ }
|
|
80
|
+
}
|
|
39
81
|
function writeFile(level, msg) {
|
|
40
82
|
ensureDir();
|
|
41
83
|
try {
|
|
84
|
+
writesSinceRotateProbe++;
|
|
85
|
+
if (writesSinceRotateProbe >= ROTATE_PROBE_EVERY_N_WRITES) {
|
|
86
|
+
writesSinceRotateProbe = 0;
|
|
87
|
+
maybeRotate();
|
|
88
|
+
}
|
|
42
89
|
const clean = msg.replace(ANSI_RE, '');
|
|
43
90
|
fs.appendFileSync(LOG_FILE, `[${new Date().toISOString()}] [${level.toUpperCase()}] ${clean}\n`);
|
|
44
91
|
}
|
package/package.json
CHANGED