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

Separate user and machine output #1175

Merged

Conversation

autoantwort
Copy link
Contributor

@autoantwort autoantwort commented Aug 31, 2023

Fixes microsoft/vcpkg#33444, microsoft/vcpkg#30180 and microsoft/vcpkg#34805

I also noticed that when using depend-info and some warnings are destroying the output

@BillyONeal
Copy link
Member

The current "everything goes to stdout" behavior is intended, not a bug. /cc @ras0219-msft

@autoantwort
Copy link
Contributor Author

autoantwort commented Sep 3, 2023

At least for the commands where it destroys the format of the output it should go to stderr :)

@ras0219-msft
Copy link
Contributor

ras0219-msft commented Oct 6, 2023

First, I want to agree with the problem statement that when we are issuing structured machine output, we should separate all "user messages" into stderr. Note though, this is not just when we use Color::warning or Color::error!

For example, take --x-json. The only print that we should send to stdout is the single, final write of the serialized json object (ignoring the case of streaming). Every other print statement in the entire program is a user message.

Indeed, the vcpkg codebase emits user-facing messages 99% of the time and machine structured output only in very specific circumstances. Off the top of my head:

  • --x-json
  • depend-info (inc. --dgml)
  • fetch
  • x-download (Maybe? I think the only structured output of x-download is the error code)

For the vast majority of commands, all output is user-facing output and none is considered "machine readable".

As an aside, we must note the downside of splitting output streams: you lose synchronization. For example, if we emit I'm going to load file: abc to stdout (because it's not an "error" message) and then error: file not found to stderr (because it's an "error"), you would lose synchronization between the two. This implies that all non-machine output should go to the same channel.

One way to do this would be to simply make stderr the default print channel for the entire tool (since 99% of our print calls are non-machine-readable). Then, the handful of commands above would explicitly print to stdout with their machine-readable output.

However, this makes interacting with most of the vcpkg tool more painful than it needs to be; in most cases, we intentionally would print nothing to stdout. I'm not sure what others experiences are, but I'm regularly annoyed by typing curl | less or curl | tee foo.log and then remembering that I have to curl 2>&1 | less after the pipeline fails to do what I want.

Therefore, I think the most reasonable path forward for flags such as --x-json is to switch the "default" pipe from stdout to stderr when those flags are passed. Then, those commands can explicitly issue their machine-readable calls to stdout.

I could see some future argument that says the default pipe should always be stderr, but I'd need to discuss that more.

@autoantwort
Copy link
Contributor Author

autoantwort commented Oct 12, 2023

Therefore, I think the most reasonable path forward for flags such as --x-json is to switch the "default" pipe from stdout to stderr when those flags are passed. Then, those commands can explicitly issue their machine-readable calls to stdout.

👍 The only downside is that this is challenging to implement the nice way, at least I have not found one. The "problem" is that there are a lot of prints before the "command selection" is done. So we have to decide which "default" pipe should be used before the normal command selection is done. I will push a solution but maybe someone has a better idea 😅

An simpler to implement solution would be to output the machine readable output to stderr, but that can also be non intuitive, but a message that says you have to redirect stderr could help.

@autoantwort autoantwort force-pushed the feature/warning-errors-to-stderr branch from 2d92048 to 149b1bc Compare October 12, 2023 14:27
Comment on lines 396 to 424
if (*command == "depend-info")
{
bool found = false;
for (const auto& arg : args.forwardable_arguments)
{
if (found)
{
const auto value = Strings::ascii_to_lowercase(arg);
use_stderr = value == "dot" || value == "mermaid" || value == "dgml";
break;
}
if (Strings::case_insensitive_ascii_starts_with(arg, "--format"))
{
if (arg.length() > 9)
{
const auto value = Strings::ascii_to_lowercase(StringView(arg).substr(9));
use_stderr = value == "dot" || value == "mermaid" || value == "dgml";
break;
}
found = true;
}
}
}
}

