-
Notifications
You must be signed in to change notification settings - Fork 4
SED-4789 Implement live reporting in the Node.js agent #661
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,6 +18,7 @@ | |
| */ | ||
|
|
||
| const { OutputBuilder } = require("./output"); | ||
| const { createLiveReporting } = require("./live-reporting"); | ||
| const Session = require("./session"); | ||
| const fs = require("fs"); | ||
| const path = require('path') | ||
|
|
@@ -34,6 +35,7 @@ process.on('message', async ({ type, projectPath, functionName, input, propertie | |
| pendingUncaughtException = null; | ||
| console.log("[Agent fork] Calling keyword " + functionName) | ||
| const outputBuilder = new OutputBuilder(); | ||
| const liveReporting = createLiveReporting(properties); | ||
| try { | ||
| if (!keywordDirectoryExists(projectPath, keywordDirectory)) { | ||
| outputBuilder.fail("The keyword directory '" + keywordDirectory + "' doesn't exist in " + path.basename(projectPath) + ". Possible cause: If using TypeScript, the keywords may not have been compiled. Fix: Ensure your project is built before deploying to Step or during 'npm install'.") | ||
|
|
@@ -52,7 +54,7 @@ process.on('message', async ({ type, projectPath, functionName, input, propertie | |
| if(beforeKeyword) { | ||
| await beforeKeyword(functionName); | ||
| } | ||
| await keyword(input, outputBuilder, session, properties); | ||
| await keyword(input, outputBuilder, session, properties, liveReporting); | ||
| } catch (e) { | ||
| console.log("[Agent fork] Keyword execution failed with following error", e) | ||
| const onError = module['onError']; | ||
|
|
@@ -82,6 +84,12 @@ process.on('message', async ({ type, projectPath, functionName, input, propertie | |
| // Flush the event loop so unhandledRejection / uncaughtException from the keyword | ||
| // (e.g. fire-and-forget promises, nextTick throws) land before we send the result. | ||
| await new Promise(resolve => setImmediate(resolve)); | ||
| // Close live reporting: flushes any buffered measures and waits for in-flight uploads. | ||
| try { | ||
| await liveReporting.close(); | ||
| } catch (e) { | ||
| console.log("[Agent fork] Error while closing live reporting", e); | ||
| } | ||
|
Comment on lines
+92
to
+98
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add a safety check to ensure try {
if (liveReporting) {
await liveReporting.close();
}
} catch (e) {
console.log("[Agent fork] Error while closing live reporting", e);
}References
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done — added the |
||
| // Surface inter-keyword errors first, labelled clearly as coming from a previous keyword. | ||
| if (prevUnhandledRejection) { | ||
| const sep = outputBuilder.hasError() ? '\n' : ''; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,6 +7,16 @@ const logger = require('../logger').child({ component: 'Agent' }); | |
|
|
||
| const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm'; | ||
|
|
||
| // Resolve the bundled `ws` module path once, so it can be injected into forked keyword processes | ||
| // (whose own require() resolves against the keyword project, not the agent). Used by live reporting | ||
| // for streaming file uploads. If ws is missing, file uploads degrade to discarding. | ||
| let wsModulePath = null; | ||
| try { | ||
| wsModulePath = require.resolve('ws'); | ||
| } catch { | ||
| logger.warn('The ws module could not be resolved; live reporting file uploads will be disabled'); | ||
| } | ||
|
|
||
| process.on('unhandledRejection', error => { | ||
| logger.error('Critical: an unhandled error (unhandled promise rejection) occurred and might not have been reported:', error) | ||
| }) | ||
|
|
@@ -348,10 +358,15 @@ class ForkedAgent { | |
| fs.mkdirSync(agentForkerLibPath, { recursive: true }); | ||
| fs.copyFileSync(path.resolve(__dirname, 'agent-fork.js'), path.join(agentForkerLibPath, 'agent-fork.js')); | ||
| fs.copyFileSync(path.join(__dirname, 'output.js'), path.join(agentForkerLibPath, 'output.js')); | ||
| fs.copyFileSync(path.join(__dirname, 'live-reporting.js'), path.join(agentForkerLibPath, 'live-reporting.js')); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I assume this is the reason of the "big" live-reporting.js file, but for code review and maintainability it would have been nicer to split the code.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agree with this. I would also prefer to split the code in dedicated files. In theory we could already do it and simply add more files here but I would prefer to have a better solution than the copy first
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done — |
||
| fs.copyFileSync(path.join(__dirname, 'session.js'), path.join(agentForkerLibPath, 'session.js')); | ||
| this.agentForkerLibPath = agentForkerLibPath; | ||
| this.startupChunks = []; | ||
| this.forkProcess = fork(path.join(agentForkerLibPath, 'agent-fork.js'), [], {cwd: keywordProjectPath, silent: true}); | ||
| const forkEnv = { ...process.env }; | ||
| if (wsModulePath) { | ||
| forkEnv.STEP_AGENT_WS_MODULE = wsModulePath; | ||
| } | ||
| this.forkProcess = fork(path.join(agentForkerLibPath, 'agent-fork.js'), [], {cwd: keywordProjectPath, silent: true, env: forkEnv}); | ||
| // Capture stdout/stderr immediately so startup crashes are not silently lost | ||
| if (this.forkProcess.stdout) { | ||
| this.forkProcess.stdout.on('data', (data) => this.startupChunks.push(data)); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To prevent any unexpected errors during
createLiveReportinginitialization from crashing the fork process, declareliveReportingusingletand initialize it inside thetryblock. This ensures any initialization errors are caught and reported gracefully viaoutputBuilder.fail.Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done —
liveReportingis now declared withletand initialized as the first statement inside thetry, so any initialization error is reported viaoutputBuilder.failinstead of escaping the message handler.