Skip to content

Conversation

@marcoesters
Copy link
Contributor

Description

Add the ability to log conda and conda-standalone output into a file. This feature essentially simulates the tee command.

This can be used to populate log files for installations, especially on systems where tee is not an option (Windows, in particular). One caveat of capturing output streams this way is that carriage returns are not properly handled and are output into new lines. This means that

Executing transaction... working... done

becomes

Executing transaction...
working...
done

I also added some more logging and error handling output to the uninstallation process.

Xref: conda/constructor#1108

Checklist - did you ...

  • Add a file to the news directory (using the template) for the next release's release notes?
  • Add / update necessary tests?
  • Add / update outdated documentation?

@github-project-automation github-project-automation bot moved this to 🆕 New in 🔎 Review Nov 11, 2025
@conda-bot conda-bot added the cla-signed [bot] added once the contributor has signed the CLA label Nov 11, 2025
@marcoesters marcoesters marked this pull request as ready for review November 12, 2025 00:03
@marcoesters marcoesters requested a review from a team as a code owner November 12, 2025 00:03
### Enhancements

* <news item>
* Add option to log `conda-standalone` and `conda` outputs into log file. (#218)
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
* Add option to log `conda-standalone` and `conda` outputs into log file. (#218)
* Add option to log `conda-standalone` and `conda` outputs into a log file. (#218)

Optional: I think it could be good also to mention how to enable it. I'm thinking in the perspective of the user, help them know what to look for in order to enable it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks! Good call on adding the CLI option

Comment on lines 233 to 234
f"Failed to unprotect {env_prefix}. Try to re-run the uninstallation with "
f"elevated privileges or remove the file {frozen_file} manually."
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we wrap the {...} in simple quotes like '{...}'? Just thinking if this is something that could contain spaces etc.

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 think that's a good idea - done

conda_main("remove", "-y", "-p", str(env_prefix), "--all")
return_code = conda_main("remove", "-y", "-p", str(env_prefix), "--all")
if return_code != 0:
raise RuntimeError(f"Failed to remove environment {env_prefix}.")
Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure if this is necessary, but would help with debugging. Feel free to just ignore.

Suggested change
raise RuntimeError(f"Failed to remove environment {env_prefix}.")
raise RuntimeError(f"Failed to remove environment {env_prefix}. Return code: {return_code}")

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Realistically, the return code won't tell us more than the preceding error messages, so I don't think the return code is necessary.

conda_main("clean", "--all", "-y")
return_code = conda_main("clean", "--all", "-y")
if return_code != 0:
logger.warning("Failed to remove all cache files.")
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here (if we should add the return_code to the warning). Perhaps even less important since it's just a warning.

Comment on lines 26 to 27
def flush(self):
pass
Copy link
Contributor

Choose a reason for hiding this comment

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

Just curious why this is needed. Maybe a docstring could help here understand why we just implement it as empty. I assume it's not necessary to flush since we the write methods write directly via self.logger.log.

Copy link
Contributor Author

@marcoesters marcoesters Nov 14, 2025

Choose a reason for hiding this comment

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

Your guess is correct. We must have a flush method implemented, but the log call does all that work already. I added a comment.

Copy link
Contributor

@lrandersson lrandersson left a comment

Choose a reason for hiding this comment

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

I know you need a secondary review but LGTM, well done!

Comment on lines +25 to +30
logger = logging.getLogger()
# On Windows, these warnings are expected because the uninstaller may still be
# accessing files (like install.log) that conda cannot rename.
if sys.platform == "win32":
conda_logger = logging.getLogger("conda.gateways.disk.delete")
conda_logger.addFilter(lambda record: "Could not remove or rename" not in record.getMessage())
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do we need this as import-time logic, or should it be a separate function we call as part of the CLI initialization? Is that too late in the process?

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 like it for the visibility, but you are right that it isn't strictly needed at import time. So, I went a third way: importing these main execution function isn't needed until we actually use it. So, I just moved the import statements into the execute function.

Comment on lines 180 to 187
logger_parser = argparse.ArgumentParser(add_help=False)
logger_parser.add_argument("--log-file", type=Path)
args, remaining = logger_parser.parse_known_args()
if args.log_file:
sys.argv[1:] = remaining
setup_logger(args.log_file.resolve())

return main()
Copy link
Collaborator

Choose a reason for hiding this comment

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

If you turn setup_logger into a context manager, then change this to:

Suggested change
logger_parser = argparse.ArgumentParser(add_help=False)
logger_parser.add_argument("--log-file", type=Path)
args, remaining = logger_parser.parse_known_args()
if args.log_file:
sys.argv[1:] = remaining
setup_logger(args.log_file.resolve())
return main()
logger_parser = argparse.ArgumentParser(add_help=False)
logger_parser.add_argument("--log-file", type=Path)
args, remaining = logger_parser.parse_known_args()
if args.log_file:
sys.argv[1:] = remaining
maybe_setup_logger = setup_logger(args.log_file.resolve())
else:
maybe_setup_logger = nullcontext() # this comes from contextlib
with maybe_setup_logger:
return main()

# on CI, setup-miniconda registers `test` as auto-activate for every CMD
# which adds some unnecessary stderr output; so, only read the first line
log_text = log_text.split("\n")[0]
assert os.path.realpath(log_text.strip()) == os.path.realpath(CONDA_EXE)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do we want to assert whether the process had any output? All of it should have technically gone to the logfile, right?

Suggested change
assert os.path.realpath(log_text.strip()) == os.path.realpath(CONDA_EXE)
assert os.path.realpath(log_text.strip()) == os.path.realpath(CONDA_EXE)
assert not process.stdout + process.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.

Output goes to both the screen and the log file, so we should still check the output.

@marcoesters marcoesters requested a review from jaimergp November 17, 2025 17:03
@github-project-automation github-project-automation bot moved this from 🆕 New to ✅ Approved in 🔎 Review Nov 21, 2025
@marcoesters marcoesters merged commit 546d786 into conda:main Nov 21, 2025
7 checks passed
@marcoesters marcoesters deleted the add-logger branch November 21, 2025 17:43
@github-project-automation github-project-automation bot moved this from ✅ Approved to 🏁 Done in 🔎 Review Nov 21, 2025
@github-project-automation github-project-automation bot moved this from In review 🔍 to Done 💪🏾 in conda Roadmap and Sprint Planning Nov 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cla-signed [bot] added once the contributor has signed the CLA

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

4 participants