on_Snapshot__21_consolelog.bg.js 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. #!/usr/bin/env node
  2. /**
  3. * Capture console output from a page.
  4. *
  5. * This hook sets up CDP listeners BEFORE chrome_navigate loads the page,
  6. * then waits for navigation to complete. The listeners stay active through
  7. * navigation and capture all console output.
  8. *
  9. * Usage: on_Snapshot__21_consolelog.js --url=<url> --snapshot-id=<uuid>
  10. * Output: Writes console.jsonl
  11. */
  12. const fs = require('fs');
  13. const path = require('path');
  14. // Add NODE_MODULES_DIR to module resolution paths if set
  15. if (process.env.NODE_MODULES_DIR) module.paths.unshift(process.env.NODE_MODULES_DIR);
  16. const puppeteer = require('puppeteer-core');
  17. // Import shared utilities from chrome_utils.js
  18. const {
  19. getEnvBool,
  20. getEnvInt,
  21. parseArgs,
  22. connectToPage,
  23. waitForPageLoaded,
  24. } = require('../chrome/chrome_utils.js');
  25. const PLUGIN_NAME = 'consolelog';
  26. const OUTPUT_DIR = '.';
  27. const OUTPUT_FILE = 'console.jsonl';
  28. const CHROME_SESSION_DIR = '../chrome';
  29. let browser = null;
  30. let page = null;
  31. let logCount = 0;
  32. let errorCount = 0;
  33. let requestFailCount = 0;
  34. let shuttingDown = false;
  35. async function serializeArgs(args) {
  36. const serialized = [];
  37. for (const arg of args) {
  38. try {
  39. const json = await arg.jsonValue();
  40. serialized.push(json);
  41. } catch (e) {
  42. try {
  43. serialized.push(String(arg));
  44. } catch (e2) {
  45. serialized.push('[Unserializable]');
  46. }
  47. }
  48. }
  49. return serialized;
  50. }
  51. async function setupListeners() {
  52. const outputPath = path.join(OUTPUT_DIR, OUTPUT_FILE);
  53. const timeout = getEnvInt('CONSOLELOG_TIMEOUT', 30) * 1000;
  54. fs.writeFileSync(outputPath, ''); // Clear existing
  55. // Connect to Chrome page using shared utility
  56. const { browser, page } = await connectToPage({
  57. chromeSessionDir: CHROME_SESSION_DIR,
  58. timeoutMs: timeout,
  59. puppeteer,
  60. });
  61. // Set up listeners that write directly to file
  62. page.on('console', async (msg) => {
  63. try {
  64. const logEntry = {
  65. timestamp: new Date().toISOString(),
  66. type: msg.type(),
  67. text: msg.text(),
  68. args: await serializeArgs(msg.args()),
  69. location: msg.location(),
  70. };
  71. fs.appendFileSync(outputPath, JSON.stringify(logEntry) + '\n');
  72. logCount += 1;
  73. } catch (e) {
  74. // Ignore errors
  75. }
  76. });
  77. page.on('pageerror', (error) => {
  78. try {
  79. const logEntry = {
  80. timestamp: new Date().toISOString(),
  81. type: 'error',
  82. text: error.message,
  83. stack: error.stack || '',
  84. };
  85. fs.appendFileSync(outputPath, JSON.stringify(logEntry) + '\n');
  86. errorCount += 1;
  87. } catch (e) {
  88. // Ignore
  89. }
  90. });
  91. page.on('requestfailed', (request) => {
  92. try {
  93. const failure = request.failure();
  94. const logEntry = {
  95. timestamp: new Date().toISOString(),
  96. type: 'request_failed',
  97. text: `Request failed: ${request.url()}`,
  98. error: failure ? failure.errorText : 'Unknown error',
  99. url: request.url(),
  100. };
  101. fs.appendFileSync(outputPath, JSON.stringify(logEntry) + '\n');
  102. requestFailCount += 1;
  103. } catch (e) {
  104. // Ignore
  105. }
  106. });
  107. return { browser, page };
  108. }
  109. function emitResult(status = 'succeeded') {
  110. if (shuttingDown) return;
  111. shuttingDown = true;
  112. const counts = `${logCount} console, ${errorCount} errors, ${requestFailCount} failed requests`;
  113. console.log(JSON.stringify({
  114. type: 'ArchiveResult',
  115. status,
  116. output_str: `${OUTPUT_FILE} (${counts})`,
  117. }));
  118. }
  119. async function handleShutdown(signal) {
  120. console.error(`\nReceived ${signal}, emitting final results...`);
  121. emitResult('succeeded');
  122. if (browser) {
  123. try {
  124. browser.disconnect();
  125. } catch (e) {}
  126. }
  127. process.exit(0);
  128. }
  129. async function main() {
  130. const args = parseArgs();
  131. const url = args.url;
  132. const snapshotId = args.snapshot_id;
  133. if (!url || !snapshotId) {
  134. console.error('Usage: on_Snapshot__21_consolelog.js --url=<url> --snapshot-id=<uuid>');
  135. process.exit(1);
  136. }
  137. if (!getEnvBool('CONSOLELOG_ENABLED', true)) {
  138. console.error('Skipping (CONSOLELOG_ENABLED=False)');
  139. console.log(JSON.stringify({type: 'ArchiveResult', status: 'skipped', output_str: 'CONSOLELOG_ENABLED=False'}));
  140. process.exit(0);
  141. }
  142. try {
  143. // Set up listeners BEFORE navigation
  144. const connection = await setupListeners();
  145. browser = connection.browser;
  146. page = connection.page;
  147. // Register signal handlers for graceful shutdown
  148. process.on('SIGTERM', () => handleShutdown('SIGTERM'));
  149. process.on('SIGINT', () => handleShutdown('SIGINT'));
  150. // Wait for chrome_navigate to complete (non-fatal)
  151. try {
  152. const timeout = getEnvInt('CONSOLELOG_TIMEOUT', 30) * 1000;
  153. await waitForPageLoaded(CHROME_SESSION_DIR, timeout * 4, 500);
  154. } catch (e) {
  155. console.error(`WARN: ${e.message}`);
  156. }
  157. // console.error('Consolelog active, waiting for cleanup signal...');
  158. await new Promise(() => {}); // Keep alive until SIGTERM
  159. return;
  160. } catch (e) {
  161. const error = `${e.name}: ${e.message}`;
  162. console.error(`ERROR: ${error}`);
  163. console.log(JSON.stringify({
  164. type: 'ArchiveResult',
  165. status: 'failed',
  166. output_str: error,
  167. }));
  168. process.exit(1);
  169. }
  170. }
  171. main().catch(e => {
  172. console.error(`Fatal error: ${e.message}`);
  173. process.exit(1);
  174. });