| 652 | } |
| 653 | |
| 654 | function tailLog(lines) { |
| 655 | if (!fs.existsSync(LOG_FILE)) return { error: 'No log file' }; |
| 656 | try { |
| 657 | const n = parseInt(lines, 10) || 20; |
| 658 | const fd = fs.openSync(LOG_FILE, 'r'); |
| 659 | var content = ''; |
| 660 | try { |
| 661 | const stat = fs.fstatSync(fd); |
| 662 | if (stat.size > 0) { |
| 663 | const chunkSize = 64 * 1024; |
| 664 | let position = stat.size; |
| 665 | let collected = ''; |
| 666 | let lineCount = 0; |
| 667 | while (position > 0 && lineCount <= n) { |
| 668 | const readSize = Math.min(chunkSize, position); |
| 669 | position -= readSize; |
| 670 | const buf = Buffer.alloc(readSize); |
| 671 | fs.readSync(fd, buf, 0, readSize, position); |
| 672 | collected = buf.toString('utf8') + collected; |
| 673 | lineCount = collected.split('\n').length - 1; |
| 674 | } |
| 675 | const rows = collected.split('\n'); |
| 676 | if (rows.length > 0 && rows[rows.length - 1] === '') rows.pop(); |
| 677 | content = rows.slice(-n).join('\n'); |
| 678 | } |
| 679 | } finally { |
| 680 | fs.closeSync(fd); |
| 681 | } |
| 682 | return { |
| 683 | file: path.relative(WORKSPACE_ROOT, LOG_FILE), |
| 684 | content: content |
| 685 | }; |
| 686 | } catch (e) { |
| 687 | return { error: e.message }; |
| 688 | } |
| 689 | } |
| 690 | |
| 691 | function checkHealth() { |
| 692 | var pids = getOwnedLoopPids(getRunningPids()); |