Skip to content
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

[rush] Replay logs for warnings and errors as well #4668

Open
wants to merge 26 commits into
base: main
Choose a base branch
from

Conversation

aramissennyeydd
Copy link
Contributor

@aramissennyeydd aramissennyeydd commented Apr 29, 2024

Summary

Replays logs through the hook context's runWithTerminalAsync to hook into the stdio summarization functionality. This allows the report at the end of execution to be the same across machines.

Operations with 2 second durations were executed on the agent, those with <.5 seconds were restored from the build cache from the cobuild.

Before:

Agent 1

==[ SUCCESS WITH WARNINGS: 7 operations ]======================================

--[ WARNING: b (build) ]-------------------------------------[ 2.10 seconds ]--

done b

--[ WARNING: c (build) ]-------------------------------------[ 2.10 seconds ]--

done 

--[ WARNING: d (build) ]-------------------------------------[ 2.10 seconds ]--

done 

--[ WARNING: a (build) ]-------------------------------------[ 0.01 seconds ]--


--[ WARNING: e (build) ]-------------------------------------[ 2.09 seconds ]--

done 

--[ WARNING: f (build) ]-------------------------------------[ 2.13 seconds ]--

done f

--[ WARNING: g (build) ]-------------------------------------[ 2.14 seconds ]--

done g


Operations succeeded with warnings.

rush cobuild (16.95 seconds)

Agent 2

==[ SUCCESS WITH WARNINGS: 7 operations ]======================================

--[ WARNING: b (build) ]-------------------------------------[ 0.02 seconds ]--


--[ WARNING: c (build) ]-------------------------------------[ 0.01 seconds ]--


--[ WARNING: d (build) ]-------------------------------------[ 0.01 seconds ]--


--[ WARNING: a (build) ]-------------------------------------[ 2.10 seconds ]--

done a

--[ WARNING: e (build) ]-------------------------------------[ 0.02 seconds ]--


--[ WARNING: f (build) ]-------------------------------------[ 2.14 seconds ]--

done f

--[ WARNING: g (build) ]-------------------------------------[ 2.13 seconds ]--

done g


Operations succeeded with warnings.

rush cobuild (20.71 seconds)

After:

Agent 1

==[ SUCCESS WITH WARNINGS: 7 operations ]======================================

--[ WARNING: b (build) ]-------------------------------------[ 0.01 seconds ]--

done b

--[ WARNING: c (build) ]-------------------------------------[ 0.01 seconds ]--

done 

--[ WARNING: d (build) ]-------------------------------------[ 0.01 seconds ]--

done 

--[ WARNING: a (build) ]-------------------------------------[ 2.09 seconds ]--

done a

--[ WARNING: e (build) ]-------------------------------------[ 0.01 seconds ]--

done 

--[ WARNING: f (build) ]-------------------------------------[ 2.14 seconds ]--

done f

--[ WARNING: g (build) ]-------------------------------------[ 2.14 seconds ]--

done g


Operations succeeded with warnings.

Agent 2


==[ SUCCESS WITH WARNINGS: 7 operations ]======================================

--[ WARNING: b (build) ]-------------------------------------[ 2.09 seconds ]--

done b

--[ WARNING: c (build) ]-------------------------------------[ 2.08 seconds ]--

done 

--[ WARNING: d (build) ]-------------------------------------[ 2.09 seconds ]--

done 

--[ WARNING: a (build) ]-------------------------------------[ 0.01 seconds ]--

done a

--[ WARNING: e (build) ]-------------------------------------[ 2.09 seconds ]--

done 

--[ WARNING: f (build) ]-------------------------------------[ 2.14 seconds ]--

done f

--[ WARNING: g (build) ]-------------------------------------[ 2.14 seconds ]--

done g


Operations succeeded with warnings.

rush cobuild (16.92 seconds)

Details

This stores the log chunks into a JSON file, that is a direct write of the ITerminalChunk type. This is then restored while the build cache is being restored to the terminal with the correct severity following the ShellOperationPlugin logging semantics.

{
  "chunks": [
    {
      "kind": "O",
      "text": "Invoking: node ../build.js b \n"
    },
    {
      "kind": "O",
      "text": "start b\n"
    },
    {
      "kind": "E",
      "text": "done b\n",
    }
  ]
}

How it was tested