args.forwardable_arguments = args.parser.get_remaining_args();
if (use_stderr)
{
msg::set_default_output_stream(OutputStream::StdErr);
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The ugly hack is here. But this now also works with vcpkg depend-info 'pcl[core,visualization]' --forMat=dOt --debug > test.dot

@autoantwort autoantwort changed the title Print warnings and errors on stderr Separate user and machine output Oct 12, 2023
@@ -388,16 +388,44 @@ namespace vcpkg
if (const auto p = args.debug_env.get()) args.debug = *p;

auto maybe_command = args.parser.extract_first_command_like_arg_lowercase();
args.forwardable_arguments = args.parser.get_remaining_args();
bool use_stderr = Util::contains(args.forwardable_arguments, "--x-json");
Copy link
Member

Choose a reason for hiding this comment

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

Instead of hard coding stuff like this, can we add a field to CommandMetadata that controls the setting?

Alternately, can we just do it at the top of the command function in question instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The problem then it is too late. There are Debug print statements before a command is selected.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

For example here:

vcpkg-tool/src/vcpkg.cpp

Lines 279 to 292 in e658895

VcpkgCmdArguments args = VcpkgCmdArguments::create_from_command_line(real_filesystem, argc, argv);
if (const auto p = args.debug.get()) Debug::g_debugging = *p;
args.imbue_from_environment();
VcpkgCmdArguments::imbue_or_apply_process_recursion(args);
if (const auto p = args.debug_env.get(); p && *p)
{
msg::write_unlocalized_text_to_stdout(Color::none,
"[DEBUG] The following environment variables are currently set:\n" +
get_environment_variables() + '\n');
}
else if (Debug::g_debugging)
{
Debug::println("To include the environment variables in debug output, pass --debug-env");
}

After line 283 we already have to know it output should go to stderr or stdout

Copy link
Contributor Author

Choose a reason for hiding this comment

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

But one could argue that we don't care if debug output breaks things

Copy link
Member

Choose a reason for hiding this comment

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

I would like to resolve those as 'debug output always goes to stderr' and 'console parse failures always go to stderr' on the grounds that neither of those types of output should apply to the edge cases @ras0219-msft enumerated above; I'm asking around and will keep you posted.

@BillyONeal
Copy link
Member

I could see some future argument that says the default pipe should always be stderr, but I'd need to discuss that more.

I think it would be good if console parse errors go directly to stderr too. In that case we generally don't expect the program to continue so synchronization issues and concerns about accidentally causing pipelines to fail should not be concerns.

@BillyONeal
Copy link
Member

@AugP @vicroms @JavierMatosD @data-queue @ras0219-msft and I discussed this today and agreed with the following outcomes:

  1. We disagree with @BillyONeal 's proposal to send all debug output to stderr, but we agree that we don't mind if it is written inconsistently.
  2. We agree with @BillyONeal 's proposal that all console parse failure messages should go to stderr in order to avoid the 'parse time machine' problem.

Do you mind if I morally take over this PR and make those changes?

@autoantwort
Copy link
Contributor Author

Do you mind if I morally take over this PR and make those changes?

No you can do that :)

…s-to-stderr

# Conflicts:
#	src/vcpkg/commands.build.cpp
#	src/vcpkg/commands.ci-verify-versions.cpp
#	src/vcpkg/paragraphs.cpp
…ding to solve a time machine problem.

Audit all commands which say 'x-json' and assign their output to stdout or stderr as appropriate.
@BillyONeal
Copy link
Member

@autoantwort Can you confirm you are happy with the changes I just pushed?

@autoantwort
Copy link
Contributor Author

Seems that something is not working:
grafik

Maybe we also need e2e tests

@autoantwort
Copy link
Contributor Author

@BillyONeal Maybe this warning also must go to stderr by default

@autoantwort
Copy link
Contributor Author

Also
grafik

@BillyONeal
Copy link
Member

@BillyONeal Maybe this warning also must go to stderr by default

I agree.

Also grafik

Hmmm I thought I got that one? Let me check again...

@BillyONeal
Copy link
Member

Ah the sendmetrics one is in vcpkg.cpp rather than vcpkgcmdarguments.cpp

@BillyONeal
Copy link
Member

OK please check again

@autoantwort
Copy link
Contributor Author

I don't know if we want to support that, but
grafik

Comment on lines +65 to +67
msg::write_unlocalized_text_to_stderr(
Color::warning,
LocalizedString::from_raw(WarningPrefix).append(msgVcpkgSendMetricsButDisabled).append_raw('\n'));
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Maybe
stderr_sink.println_warnin

Suggested change
msg::write_unlocalized_text_to_stderr(
Color::warning,
LocalizedString::from_raw(WarningPrefix).append(msgVcpkgSendMetricsButDisabled).append_raw('\n'));
stderr_sink.println_warning(msgVcpkgSendMetricsButDisabled);

Copy link
Member

Choose a reason for hiding this comment

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

I don't want to introduce use of the sinks where the polymorphism is not needed right now but I could see an argument in the future that all console output should be polymorphic like this. (But that seems way outside the scope of this PR)

@BillyONeal BillyONeal merged commit c1b70fb into microsoft:main Dec 11, 2023
5 checks passed
@BillyONeal
Copy link
Member

Thanks!

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

Successfully merging this pull request may close these issues.

--x-json has non-json messages in stdout
3 participants