Tested against the repo cobuild sandbox with build caching enabled. Both restoring from the original build cache and cobuild cache work as expected and display the output above.

Impacted documentation

Copy link
Member

@iclanton iclanton left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This overall looks good to me, but it'd be good for @octogonz to review this too, as he originally wrote a lot of the streaming logs stuff.

@@ -197,6 +198,14 @@ export class OperationExecutionRecord implements IOperationRunnerContext {
`${associatedPhase.logFilenameIdentifier}${logFileSuffix}`
)
: undefined;
const logChunksWritable: LogChunksWritable | undefined =
associatedPhase && associatedProject
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is unclear why associatedPhase && associatedProject should be the criteria to determine whether we use LogChunksWritable to save chunks or not. Perhaps associatedPhase && associatedProject is only true when cobuilds are enabled? This code needs a comment explanation or a more intuitive criteria.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For example, if I want to test this PR to inspect the `${logFileBaseName}.chunks.jsonl` output, from looking at the code it's not obvious what I would need to do to cause associatedPhase && associatedProject to become true. Enable cobuilds?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm also unclear why these are nullable in the first place, they should both be defined when an operation is created with the PhasedOperationPlugin. Cobuilds are not a requirement. The && check is also part of the projectLogWritable above.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dmichon-msft do you know the answer?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason these were not required when added to the interface is that I had other plugins that created operations in the graph that were not strictly related to any phase or any operation. For example I had a custom pnpm installer that added operations to the Rush graph, and those installation steps were associated with external dependencies, not Rush projects.

In current usage we can make them be required (or make them required properties of an optional subobject that also contains all the various things that depend on their presence if we want to maintain that degree of compatibility with fewer checks).

@aramissennyeydd aramissennyeydd force-pushed the sennyeya/log-replays branch 2 times, most recently from fccbf98 to 82b0c1d Compare May 7, 2024 14:56
@octogonz octogonz changed the title fix: replay logs for warnings and errors as well [rush] Replay logs for warnings and errors as well May 14, 2024
@@ -197,6 +198,14 @@ export class OperationExecutionRecord implements IOperationRunnerContext {
`${associatedPhase.logFilenameIdentifier}${logFileSuffix}`
)
: undefined;
const logChunksWritable: LogChunksWritable | undefined =
associatedPhase && associatedProject
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason these were not required when added to the interface is that I had other plugins that created operations in the graph that were not strictly related to any phase or any operation. For example I had a custom pnpm installer that added operations to the Rush graph, and those installation steps were associated with external dependencies, not Rush projects.

In current usage we can make them be required (or make them required properties of an optional subobject that also contains all the various things that depend on their presence if we want to maintain that degree of compatibility with fewer checks).

@aramissennyeydd
Copy link
Contributor Author

@octogonz @dmichon-msft This is ready for re-review when you get a chance!

Comment on lines +151 to +152
if (await FileSystem.existsAsync(this._logChunksPath)) {
const rawLogChunks: string = await FileSystem.readFileAsync(this._logChunksPath);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's safer and faster to try/catch the await FileSystem.readFileAsync call and handle the FileSystem.isNotExistError(err) case. Existence checking files before reading them is well-documented as an error-prone antipattern. Yes, I know that there are many places in the rushstack repo where we do that, but we don't need to add more of them.

Comment on lines +168 to +173
if (this._enableChunkedOutput) {
if (!this._chunkWriter) {
this._chunkWriter = FileWriter.open(this.logChunksPath);
}
this._chunkWriter.write(JSON.stringify(chunk) + '\n');
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like creating files inside of the write callback. I'd rather pay the tax of creating this file up front and then letting it be empty.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, there's a handler for this._logWriter being closed; we should have the same for this._chunkWriter for the same reason.

Copy link
Member

@iclanton iclanton left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How painful would it be to add a unit test for the chunked file format?

Comment on lines 196 to 201
? new ProjectLogWritable(
associatedProject,
this.collatedWriter.terminal,
`${associatedPhase.logFilenameIdentifier}${logFileSuffix}`
`${associatedPhase.logFilenameIdentifier}${logFileSuffix}`,
true
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With this many arguments, consider making this an options object with named properties.

@iclanton
Copy link
Member

@aramissennyeydd - Looks good overall. Once you just address @dmichon-msft's comments we can get this in.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: In Progress
Development

Successfully merging this pull request may close these issues.

None yet

4 participants