From d9ac26431a9cfdf8b4619a10621dfb9125e39c69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klaim=20=28Jo=C3=ABl=20Lamotte=29?= <142265+Klaim@users.noreply.github.com> Date: Thu, 19 Mar 2026 15:56:38 +0100 Subject: [PATCH 01/52] Don't log if `--json` or `--quiet` --- libmamba/src/api/configuration.cpp | 14 ++++++++++++++ libmamba/src/core/context.cpp | 15 ++++++++++++++- libmamba/src/core/logging.cpp | 5 +++++ 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/libmamba/src/api/configuration.cpp b/libmamba/src/api/configuration.cpp index a4cb83c3ed..85d8189fc8 100644 --- a/libmamba/src/api/configuration.cpp +++ b/libmamba/src/api/configuration.cpp @@ -1247,6 +1247,18 @@ namespace mamba set_configurables(); } + namespace + { + // Immediately deactivates the logging system if feature is enabled. + void stop_logging_if_enabled(const bool enabled) + { + if (enabled) + { + logging::stop_logging(); + } + } + } + void Configuration::set_configurables() { // Basic @@ -1968,6 +1980,7 @@ namespace mamba .group("Output, Prompt and Flow Control") .set_rc_configurable() .needs({ "print_config_only", "print_context_only" }) + .set_post_merge_hook(stop_logging_if_enabled) .set_env_var_names() .description("Report all output as json")); @@ -2048,6 +2061,7 @@ namespace mamba .set_rc_configurable() .set_env_var_names() .needs({ "json", "print_config_only", "print_context_only" }) + .set_post_merge_hook(stop_logging_if_enabled) .description("Set quiet mode (print less output)")); insert(Configurable("verbose", 0) diff --git a/libmamba/src/core/context.cpp b/libmamba/src/core/context.cpp index 75e60353be..7c647dac3b 100644 --- a/libmamba/src/core/context.cpp +++ b/libmamba/src/core/context.cpp @@ -53,6 +53,11 @@ namespace mamba void Context::start_logging(logging::AnyLogHandler log_handler) { + if (output_params.json or output_params.quiet) + { + throw mamba_error{ "cannot start logging with `--json` or `--quiet`", mamba_error_code::incorrect_usage }; + } + // Only change the log-handler if specified, keep the current one otherwise. if (log_handler) { @@ -98,10 +103,18 @@ namespace mamba enable_signal_handling(); } - if (options.enable_logging) + // Do not start logging if `--json` or `--quiet` are used. + if (options.enable_logging and not (output_params.json or output_params.quiet)) { start_logging(std::move(log_handler)); } + + if (output_params.json or output_params.quiet) + { + // Explicitly stop logging in case a log-handler + // has already been installed before `Context`'s creation. + logging::stop_logging(); + } } Context::~Context() diff --git a/libmamba/src/core/logging.cpp b/libmamba/src/core/logging.cpp index 39623385e6..c03e1dcd9b 100644 --- a/libmamba/src/core/logging.cpp +++ b/libmamba/src/core/logging.cpp @@ -116,6 +116,11 @@ namespace mamba::logging auto stop_logging(stop_reason reason) -> AnyLogHandler { + if (not details::current_log_handler) + { + // No installed log-handler: do nothing. + return {}; + } return change_log_handler({}, {}, reason, {}); } From 243d9286a6c4ede8e62a36a0ebe28c0564f0bbfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klaim=20=28Jo=C3=ABl=20Lamotte=29?= <142265+Klaim@users.noreply.github.com> Date: Wed, 25 Mar 2026 18:58:09 +0100 Subject: [PATCH 02/52] fixed LogHandler_History incorrect filtering of log records + added related tests --- libmamba/include/mamba/core/logging_tools.hpp | 2 +- .../mamba/testing/test_logging_common.hpp | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/libmamba/include/mamba/core/logging_tools.hpp b/libmamba/include/mamba/core/logging_tools.hpp index 7787da1f30..1d1adf3dfc 100644 --- a/libmamba/include/mamba/core/logging_tools.hpp +++ b/libmamba/include/mamba/core/logging_tools.hpp @@ -431,7 +431,7 @@ namespace mamba::logging inline auto LogHandler_History::log(LogRecord record) -> void { assert(pimpl); - if (pimpl->current_log_level < record.level) + if (pimpl->current_log_level > record.level) { return; } diff --git a/libmamba/tests/libmamba_logging/include/mamba/testing/test_logging_common.hpp b/libmamba/tests/libmamba_logging/include/mamba/testing/test_logging_common.hpp index 572113700b..092b460044 100644 --- a/libmamba/tests/libmamba_logging/include/mamba/testing/test_logging_common.hpp +++ b/libmamba/tests/libmamba_logging/include/mamba/testing/test_logging_common.hpp @@ -97,9 +97,15 @@ namespace mamba::logging::testing stats->current_params = std::move(new_params); } - auto log(LogRecord) -> void + auto log(LogRecord record) -> void { auto stats = pimpl->stats.synchronize(); + if (stats->current_params.logging_level > record.level) + { + // we ignore logs that should be filtered + return; + } + stats->log_count++; if (stats->backtrace_size == 0) { @@ -279,6 +285,14 @@ namespace mamba::logging::testing } stats.log_count += options.log_count; stats.real_output_log_count += options.log_count; + + // log level handling + if (options.level > log_level::trace) + { + log({ .message = "this log record must be filtered out, if you read this from the log output this test has failed", + .level = log_level::trace, + .source = options.log_sources.front() }); + } } // backtrace From bb89645a3544679c872614361b6460ad9edb3cf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klaim=20=28Jo=C3=ABl=20Lamotte=29?= <142265+Klaim@users.noreply.github.com> Date: Wed, 25 Mar 2026 19:14:08 +0100 Subject: [PATCH 03/52] improved logging tests --- .../include/mamba/testing/test_logging_common.hpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libmamba/tests/libmamba_logging/include/mamba/testing/test_logging_common.hpp b/libmamba/tests/libmamba_logging/include/mamba/testing/test_logging_common.hpp index 092b460044..a3039e19d2 100644 --- a/libmamba/tests/libmamba_logging/include/mamba/testing/test_logging_common.hpp +++ b/libmamba/tests/libmamba_logging/include/mamba/testing/test_logging_common.hpp @@ -32,6 +32,7 @@ namespace mamba::logging::testing std::size_t stop_count = 0; std::size_t log_count = 0; std::size_t real_output_log_count = 0; + std::size_t filtered_out_log_count = 0; std::size_t log_level_change_count = 0; std::size_t params_change_count = 0; std::size_t backtrace_size_change_count = 0; @@ -103,6 +104,7 @@ namespace mamba::logging::testing if (stats->current_params.logging_level > record.level) { // we ignore logs that should be filtered + ++stats->filtered_out_log_count; return; } @@ -292,6 +294,7 @@ namespace mamba::logging::testing log({ .message = "this log record must be filtered out, if you read this from the log output this test has failed", .level = log_level::trace, .source = options.log_sources.front() }); + ++stats.filtered_out_log_count; } } From 58dc097a04cd8be65e86b6a82e6756eb5aa09a4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klaim=20=28Jo=C3=ABl=20Lamotte=29?= <142265+Klaim@users.noreply.github.com> Date: Wed, 25 Mar 2026 19:16:31 +0100 Subject: [PATCH 04/52] json output will contain log records passing the log level filter (>= warning by default) --- libmamba/include/mamba/core/logging_tools.hpp | 26 ++++----- libmamba/include/mamba/core/output.hpp | 1 + libmamba/src/api/configuration.cpp | 41 +++++++------- libmamba/src/core/context.cpp | 7 ++- libmamba/src/core/output.cpp | 53 +++++++++++++++++++ .../libmamba_logging/test_logging_tools.cpp | 2 +- 6 files changed, 96 insertions(+), 34 deletions(-) diff --git a/libmamba/include/mamba/core/logging_tools.hpp b/libmamba/include/mamba/core/logging_tools.hpp index 1d1adf3dfc..d9ae9c3a92 100644 --- a/libmamba/include/mamba/core/logging_tools.hpp +++ b/libmamba/include/mamba/core/logging_tools.hpp @@ -25,6 +25,19 @@ namespace mamba::logging { out.flush() }; }; + + /** Transforms a source location into a human-readable string ready for logging purpose. */ + inline auto as_log(const std::source_location& location) -> std::string + { + return fmt::format( + "{}:{}:{} {}", + location.file_name(), + location.line(), + location.column(), + location.function_name() + ); + } + namespace details { inline auto queue_push(std::deque& queue, size_t max_elements, LogRecord record) @@ -138,17 +151,6 @@ namespace mamba::logging } }; - inline auto as_log(const std::source_location& location) -> std::string - { - return fmt::format( - "{}:{}:{} {}", - location.file_name(), - location.line(), - location.column(), - location.function_name() - ); - } - struct log_to_stream_options { bool with_location = false; @@ -159,7 +161,7 @@ namespace mamba::logging -> OutputStream auto& { auto location_str = options.with_location - ? fmt::format(" ({})", details::as_log(record.location)) + ? fmt::format(" ({})", as_log(record.location)) : std::string{}; out << fmt::format( diff --git a/libmamba/include/mamba/core/output.hpp b/libmamba/include/mamba/core/output.hpp index ca4d2a600f..0b6a111970 100644 --- a/libmamba/include/mamba/core/output.hpp +++ b/libmamba/include/mamba/core/output.hpp @@ -153,6 +153,7 @@ namespace mamba void json_append(const nlohmann::json& j); void json_down(const std::string& key); void json_up(); + static void setup_log_handling_for_json(); static void print_buffer(std::ostream& ostream); diff --git a/libmamba/src/api/configuration.cpp b/libmamba/src/api/configuration.cpp index 85d8189fc8..6cb4da9e44 100644 --- a/libmamba/src/api/configuration.cpp +++ b/libmamba/src/api/configuration.cpp @@ -815,14 +815,10 @@ namespace mamba mamba::log_level log_level_fallback_hook(Configuration& config) { - const auto& ctx = config.context(); - if (ctx.output_params.json) - { - return mamba::log_level::critical; - } - else if (config.at("verbose").configured()) + if (config.at("verbose").configured()) { + const auto& ctx = config.context(); switch (ctx.output_params.verbosity) { case 0: @@ -1247,18 +1243,6 @@ namespace mamba set_configurables(); } - namespace - { - // Immediately deactivates the logging system if feature is enabled. - void stop_logging_if_enabled(const bool enabled) - { - if (enabled) - { - logging::stop_logging(); - } - } - } - void Configuration::set_configurables() { // Basic @@ -1980,7 +1964,15 @@ namespace mamba .group("Output, Prompt and Flow Control") .set_rc_configurable() .needs({ "print_config_only", "print_context_only" }) - .set_post_merge_hook(stop_logging_if_enabled) + .set_post_merge_hook( + [](const bool enabled) + { + if (enabled) + { + Console::setup_log_handling_for_json(); + } + } + ) .set_env_var_names() .description("Report all output as json")); @@ -2061,7 +2053,15 @@ namespace mamba .set_rc_configurable() .set_env_var_names() .needs({ "json", "print_config_only", "print_context_only" }) - .set_post_merge_hook(stop_logging_if_enabled) + .set_post_merge_hook( + [](const bool enabled) + { + if (enabled) + { + logging::stop_logging(); + } + } + ) .description("Set quiet mode (print less output)")); insert(Configurable("verbose", 0) @@ -2300,6 +2300,7 @@ namespace mamba if (this->at("print_config_only").value()) { + // TOOD: fix this for the case where `--json` is used int dump_opts = MAMBA_SHOW_CONFIG_VALUES | MAMBA_SHOW_CONFIG_SRCS | MAMBA_SHOW_ALL_CONFIGS; print_dump(*this, dump_opts); diff --git a/libmamba/src/core/context.cpp b/libmamba/src/core/context.cpp index 7c647dac3b..7287755a6e 100644 --- a/libmamba/src/core/context.cpp +++ b/libmamba/src/core/context.cpp @@ -109,12 +109,17 @@ namespace mamba start_logging(std::move(log_handler)); } - if (output_params.json or output_params.quiet) + if (output_params.quiet) { // Explicitly stop logging in case a log-handler // has already been installed before `Context`'s creation. logging::stop_logging(); } + if (output_params.json) + { + // We still need to capture the log and put it into the json output. + Console::setup_log_handling_for_json(); + } } Context::~Context() diff --git a/libmamba/src/core/output.cpp b/libmamba/src/core/output.cpp index 0c012a8b11..f23e366fb3 100644 --- a/libmamba/src/core/output.cpp +++ b/libmamba/src/core/output.cpp @@ -20,6 +20,7 @@ #include "mamba/core/context.hpp" #include "mamba/core/execution.hpp" +#include "mamba/core/logging_tools.hpp" #include "mamba/core/output.hpp" #include "mamba/core/tasksync.hpp" #include "mamba/core/thread_utils.hpp" @@ -274,6 +275,52 @@ namespace mamba ***********/ + namespace + { + std::unique_ptr log_history_handler; + + auto to_json(const logging::LogRecord& record) -> nlohmann::json + { + nlohmann::json result = { { "message", record.message }, + { "level", name_of(record.level) }, + { "source", name_of(record.source) } }; + if (strlen(record.location.file_name()) > 0 or strlen(record.location.function_name()) > 0) + { + result["location"] = logging::as_log(record.location); + } + return result; + } + + auto capture_log_history_as_json() -> nlohmann::json + { + nlohmann::json json_history = nlohmann::json::array(); + + if (not log_history_handler) + { + return json_history; + } + + const auto history = log_history_handler->capture_history(); + for (const auto& log_record : history) + { + json_history.push_back(to_json(log_record)); + } + return json_history; + } + } + + void Console::setup_log_handling_for_json() + { + if (not log_history_handler) + { + log_history_handler = std::make_unique( + logging::LogHandler_History::Options{ .clear_on_stop = false } + ); + } + + logging::set_log_handler(log_history_handler.get()); // use the existing logging parameters + } + using ConsoleBuffer = std::vector; class ConsoleData @@ -542,6 +589,12 @@ namespace mamba void Console::json_print() { + if (context().output_params.json and log_history_handler) + { + auto log_history = capture_log_history_as_json(); + json_write({ { "log_history", std::move(log_history) } }); + } + print(p_data->json_log.unflatten().dump(4), true); } diff --git a/libmamba/tests/libmamba_logging/test_logging_tools.cpp b/libmamba/tests/libmamba_logging/test_logging_tools.cpp index 7b07ddb6d9..e248549a50 100644 --- a/libmamba/tests/libmamba_logging/test_logging_tools.cpp +++ b/libmamba/tests/libmamba_logging/test_logging_tools.cpp @@ -175,7 +175,7 @@ namespace mamba::logging { std::stringstream out; const auto location = std::source_location::current(); - const auto location_str = fmt::format(" ({})", details::as_log(location)); + const auto location_str = fmt::format(" ({})", as_log(location)); details::log_to_stream( out, From 9ca3a20163827497853b722c917e7d1a2a216ccc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klaim=20=28Jo=C3=ABl=20Lamotte=29?= <142265+Klaim@users.noreply.github.com> Date: Thu, 26 Mar 2026 11:06:04 +0100 Subject: [PATCH 05/52] attempt to get some info from tests --- micromamba/tests/test_create.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/micromamba/tests/test_create.py b/micromamba/tests/test_create.py index ff8c48424c..9ca3fa06b9 100644 --- a/micromamba/tests/test_create.py +++ b/micromamba/tests/test_create.py @@ -376,6 +376,8 @@ def test_clone_conflicts_with_specs(tmp_home, tmp_root_prefix, tmp_path): no_dry_run=True, ) stderr = info.value.stderr.decode() + print("stderr = " + stderr) + print("stdcout = " + info.value.stdout.decode()) assert "Cannot use --clone together with package specs." in stderr From 22a1177652b989111a863a42cc28a2a60cda4101 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klaim=20=28Jo=C3=ABl=20Lamotte=29?= <142265+Klaim@users.noreply.github.com> Date: Fri, 27 Mar 2026 14:46:56 +0100 Subject: [PATCH 06/52] fixed test expecting an error in cerr, looking into json instead --- micromamba/tests/helpers.py | 7 +++++++ micromamba/tests/test_create.py | 25 +++++++++++-------------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/micromamba/tests/helpers.py b/micromamba/tests/helpers.py index e129ab1185..5fa9bffb0b 100644 --- a/micromamba/tests/helpers.py +++ b/micromamba/tests/helpers.py @@ -707,3 +707,10 @@ def assert_state_file(state_file_path: Path, expected_state: dict): assert state[field_name] == expected_value, ( f"Expected {field_name} to be {expected_value}, but got {state[field_name]}" ) + +def find_message_in_json_logs(json_result, message_to_find): + for log_record in json_result["log_history"]: + if message_to_find in log_record["message"]: + return log_record + + return None \ No newline at end of file diff --git a/micromamba/tests/test_create.py b/micromamba/tests/test_create.py index 9ca3fa06b9..074d910869 100644 --- a/micromamba/tests/test_create.py +++ b/micromamba/tests/test_create.py @@ -365,20 +365,17 @@ def test_clone_conflicts_with_specs(tmp_home, tmp_root_prefix, tmp_path): env_name = "clone-conflict-specs" helpers.create("-n", "src-env-for-conflict", "xtensor", "--json", no_dry_run=True) - with pytest.raises(subprocess.CalledProcessError) as info: - helpers.create( - "--clone", - "src-env-for-conflict", - "-n", - env_name, - "xsimd", - "--json", - no_dry_run=True, - ) - stderr = info.value.stderr.decode() - print("stderr = " + stderr) - print("stdcout = " + info.value.stdout.decode()) - assert "Cannot use --clone together with package specs." in stderr + res = helpers.create( + "--clone", + "src-env-for-conflict", + "-n", + env_name, + "xsimd", + "--json", + no_dry_run=True, + ) + + assert helpers.find_message_in_json_logs(res, "Cannot use --clone together with package specs.") != None @pytest.mark.parametrize("shared_pkgs_dirs", [True], indirect=True) From fe59cb8b94e4184c04e0e3f0f4bdec3e4e401197 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klaim=20=28Jo=C3=ABl=20Lamotte=29?= <142265+Klaim@users.noreply.github.com> Date: Mon, 30 Mar 2026 11:50:26 +0200 Subject: [PATCH 07/52] LogHandler_History allows capture with clear --- libmamba/include/mamba/core/logging_tools.hpp | 30 ++++++++++++++----- libmamba/src/core/output.cpp | 5 +++- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/libmamba/include/mamba/core/logging_tools.hpp b/libmamba/include/mamba/core/logging_tools.hpp index d9ae9c3a92..7526ad840c 100644 --- a/libmamba/include/mamba/core/logging_tools.hpp +++ b/libmamba/include/mamba/core/logging_tools.hpp @@ -25,7 +25,6 @@ namespace mamba::logging { out.flush() }; }; - /** Transforms a source location into a human-readable string ready for logging purpose. */ inline auto as_log(const std::source_location& location) -> std::string { @@ -160,9 +159,8 @@ namespace mamba::logging log_to_stream(OutputStream auto& out, const LogRecord& record, log_to_stream_options options = {}) -> OutputStream auto& { - auto location_str = options.with_location - ? fmt::format(" ({})", as_log(record.location)) - : std::string{}; + auto location_str = options.with_location ? fmt::format(" ({})", as_log(record.location)) + : std::string{}; out << fmt::format( "\n{} {}{} : {}", @@ -247,12 +245,14 @@ namespace mamba::logging //////////////////////////////////////////// // History api - thread-safe - /** @returns A copy of the current log record history. + /** Captures the currently stored sequence of `LogRecord`. + @param `and_clear` Will also clear the existing history if `true`. + @returns A copy of the current log record history. The value should be considered immediately obsolete as new log records could be pushed concurrently. The returned history will be empty if `is_started()` == false. */ - auto capture_history() const -> std::vector; + auto capture_history(bool and_clear=false) const -> std::vector; /** Clears the internal history. @@ -487,12 +487,26 @@ namespace mamba::logging // nothing to do, we keep history, there is no flush } - inline auto LogHandler_History::capture_history() const -> std::vector + inline auto LogHandler_History::capture_history(bool and_clear) const + -> std::vector { if (pimpl) { auto synched_data = pimpl->data.synchronize(); - return std::vector(synched_data->history.begin(), synched_data->history.end()); + if (and_clear) + { + std::vector result(synched_data->history.size()); + std::ranges::move(synched_data->history, result.begin()); + synched_data->history.clear(); + return result; + } + else + { + return std::vector( + synched_data->history.begin(), + synched_data->history.end() + ); + } } return {}; diff --git a/libmamba/src/core/output.cpp b/libmamba/src/core/output.cpp index f23e366fb3..244dc56503 100644 --- a/libmamba/src/core/output.cpp +++ b/libmamba/src/core/output.cpp @@ -300,7 +300,10 @@ namespace mamba return json_history; } - const auto history = log_history_handler->capture_history(); + // We clear the history on the way in case this function is called + // more than once in the program's execution, in which case the + // history will be accumulated here. + const auto history = log_history_handler->capture_history(true); for (const auto& log_record : history) { json_history.push_back(to_json(log_record)); From 5515fb1bc759d4050a40c38a6e1a432da8988a28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klaim=20=28Jo=C3=ABl=20Lamotte=29?= <142265+Klaim@users.noreply.github.com> Date: Mon, 30 Mar 2026 11:51:56 +0200 Subject: [PATCH 08/52] output a potentially empty json if `--json` is used even if no log history --- libmamba/src/core/output.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/libmamba/src/core/output.cpp b/libmamba/src/core/output.cpp index 244dc56503..eb7c882763 100644 --- a/libmamba/src/core/output.cpp +++ b/libmamba/src/core/output.cpp @@ -293,7 +293,7 @@ namespace mamba auto capture_log_history_as_json() -> nlohmann::json { - nlohmann::json json_history = nlohmann::json::array(); + nlohmann::json json_history = nlohmann::json::array({}); if (not log_history_handler) { @@ -379,7 +379,9 @@ namespace mamba Console::~Console() { - if (!p_data->is_json_print_cancelled && !p_data->json_log.is_null()) + // Note: even if the json is empty, we should still print + // and empty json object as long as `--json` is used. + if (context().output_params.json and not p_data->is_json_print_cancelled) { this->json_print(); } From f529c23e09cc99412a66cfae027ff2f73d13b5a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klaim=20=28Jo=C3=ABl=20Lamotte=29?= <142265+Klaim@users.noreply.github.com> Date: Mon, 30 Mar 2026 11:52:48 +0200 Subject: [PATCH 09/52] console must capture and output json after error logging --- libmamba/include/mamba/core/logging_tools.hpp | 5 ++--- micromamba/src/main.cpp | 10 ++++++++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/libmamba/include/mamba/core/logging_tools.hpp b/libmamba/include/mamba/core/logging_tools.hpp index 7526ad840c..9422f8d852 100644 --- a/libmamba/include/mamba/core/logging_tools.hpp +++ b/libmamba/include/mamba/core/logging_tools.hpp @@ -252,7 +252,7 @@ namespace mamba::logging as new log records could be pushed concurrently. The returned history will be empty if `is_started()` == false. */ - auto capture_history(bool and_clear=false) const -> std::vector; + auto capture_history(bool and_clear = false) const -> std::vector; /** Clears the internal history. @@ -487,8 +487,7 @@ namespace mamba::logging // nothing to do, we keep history, there is no flush } - inline auto LogHandler_History::capture_history(bool and_clear) const - -> std::vector + inline auto LogHandler_History::capture_history(bool and_clear) const -> std::vector { if (pimpl) { diff --git a/micromamba/src/main.cpp b/micromamba/src/main.cpp index 98430837d3..a97e1d5c26 100644 --- a/micromamba/src/main.cpp +++ b/micromamba/src/main.cpp @@ -129,8 +129,8 @@ main(int argc, char** argv) if (is_interruption) { - reset_console(); LOG_WARNING << e.what(); + reset_console(); return 0; } else @@ -143,11 +143,17 @@ main(int argc, char** argv) handle_exception(e); } + if (error_to_report) + { + // we need the report to be done before resetting the console + // otherwise we might not capture the errors in the json output + LOG_CRITICAL << error_to_report.value(); + } + reset_console(); if (error_to_report) { - LOG_CRITICAL << error_to_report.value(); return 1; // TODO: consider returning EXIT_FAILURE } From dee6f123ca9fd3112361ce6a6122008677cf2108 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klaim=20=28Jo=C3=ABl=20Lamotte=29?= <142265+Klaim@users.noreply.github.com> Date: Mon, 30 Mar 2026 11:57:53 +0200 Subject: [PATCH 10/52] (micro)mamba always reset the console whatever the way it returns --- micromamba/src/main.cpp | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/micromamba/src/main.cpp b/micromamba/src/main.cpp index a97e1d5c26..d602abc687 100644 --- a/micromamba/src/main.cpp +++ b/micromamba/src/main.cpp @@ -44,6 +44,7 @@ main(int argc, char** argv) mamba::Configuration config{ ctx }; init_console(); + mamba::on_scope_exit _console_reset{ [] { reset_console(); } }; ctx.command_params.is_mamba_exe = true; @@ -74,7 +75,6 @@ main(int argc, char** argv) if (argc >= 2 && strcmp(argv[1], "completer") == 0) { get_completions(&app, config, argc, utf8argv); - reset_console(); return 0; } @@ -130,7 +130,6 @@ main(int argc, char** argv) if (is_interruption) { LOG_WARNING << e.what(); - reset_console(); return 0; } else @@ -145,15 +144,7 @@ main(int argc, char** argv) if (error_to_report) { - // we need the report to be done before resetting the console - // otherwise we might not capture the errors in the json output LOG_CRITICAL << error_to_report.value(); - } - - reset_console(); - - if (error_to_report) - { return 1; // TODO: consider returning EXIT_FAILURE } From 4f3db356925258d3ed4b1ed6e716c0bb1feb4745 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klaim=20=28Jo=C3=ABl=20Lamotte=29?= <142265+Klaim@users.noreply.github.com> Date: Mon, 30 Mar 2026 11:58:43 +0200 Subject: [PATCH 11/52] tests: read json from process error (attempt) --- micromamba/tests/test_create.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/micromamba/tests/test_create.py b/micromamba/tests/test_create.py index 074d910869..5f4b02175e 100644 --- a/micromamba/tests/test_create.py +++ b/micromamba/tests/test_create.py @@ -365,17 +365,21 @@ def test_clone_conflicts_with_specs(tmp_home, tmp_root_prefix, tmp_path): env_name = "clone-conflict-specs" helpers.create("-n", "src-env-for-conflict", "xtensor", "--json", no_dry_run=True) - res = helpers.create( - "--clone", - "src-env-for-conflict", - "-n", - env_name, - "xsimd", - "--json", - no_dry_run=True, - ) + # We expect this run to fail + with pytest.raises(subprocess.CalledProcessError) as info: + helpers.create( + "--clone", + "src-env-for-conflict", + "-n", + env_name, + "xsimd", + "--json", + no_dry_run=True, + ) + + json_output = json.loads(info.output.decode()) - assert helpers.find_message_in_json_logs(res, "Cannot use --clone together with package specs.") != None + assert helpers.find_message_in_json_logs(json_output, "Cannot use --clone together with package specs.") != None @pytest.mark.parametrize("shared_pkgs_dirs", [True], indirect=True) From e62c40e030349841fffabd9f96007eb51c683b65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klaim=20=28Jo=C3=ABl=20Lamotte=29?= <142265+Klaim@users.noreply.github.com> Date: Mon, 30 Mar 2026 14:19:38 +0200 Subject: [PATCH 12/52] do not output Console's JSON when command already outputs JSON or YAML --- libmamba/src/api/config.cpp | 2 ++ libmamba/src/api/configuration.cpp | 1 + 2 files changed, 3 insertions(+) diff --git a/libmamba/src/api/config.cpp b/libmamba/src/api/config.cpp index f7b2f98f35..47a53869e6 100644 --- a/libmamba/src/api/config.cpp +++ b/libmamba/src/api/config.cpp @@ -31,6 +31,7 @@ namespace mamba auto specs = config.at("specs").value>(); int dump_opts = MAMBA_SHOW_CONFIG_DESCS | show_long_desc | show_group; + Console::instance().cancel_json_print(); // we will output json or yaml already print_dump(config, dump_opts, specs); config.operation_teardown(); @@ -64,6 +65,7 @@ namespace mamba int dump_opts = MAMBA_SHOW_CONFIG_VALUES | show_sources | show_desc | show_long_desc | show_group | show_all_rcs | show_all; + Console::instance().cancel_json_print(); // we will output json or yaml already print_dump(config, dump_opts, specs); config.operation_teardown(); diff --git a/libmamba/src/api/configuration.cpp b/libmamba/src/api/configuration.cpp index 6cb4da9e44..1201616f01 100644 --- a/libmamba/src/api/configuration.cpp +++ b/libmamba/src/api/configuration.cpp @@ -2303,6 +2303,7 @@ namespace mamba // TOOD: fix this for the case where `--json` is used int dump_opts = MAMBA_SHOW_CONFIG_VALUES | MAMBA_SHOW_CONFIG_SRCS | MAMBA_SHOW_ALL_CONFIGS; + Console::instance().cancel_json_print(); // we will output json or yaml already print_dump(*this, dump_opts); exit(0); } From 5596d037ab93ae21d22146bf5fadee27705fcd85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klaim=20=28Jo=C3=ABl=20Lamotte=29?= <142265+Klaim@users.noreply.github.com> Date: Mon, 30 Mar 2026 14:51:26 +0200 Subject: [PATCH 13/52] `--json` and `--quiet` are specially handled before creating the context to avoid error output when `--json` is used --- libmamba/include/mamba/core/context.hpp | 15 ++++--- libmamba/src/core/context.cpp | 5 +++ micromamba/src/main.cpp | 53 ++++++++++++++++++++++--- 3 files changed, 61 insertions(+), 12 deletions(-) diff --git a/libmamba/include/mamba/core/context.hpp b/libmamba/include/mamba/core/context.hpp index af73c3fa3c..5f586003c8 100644 --- a/libmamba/include/mamba/core/context.hpp +++ b/libmamba/include/mamba/core/context.hpp @@ -65,10 +65,18 @@ namespace mamba class Logger; class Context; + struct OutputParams : LoggingParams + { + bool json{ false }; + bool quiet{ false }; + int verbosity{ 0 }; + }; + struct ContextOptions { bool enable_logging = false; bool enable_signal_handling = false; + std::optional output_params; }; // Context singleton class @@ -78,12 +86,7 @@ namespace mamba static void use_default_signal_handler(bool val); - struct OutputParams : LoggingParams - { - bool json{ false }; - bool quiet{ false }; - int verbosity{ 0 }; - }; + using OutputParams = mamba::OutputParams; struct GraphicsParams { diff --git a/libmamba/src/core/context.cpp b/libmamba/src/core/context.cpp index 7287755a6e..4063bded82 100644 --- a/libmamba/src/core/context.cpp +++ b/libmamba/src/core/context.cpp @@ -103,6 +103,11 @@ namespace mamba enable_signal_handling(); } + if (options.output_params) + { + output_params = *options.output_params; + } + // Do not start logging if `--json` or `--quiet` are used. if (options.enable_logging and not (output_params.json or output_params.quiet)) { diff --git a/micromamba/src/main.cpp b/micromamba/src/main.cpp index d602abc687..1013f98bc5 100644 --- a/micromamba/src/main.cpp +++ b/micromamba/src/main.cpp @@ -31,15 +31,54 @@ using namespace mamba; // NOLINT(build/namespaces) +auto +decide_preconfig_context_options(int argc, char** argv) -> mamba::ContextOptions +{ + using namespace std::literals; + + mamba::ContextOptions options{ + .enable_logging = true, + .enable_signal_handling = true, + }; + + mamba::OutputParams output_params; + for (const char* arg : std::ranges::subrange(argv, argv + argc)) + { + if (arg == "--json"sv) + { + output_params.json = true; + } + else if (arg == "--quiet"sv) + { + output_params.quiet = true; + } + } + + if (output_params.json or output_params.quiet) + { + options.output_params = output_params; + } + + return options; +} + +auto +decide_log_handler(const ContextOptions& options) -> mamba::logging::AnyLogHandler +{ + if (options.output_params and (options.output_params->json or options.output_params->quiet)) + { + return {}; + } + + return mamba::logging::spdlogimpl::LogHandler_spdlog{}; +} + int main(int argc, char** argv) { mamba::MainExecutor scoped_threads; - mamba::Context ctx{ { - .enable_logging = true, - .enable_signal_handling = true, - }, - mamba::logging::spdlogimpl::LogHandler_spdlog{} }; + const auto pre_config_options = decide_preconfig_context_options(argc, argv); + mamba::Context ctx{ pre_config_options, decide_log_handler(pre_config_options) }; mamba::Console console{ ctx }; mamba::Configuration config{ ctx }; @@ -98,7 +137,9 @@ main(int argc, char** argv) try { - CLI11_PARSE(app, argc, utf8argv); + // Note: do not use CLI11_PARSE macro as it's error handling + // would bypass ours. + app.parse(argc, utf8argv); if (app.get_subcommands().size() == 0) { config.load(); From 91c65a8a59168c0e71356c7ffcc537a93945fb88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klaim=20=28Jo=C3=ABl=20Lamotte=29?= <142265+Klaim@users.noreply.github.com> Date: Mon, 30 Mar 2026 15:19:43 +0200 Subject: [PATCH 14/52] keep "run --help" message when arguments parsing failed --- micromamba/src/main.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/micromamba/src/main.cpp b/micromamba/src/main.cpp index 1013f98bc5..4bf7bfce56 100644 --- a/micromamba/src/main.cpp +++ b/micromamba/src/main.cpp @@ -129,9 +129,11 @@ main(int argc, char** argv) ctx.command_params.current_command = full_command.str(); std::optional error_to_report; - auto handle_exception = [&](auto& e) + auto handle_exception = [&](auto& e, const auto&... additional_messages) { - error_to_report = e.what(); + using namespace std::literals; + error_to_report.emplace(e.what()); + (error_to_report->append(additional_messages), ...); set_sig_interrupted(); }; @@ -178,6 +180,10 @@ main(int argc, char** argv) handle_exception(e); } } + catch (const CLI::Error& e) + { + handle_exception(e, "\nRun with --help for more information."); + } catch (const std::exception& e) { handle_exception(e); From 2f7c9ae081917e928486c750fb7b3d58601b06e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klaim=20=28Jo=C3=ABl=20Lamotte=29?= <142265+Klaim@users.noreply.github.com> Date: Mon, 30 Mar 2026 15:30:18 +0200 Subject: [PATCH 15/52] workaround compilers incorrectly requiring explicit member initialization --- libmamba/include/mamba/core/context.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libmamba/include/mamba/core/context.hpp b/libmamba/include/mamba/core/context.hpp index 5f586003c8..563d692c28 100644 --- a/libmamba/include/mamba/core/context.hpp +++ b/libmamba/include/mamba/core/context.hpp @@ -76,7 +76,7 @@ namespace mamba { bool enable_logging = false; bool enable_signal_handling = false; - std::optional output_params; + std::optional output_params = {}; }; // Context singleton class From 808be208111721da8b1157d48fde73b712b8ad41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klaim=20=28Jo=C3=ABl=20Lamotte=29?= <142265+Klaim@users.noreply.github.com> Date: Tue, 31 Mar 2026 17:02:20 +0200 Subject: [PATCH 16/52] config sub-commands outputing json are merged with the normal json output (when using --json) --- libmamba/src/api/config.cpp | 6 ++---- libmamba/src/api/configuration.cpp | 17 +++++++++++++---- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/libmamba/src/api/config.cpp b/libmamba/src/api/config.cpp index 47a53869e6..58485a0a92 100644 --- a/libmamba/src/api/config.cpp +++ b/libmamba/src/api/config.cpp @@ -31,8 +31,7 @@ namespace mamba auto specs = config.at("specs").value>(); int dump_opts = MAMBA_SHOW_CONFIG_DESCS | show_long_desc | show_group; - Console::instance().cancel_json_print(); // we will output json or yaml already - print_dump(config, dump_opts, specs); + print_dump(config, dump_opts, std::move(specs)); config.operation_teardown(); } @@ -65,8 +64,7 @@ namespace mamba int dump_opts = MAMBA_SHOW_CONFIG_VALUES | show_sources | show_desc | show_long_desc | show_group | show_all_rcs | show_all; - Console::instance().cancel_json_print(); // we will output json or yaml already - print_dump(config, dump_opts, specs); + print_dump(config, dump_opts, std::move(specs)); config.operation_teardown(); } diff --git a/libmamba/src/api/configuration.cpp b/libmamba/src/api/configuration.cpp index 1201616f01..448f3dbe55 100644 --- a/libmamba/src/api/configuration.cpp +++ b/libmamba/src/api/configuration.cpp @@ -2303,7 +2303,6 @@ namespace mamba // TOOD: fix this for the case where `--json` is used int dump_opts = MAMBA_SHOW_CONFIG_VALUES | MAMBA_SHOW_CONFIG_SRCS | MAMBA_SHOW_ALL_CONFIGS; - Console::instance().cancel_json_print(); // we will output json or yaml already print_dump(*this, dump_opts); exit(0); } @@ -2790,9 +2789,19 @@ namespace mamba void print_dump(const Configuration& config, int dump_opts, std::vector dump_names) { - // Note: this function is intended to get more complex with incoming changes and need to be - // isolated in preparation for these changes. const std::string dump_text = hide_secrets(config.dump(dump_opts, std::move(dump_names))); - std::cout << dump_text << std::endl; + if (config.context().output_params.json) + { + // merge the output with existing json output + auto dump_json = nlohmann::json::parse(dump_text); + Console::instance().json_write(dump_json); + } + else + { + // FIXME: Console's json should be output here too, mixed in some way + Console::instance().cancel_json_print(); + std::cout << dump_text << std::endl; // output YAML + } } + } From 1e752c7475ce4ad4fa20711def2b4d4da874ef18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klaim=20=28Jo=C3=ABl=20Lamotte=29?= <142265+Klaim@users.noreply.github.com> Date: Tue, 31 Mar 2026 17:09:07 +0200 Subject: [PATCH 17/52] removed incorrect code and comment --- libmamba/src/api/configuration.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/libmamba/src/api/configuration.cpp b/libmamba/src/api/configuration.cpp index 448f3dbe55..40f8b45757 100644 --- a/libmamba/src/api/configuration.cpp +++ b/libmamba/src/api/configuration.cpp @@ -2798,9 +2798,7 @@ namespace mamba } else { - // FIXME: Console's json should be output here too, mixed in some way - Console::instance().cancel_json_print(); - std::cout << dump_text << std::endl; // output YAML + std::cout << dump_text << std::endl; // outputs YAML } } From ce45264cb5411b1b6c5e4c916f1b05d022f517a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klaim=20=28Jo=C3=ABl=20Lamotte=29?= <142265+Klaim@users.noreply.github.com> Date: Tue, 31 Mar 2026 18:42:44 +0200 Subject: [PATCH 18/52] let cli11 handle it's ownt errors but output where we want it to depending on json or not --- micromamba/src/main.cpp | 34 ++++++++++++++++++++++++++++++---- micromamba/src/umamba.cpp | 1 + 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/micromamba/src/main.cpp b/micromamba/src/main.cpp index 4bf7bfce56..d72a0577a6 100644 --- a/micromamba/src/main.cpp +++ b/micromamba/src/main.cpp @@ -15,6 +15,8 @@ #include "mamba/util/os_win.hpp" #endif +#include + #include #include "mamba/api/configuration.hpp" @@ -58,7 +60,7 @@ decide_preconfig_context_options(int argc, char** argv) -> mamba::ContextOptions { options.output_params = output_params; } - + return options; } @@ -137,6 +139,8 @@ main(int argc, char** argv) set_sig_interrupted(); }; + int return_value = EXIT_SUCCESS; + try { // Note: do not use CLI11_PARSE macro as it's error handling @@ -182,7 +186,29 @@ main(int argc, char** argv) } catch (const CLI::Error& e) { - handle_exception(e, "\nRun with --help for more information."); + using namespace std::literals; + // We only preserve CLI11 output behavior when errors from CLI11 + // occurs because of `--help` or `--version` is used. Otherwise we follow the + // logic that `--json` outputs everything as JSON. + static constexpr std::array non_error_request_names = { "CallForHelp"sv, + "CallForAllHelp"sv, + "CallForVersion"sv }; + const bool is_non_error_request = std::ranges::find(non_error_request_names, e.get_name()) + != non_error_request_names.end(); + + if (ctx.output_params.json and not is_non_error_request) + { + // we want the output to end up in the json log history + std::stringstream output; + return_value = app.exit(e, output, output); + LOG_WARNING << output.str(); + } + else + { + // we don't want any json output even if requested, CLI11 will handle this + console.cancel_json_print(); + return_value = app.exit(e); + } } catch (const std::exception& e) { @@ -192,8 +218,8 @@ main(int argc, char** argv) if (error_to_report) { LOG_CRITICAL << error_to_report.value(); - return 1; // TODO: consider returning EXIT_FAILURE + return_value = EXIT_FAILURE; } - return 0; + return return_value; } diff --git a/micromamba/src/umamba.cpp b/micromamba/src/umamba.cpp index d1ae4753ff..0b92c0552c 100644 --- a/micromamba/src/umamba.cpp +++ b/micromamba/src/umamba.cpp @@ -33,6 +33,7 @@ set_umamba_command(CLI::App* com, mamba::Configuration& config) auto print_version = [](int /*count*/) { + // TODO: if `--json` is used, output there instead std::cout << umamba::version() << std::endl; exit(0); }; From ee538badd0f3583b90256ffb4c0a95f9d548de49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klaim=20=28Jo=C3=ABl=20Lamotte=29?= <142265+Klaim@users.noreply.github.com> Date: Tue, 7 Apr 2026 17:11:52 +0200 Subject: [PATCH 19/52] fix test json output erorr checks --- micromamba/tests/test_create.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/micromamba/tests/test_create.py b/micromamba/tests/test_create.py index 5f4b02175e..2d37ab8b38 100644 --- a/micromamba/tests/test_create.py +++ b/micromamba/tests/test_create.py @@ -377,7 +377,7 @@ def test_clone_conflicts_with_specs(tmp_home, tmp_root_prefix, tmp_path): no_dry_run=True, ) - json_output = json.loads(info.output.decode()) + json_output = json.loads(info.stdout.decode()) assert helpers.find_message_in_json_logs(json_output, "Cannot use --clone together with package specs.") != None @@ -401,9 +401,9 @@ def test_clone_conflicts_with_file(tmp_home, tmp_root_prefix, tmp_path): "--json", no_dry_run=True, ) - stderr = info.value.stderr.decode() + json_output = json.loads(info.stdout.decode()) - assert "Cannot use --clone together with package specs." in stderr + assert helpers.find_message_in_json_logs(json_output, "Cannot use --clone together with package specs.") != None @pytest.mark.parametrize("shared_pkgs_dirs", [True], indirect=True) @@ -415,15 +415,15 @@ def test_clone_non_existing_source(tmp_home, tmp_root_prefix, tmp_path): helpers.create( "--clone", "this-env-does-not-exist", "-n", env_name, "--json", no_dry_run=True ) - stderr = info.value.stderr.decode() - assert "Could not find environment to clone: this-env-does-not-exist" in stderr + json_output = json.loads(info.stdout.decode()) + assert helpers.find_message_in_json_logs(json_output, "Could not find environment to clone: this-env-does-not-exist") != None # Non-existing prefix path non_existing_prefix = tmp_path / "does-not-exist" with pytest.raises(subprocess.CalledProcessError) as info2: helpers.create("--clone", non_existing_prefix, "-n", env_name, "--json", no_dry_run=True) - stderr2 = info2.value.stderr.decode() - assert f"Source prefix '{non_existing_prefix}" in stderr2 + json_output2 = json.loads(info2.stdout.decode()) + assert helpers.find_message_in_json_logs(json_output2, f"Source prefix '{non_existing_prefix}") != None @pytest.mark.skipif( From 63ed7bdd757c5ea975de08a975a81fa2f2efcc26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klaim=20=28Jo=C3=ABl=20Lamotte=29?= <142265+Klaim@users.noreply.github.com> Date: Wed, 8 Apr 2026 14:47:21 +0200 Subject: [PATCH 20/52] fixup --- micromamba/tests/test_create.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/micromamba/tests/test_create.py b/micromamba/tests/test_create.py index 2d37ab8b38..b282d20cf9 100644 --- a/micromamba/tests/test_create.py +++ b/micromamba/tests/test_create.py @@ -376,8 +376,7 @@ def test_clone_conflicts_with_specs(tmp_home, tmp_root_prefix, tmp_path): "--json", no_dry_run=True, ) - - json_output = json.loads(info.stdout.decode()) + json_output = json.loads(info.value.stdout.decode()) assert helpers.find_message_in_json_logs(json_output, "Cannot use --clone together with package specs.") != None @@ -401,7 +400,7 @@ def test_clone_conflicts_with_file(tmp_home, tmp_root_prefix, tmp_path): "--json", no_dry_run=True, ) - json_output = json.loads(info.stdout.decode()) + json_output = json.loads(info.value.stdout.decode()) assert helpers.find_message_in_json_logs(json_output, "Cannot use --clone together with package specs.") != None @@ -415,14 +414,14 @@ def test_clone_non_existing_source(tmp_home, tmp_root_prefix, tmp_path): helpers.create( "--clone", "this-env-does-not-exist", "-n", env_name, "--json", no_dry_run=True ) - json_output = json.loads(info.stdout.decode()) + json_output = json.loads(info.value.stdout.decode()) assert helpers.find_message_in_json_logs(json_output, "Could not find environment to clone: this-env-does-not-exist") != None # Non-existing prefix path non_existing_prefix = tmp_path / "does-not-exist" with pytest.raises(subprocess.CalledProcessError) as info2: helpers.create("--clone", non_existing_prefix, "-n", env_name, "--json", no_dry_run=True) - json_output2 = json.loads(info2.stdout.decode()) + json_output2 = json.loads(info2.value.stdout.decode()) assert helpers.find_message_in_json_logs(json_output2, f"Source prefix '{non_existing_prefix}") != None From 3fc7d6925520495b80c8f07ecd1d8f896ead2767 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klaim=20=28Jo=C3=ABl=20Lamotte=29?= <142265+Klaim@users.noreply.github.com> Date: Wed, 8 Apr 2026 17:59:49 +0200 Subject: [PATCH 21/52] partial text fix --- micromamba/tests/test_shell.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/micromamba/tests/test_shell.py b/micromamba/tests/test_shell.py index 24c46b4341..0e96004cf1 100644 --- a/micromamba/tests/test_shell.py +++ b/micromamba/tests/test_shell.py @@ -56,7 +56,7 @@ def test_hook(tmp_home, tmp_root_prefix, shell_type): assert res.count(mamba_exe_posix) == 0 res = helpers.shell("hook", "-s", shell_type, "--json") - expected_keys = {"success", "operation", "context", "actions"} + expected_keys = {"success", "operation", "context", "actions", "log_history"} assert set(res.keys()) == expected_keys assert res["success"] @@ -118,7 +118,7 @@ def custom_shell(shell): res = custom_shell(shell_type) - expected_keys = {"success", "operation", "context", "actions"} + expected_keys = {"success", "operation", "context", "actions", "log_history"} assert set(res.keys()) == expected_keys assert res["success"] From cfeadc94c27915c1fdac50c2b44f8f8aac5de233 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klaim=20=28Jo=C3=ABl=20Lamotte=29?= <142265+Klaim@users.noreply.github.com> Date: Mon, 13 Apr 2026 17:16:22 +0200 Subject: [PATCH 22/52] list sub-command's array of packages is now part of the returned json object when `--json` is used --- libmamba/src/api/list.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libmamba/src/api/list.cpp b/libmamba/src/api/list.cpp index fc45a75148..959c181011 100644 --- a/libmamba/src/api/list.cpp +++ b/libmamba/src/api/list.cpp @@ -205,7 +205,7 @@ namespace mamba } } } - std::cout << jout.dump(4) << std::endl; + Console::instance().json_write({ { "packages", jout } }); } else { From b6736ee584d40493454ae599c985024e1c01b0ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klaim=20=28Jo=C3=ABl=20Lamotte=29?= <142265+Klaim@users.noreply.github.com> Date: Tue, 14 Apr 2026 17:20:00 +0200 Subject: [PATCH 23/52] fixed: tools log-handlers not properly filtering log records --- libmamba/include/mamba/core/logging_tools.hpp | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/libmamba/include/mamba/core/logging_tools.hpp b/libmamba/include/mamba/core/logging_tools.hpp index 9422f8d852..bc1dbc3e1c 100644 --- a/libmamba/include/mamba/core/logging_tools.hpp +++ b/libmamba/include/mamba/core/logging_tools.hpp @@ -49,6 +49,21 @@ namespace mamba::logging } } + inline auto should_be_ignored(const LogRecord& record, log_level current_level) -> bool + { + switch (current_level) + { + case log_level::off: + return true; + + case log_level::all: + return false; + + default: + return current_level > record.level; + } + } + /** Backtrace feature implementation in it's most basic form. This is the simplest implementation for a backtrace feature @@ -433,7 +448,7 @@ namespace mamba::logging inline auto LogHandler_History::log(LogRecord record) -> void { assert(pimpl); - if (pimpl->current_log_level > record.level) + if (details::should_be_ignored(record, pimpl->current_log_level)) { return; } @@ -603,7 +618,7 @@ namespace mamba::logging assert(out); assert(pimpl); - if (pimpl->current_log_level > record.level) + if (details::should_be_ignored(record, pimpl->current_log_level)) { return; } From fafe861414cced52659a8ab5efaeedc3c1dbb938 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klaim=20=28Jo=C3=ABl=20Lamotte=29?= <142265+Klaim@users.noreply.github.com> Date: Wed, 15 Apr 2026 15:43:15 +0200 Subject: [PATCH 24/52] adapt integration tests `list` calls assuming the result is a package list --- micromamba/tests/helpers.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/micromamba/tests/helpers.py b/micromamba/tests/helpers.py index 5fa9bffb0b..50dfcb6f70 100644 --- a/micromamba/tests/helpers.py +++ b/micromamba/tests/helpers.py @@ -315,7 +315,15 @@ def run_env(*args, f=None, **kwargs): return res.decode() -def umamba_list(*args, **kwargs): +def pkgs_list_from_json_result(json_result): + assert json_result + packages = json_result["packages"] + # TODO: consider if we check here if there was warnings or errors, + # maybe driven by a parameter + return packages + + +def umamba_list(*args, json_as_pkgs_list=True, **kwargs): umamba = get_umamba() cmd = [umamba, "list"] + [str(arg) for arg in args if arg] @@ -323,7 +331,12 @@ def umamba_list(*args, **kwargs): if "--json" in args: j = json.loads(res) - return j + if json_as_pkgs_list: + # TODO: consider checking here that there is no errors + # otherwise any log is lost beyond this point + return pkgs_list_from_json_result(j) + else: + return j return res.decode() @@ -713,4 +726,5 @@ def find_message_in_json_logs(json_result, message_to_find): if message_to_find in log_record["message"]: return log_record - return None \ No newline at end of file + return None + From b3b511008eba7f6d607493bc8b86144766a4a9c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klaim=20=28Jo=C3=ABl=20Lamotte=29?= <142265+Klaim@users.noreply.github.com> Date: Fri, 17 Apr 2026 12:24:15 +0200 Subject: [PATCH 25/52] fixed: `mamba create --dry-run --json` output 2 json objects --- libmamba/src/api/create.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libmamba/src/api/create.cpp b/libmamba/src/api/create.cpp index d2ad8c0217..f7aa4e3b44 100644 --- a/libmamba/src/api/create.cpp +++ b/libmamba/src/api/create.cpp @@ -230,7 +230,7 @@ namespace mamba output["dry_run"] = true; output["prefix"] = ctx.prefix_params.target_prefix; output["success"] = true; - std::cout << output.dump(2) << std::endl; + Console::instance().json_write(output); return; } } From 139beecffff618518dd22dbddd28668ed95c2815 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klaim=20=28Jo=C3=ABl=20Lamotte=29?= <142265+Klaim@users.noreply.github.com> Date: Fri, 17 Apr 2026 12:25:09 +0200 Subject: [PATCH 26/52] fixed: `list` subcommand tests guaranteed to return an array even if empty (null in json because of json flattening in mamba) --- libmamba/src/core/output.cpp | 4 ++-- micromamba/tests/helpers.py | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/libmamba/src/core/output.cpp b/libmamba/src/core/output.cpp index eb7c882763..e04179df24 100644 --- a/libmamba/src/core/output.cpp +++ b/libmamba/src/core/output.cpp @@ -609,7 +609,7 @@ namespace mamba { if (context().output_params.json) { - nlohmann::json tmp = j.flatten(); + nlohmann::json tmp = j.flatten(); // FIXME: flattening makes empty arrays 'null' for (auto it = tmp.begin(); it != tmp.end(); ++it) { p_data->json_log[p_data->json_hier + it.key()] = it.value(); @@ -632,7 +632,7 @@ namespace mamba { if (context().output_params.json) { - nlohmann::json tmp = j.flatten(); + nlohmann::json tmp = j.flatten(); // FIXME: flattening makes empty arrays 'null' for (auto it = tmp.begin(); it != tmp.end(); ++it) { p_data->json_log[p_data->json_hier + '/' + std::to_string(p_data->json_index) + it.key()] = it.value(); diff --git a/micromamba/tests/helpers.py b/micromamba/tests/helpers.py index 50dfcb6f70..a13e972b08 100644 --- a/micromamba/tests/helpers.py +++ b/micromamba/tests/helpers.py @@ -334,7 +334,10 @@ def umamba_list(*args, json_as_pkgs_list=True, **kwargs): if json_as_pkgs_list: # TODO: consider checking here that there is no errors # otherwise any log is lost beyond this point - return pkgs_list_from_json_result(j) + packages = pkgs_list_from_json_result(j) + # empty list are currently set to null because of json flattening + # in libmamba, so we need to translate null as empty list instead + return packages if packages != None else list() else: return j From 7eeed5a45edbf033427a5f3ba6ccd8525405c79b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klaim=20=28Jo=C3=ABl=20Lamotte=29?= <142265+Klaim@users.noreply.github.com> Date: Fri, 17 Apr 2026 12:28:57 +0200 Subject: [PATCH 27/52] fixed: `mamba env list --json` outputs 2 json objects --- libmamba/src/api/env.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libmamba/src/api/env.cpp b/libmamba/src/api/env.cpp index efea14d3c8..0a6c4a84b7 100644 --- a/libmamba/src/api/env.cpp +++ b/libmamba/src/api/env.cpp @@ -60,7 +60,7 @@ namespace mamba [](const mamba::fs::u8path& path) { return path.string(); } ); res["envs"] = envs; - std::cout << res.dump(4) << std::endl; + Console::instance().json_write(res); return; } From 30de13b25c18a58f465434ef91ea08739c304b47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klaim=20=28Jo=C3=ABl=20Lamotte=29?= <142265+Klaim@users.noreply.github.com> Date: Fri, 17 Apr 2026 12:56:01 +0200 Subject: [PATCH 28/52] fixed: `mamba env export|list --json` outputing 2 json objects --- libmamba/src/api/env.cpp | 2 +- micromamba/src/env.cpp | 22 +++++++++++++--------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/libmamba/src/api/env.cpp b/libmamba/src/api/env.cpp index 0a6c4a84b7..2f886d5018 100644 --- a/libmamba/src/api/env.cpp +++ b/libmamba/src/api/env.cpp @@ -182,7 +182,7 @@ namespace mamba { nlohmann::ordered_json j; j["env_vars"] = env_vars; - std::cout << j.dump(4) << std::endl; + Console::instance().json_write(j); return; } diff --git a/micromamba/src/env.cpp b/micromamba/src/env.cpp index a91a99074d..3cc5a56dd1 100644 --- a/micromamba/src/env.cpp +++ b/micromamba/src/env.cpp @@ -13,6 +13,7 @@ #include "mamba/api/update.hpp" #include "mamba/core/channel_context.hpp" #include "mamba/core/environments_manager.hpp" +#include "mamba/core/output.hpp" #include "mamba/core/prefix_data.hpp" #include "mamba/core/util.hpp" #include "mamba/specs/conda_url.hpp" @@ -78,6 +79,7 @@ set_env_command(CLI::App* com, mamba::Configuration& config) // Raise a warning if `--json` and `--explicit` are used together. if (json_format && explicit_format) { + // FIXME? std::cerr << "Warning: `--json` and `--explicit` are used together but are incompatible. The `--json` flag will be ignored." << std::endl; } @@ -192,26 +194,28 @@ set_env_command(CLI::App* com, mamba::Configuration& config) dependencies << (first_dependency_printed ? "\n" : ""); - std::cout << "{\n"; + std::stringstream out; + out << "{\n"; - std::cout << " \"channels\": [\n"; + out << " \"channels\": [\n"; for (auto channel_it = channels.begin(); channel_it != channels.end(); ++channel_it) { auto last_channel = std::next(channel_it) == channels.end(); - std::cout << " \"" << *channel_it << "\"" << (last_channel ? "" : ",") << "\n"; + out << " \"" << *channel_it << "\"" << (last_channel ? "" : ",") << "\n"; } - std::cout << " ],\n"; + out << " ],\n"; - std::cout << " \"dependencies\": [\n" << dependencies.str() << " ],\n"; + out << " \"dependencies\": [\n" << dependencies.str() << " ],\n"; - std::cout << " \"name\": \"" + out << " \"name\": \"" << mamba::detail::get_env_name(ctx, ctx.prefix_params.target_prefix) << "\",\n"; - std::cout << " \"prefix\": " << ctx.prefix_params.target_prefix << "\n"; + out << " \"prefix\": " << ctx.prefix_params.target_prefix << "\n"; - std::cout << "}\n"; + out << "}\n"; - std::cout.flush(); + const auto out_json = nlohmann::json::parse(out); + mamba::Console::instance().json_write(out_json); } else { From b7f20cf1b4b8b551dff88a6d1052f25fd97b6c81 Mon Sep 17 00:00:00 2001 From: Julien Jerphanion Date: Mon, 11 May 2026 16:55:02 +0200 Subject: [PATCH 29/52] fix: mark `LogHandler_History::is_started` inline in header Signed-off-by: Julien Jerphanion --- libmamba/include/mamba/core/logging_tools.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libmamba/include/mamba/core/logging_tools.hpp b/libmamba/include/mamba/core/logging_tools.hpp index bc1dbc3e1c..c77b6c5270 100644 --- a/libmamba/include/mamba/core/logging_tools.hpp +++ b/libmamba/include/mamba/core/logging_tools.hpp @@ -534,7 +534,7 @@ namespace mamba::logging } } - auto LogHandler_History::is_started() const -> bool + inline auto LogHandler_History::is_started() const -> bool { return pimpl != nullptr; } From 42aef3dc986af875c9965f6a19aec4a3f618c49a Mon Sep 17 00:00:00 2001 From: Julien Jerphanion Date: Mon, 11 May 2026 17:00:39 +0200 Subject: [PATCH 30/52] Make linters happy Signed-off-by: Julien Jerphanion --- libmamba/src/api/configuration.cpp | 3 +-- libmamba/src/core/context.cpp | 5 ++-- libmamba/src/core/output.cpp | 8 +++--- .../mamba/testing/test_logging_common.hpp | 2 +- micromamba/src/env.cpp | 3 +-- micromamba/tests/helpers.py | 4 +-- micromamba/tests/test_create.py | 26 ++++++++++++++++--- 7 files changed, 34 insertions(+), 17 deletions(-) diff --git a/libmamba/src/api/configuration.cpp b/libmamba/src/api/configuration.cpp index 40f8b45757..05c3dc7af8 100644 --- a/libmamba/src/api/configuration.cpp +++ b/libmamba/src/api/configuration.cpp @@ -815,7 +815,6 @@ namespace mamba mamba::log_level log_level_fallback_hook(Configuration& config) { - if (config.at("verbose").configured()) { const auto& ctx = config.context(); @@ -2300,7 +2299,7 @@ namespace mamba if (this->at("print_config_only").value()) { - // TOOD: fix this for the case where `--json` is used + // TODO: fix this for the case where `--json` is used int dump_opts = MAMBA_SHOW_CONFIG_VALUES | MAMBA_SHOW_CONFIG_SRCS | MAMBA_SHOW_ALL_CONFIGS; print_dump(*this, dump_opts); diff --git a/libmamba/src/core/context.cpp b/libmamba/src/core/context.cpp index 4063bded82..2cca030b6e 100644 --- a/libmamba/src/core/context.cpp +++ b/libmamba/src/core/context.cpp @@ -55,7 +55,8 @@ namespace mamba { if (output_params.json or output_params.quiet) { - throw mamba_error{ "cannot start logging with `--json` or `--quiet`", mamba_error_code::incorrect_usage }; + throw mamba_error{ "cannot start logging with `--json` or `--quiet`", + mamba_error_code::incorrect_usage }; } // Only change the log-handler if specified, keep the current one otherwise. @@ -109,7 +110,7 @@ namespace mamba } // Do not start logging if `--json` or `--quiet` are used. - if (options.enable_logging and not (output_params.json or output_params.quiet)) + if (options.enable_logging and not(output_params.json or output_params.quiet)) { start_logging(std::move(log_handler)); } diff --git a/libmamba/src/core/output.cpp b/libmamba/src/core/output.cpp index e04179df24..39e22d39fc 100644 --- a/libmamba/src/core/output.cpp +++ b/libmamba/src/core/output.cpp @@ -321,7 +321,7 @@ namespace mamba ); } - logging::set_log_handler(log_history_handler.get()); // use the existing logging parameters + logging::set_log_handler(log_history_handler.get()); // use the existing logging parameters } using ConsoleBuffer = std::vector; @@ -379,7 +379,7 @@ namespace mamba Console::~Console() { - // Note: even if the json is empty, we should still print + // Note: even if the json is empty, we should still print // and empty json object as long as `--json` is used. if (context().output_params.json and not p_data->is_json_print_cancelled) { @@ -609,7 +609,7 @@ namespace mamba { if (context().output_params.json) { - nlohmann::json tmp = j.flatten(); // FIXME: flattening makes empty arrays 'null' + nlohmann::json tmp = j.flatten(); // FIXME: flattening makes empty arrays 'null' for (auto it = tmp.begin(); it != tmp.end(); ++it) { p_data->json_log[p_data->json_hier + it.key()] = it.value(); @@ -632,7 +632,7 @@ namespace mamba { if (context().output_params.json) { - nlohmann::json tmp = j.flatten(); // FIXME: flattening makes empty arrays 'null' + nlohmann::json tmp = j.flatten(); // FIXME: flattening makes empty arrays 'null' for (auto it = tmp.begin(); it != tmp.end(); ++it) { p_data->json_log[p_data->json_hier + '/' + std::to_string(p_data->json_index) + it.key()] = it.value(); diff --git a/libmamba/tests/libmamba_logging/include/mamba/testing/test_logging_common.hpp b/libmamba/tests/libmamba_logging/include/mamba/testing/test_logging_common.hpp index a3039e19d2..32fdbcbed6 100644 --- a/libmamba/tests/libmamba_logging/include/mamba/testing/test_logging_common.hpp +++ b/libmamba/tests/libmamba_logging/include/mamba/testing/test_logging_common.hpp @@ -32,7 +32,7 @@ namespace mamba::logging::testing std::size_t stop_count = 0; std::size_t log_count = 0; std::size_t real_output_log_count = 0; - std::size_t filtered_out_log_count = 0; + std::size_t filtered_out_log_count = 0; std::size_t log_level_change_count = 0; std::size_t params_change_count = 0; std::size_t backtrace_size_change_count = 0; diff --git a/micromamba/src/env.cpp b/micromamba/src/env.cpp index 3cc5a56dd1..fb07d58198 100644 --- a/micromamba/src/env.cpp +++ b/micromamba/src/env.cpp @@ -208,8 +208,7 @@ set_env_command(CLI::App* com, mamba::Configuration& config) out << " \"dependencies\": [\n" << dependencies.str() << " ],\n"; out << " \"name\": \"" - << mamba::detail::get_env_name(ctx, ctx.prefix_params.target_prefix) - << "\",\n"; + << mamba::detail::get_env_name(ctx, ctx.prefix_params.target_prefix) << "\",\n"; out << " \"prefix\": " << ctx.prefix_params.target_prefix << "\n"; out << "}\n"; diff --git a/micromamba/tests/helpers.py b/micromamba/tests/helpers.py index a13e972b08..9c9183d8b4 100644 --- a/micromamba/tests/helpers.py +++ b/micromamba/tests/helpers.py @@ -337,7 +337,7 @@ def umamba_list(*args, json_as_pkgs_list=True, **kwargs): packages = pkgs_list_from_json_result(j) # empty list are currently set to null because of json flattening # in libmamba, so we need to translate null as empty list instead - return packages if packages != None else list() + return packages if packages is not None else list() else: return j @@ -724,10 +724,10 @@ def assert_state_file(state_file_path: Path, expected_state: dict): f"Expected {field_name} to be {expected_value}, but got {state[field_name]}" ) + def find_message_in_json_logs(json_result, message_to_find): for log_record in json_result["log_history"]: if message_to_find in log_record["message"]: return log_record return None - diff --git a/micromamba/tests/test_create.py b/micromamba/tests/test_create.py index b282d20cf9..589e9b3e33 100644 --- a/micromamba/tests/test_create.py +++ b/micromamba/tests/test_create.py @@ -378,7 +378,12 @@ def test_clone_conflicts_with_specs(tmp_home, tmp_root_prefix, tmp_path): ) json_output = json.loads(info.value.stdout.decode()) - assert helpers.find_message_in_json_logs(json_output, "Cannot use --clone together with package specs.") != None + assert ( + helpers.find_message_in_json_logs( + json_output, "Cannot use --clone together with package specs." + ) + is not None + ) @pytest.mark.parametrize("shared_pkgs_dirs", [True], indirect=True) @@ -402,7 +407,12 @@ def test_clone_conflicts_with_file(tmp_home, tmp_root_prefix, tmp_path): ) json_output = json.loads(info.value.stdout.decode()) - assert helpers.find_message_in_json_logs(json_output, "Cannot use --clone together with package specs.") != None + assert ( + helpers.find_message_in_json_logs( + json_output, "Cannot use --clone together with package specs." + ) + is not None + ) @pytest.mark.parametrize("shared_pkgs_dirs", [True], indirect=True) @@ -415,14 +425,22 @@ def test_clone_non_existing_source(tmp_home, tmp_root_prefix, tmp_path): "--clone", "this-env-does-not-exist", "-n", env_name, "--json", no_dry_run=True ) json_output = json.loads(info.value.stdout.decode()) - assert helpers.find_message_in_json_logs(json_output, "Could not find environment to clone: this-env-does-not-exist") != None + assert ( + helpers.find_message_in_json_logs( + json_output, "Could not find environment to clone: this-env-does-not-exist" + ) + is not None + ) # Non-existing prefix path non_existing_prefix = tmp_path / "does-not-exist" with pytest.raises(subprocess.CalledProcessError) as info2: helpers.create("--clone", non_existing_prefix, "-n", env_name, "--json", no_dry_run=True) json_output2 = json.loads(info2.value.stdout.decode()) - assert helpers.find_message_in_json_logs(json_output2, f"Source prefix '{non_existing_prefix}") != None + assert ( + helpers.find_message_in_json_logs(json_output2, f"Source prefix '{non_existing_prefix}") + is not None + ) @pytest.mark.skipif( From a8753d344946955578458a6ee5000c100566532a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klaim=20=28Jo=C3=ABl=20Lamotte=29?= <142265+Klaim@users.noreply.github.com> Date: Mon, 8 Jun 2026 13:58:18 +0200 Subject: [PATCH 31/52] log handlers are now comparable to check if they refer to the same instance --- libmamba/include/mamba/core/logging.hpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/libmamba/include/mamba/core/logging.hpp b/libmamba/include/mamba/core/logging.hpp index 61314d7fa3..a702e9f2a7 100644 --- a/libmamba/include/mamba/core/logging.hpp +++ b/libmamba/include/mamba/core/logging.hpp @@ -694,6 +694,13 @@ namespace mamba auto unsafe_get() const -> const std::remove_pointer_t

*; ///@} + /** `true` if the log handler instance is the same for both. + */ + ///@{ + friend bool operator==(const AnyLogHandler&, const AnyLogHandler&) = default; + friend bool operator!=(const AnyLogHandler&, const AnyLogHandler&) = default; + ///@} + private: struct Interface; From 0fce75f8eb9025dbf37d0beb06b70cd7bc2dd2b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klaim=20=28Jo=C3=ABl=20Lamotte=29?= <142265+Klaim@users.noreply.github.com> Date: Mon, 8 Jun 2026 15:51:18 +0200 Subject: [PATCH 32/52] fixed: logging backtrace changes were not persisted in logging parameters --- libmamba/include/mamba/core/logging.hpp | 16 +++++-- libmamba/include/mamba/core/logging_tools.hpp | 46 ++++++++++++++----- libmamba/src/core/logging.cpp | 4 +- 3 files changed, 49 insertions(+), 17 deletions(-) diff --git a/libmamba/include/mamba/core/logging.hpp b/libmamba/include/mamba/core/logging.hpp index a702e9f2a7..c3a0ce022f 100644 --- a/libmamba/include/mamba/core/logging.hpp +++ b/libmamba/include/mamba/core/logging.hpp @@ -799,16 +799,21 @@ namespace mamba /** Changes the logging system configuration. - If a log handler is registered, this function calls `AnyLogHandler::set_params` with the - same arguments. + If a log handler is registered and `update_log_handler` is `true`, + this function calls `AnyLogHandler::set_params` with the same arguments. @see `mamba::logging:LogHandler` and @see `mamba::logging::AnyLogHandler` for details. This call is thread safe as long as the log handler implementation fulfills the thread-safety requirements, @see `mamba::logging::LogHandler`. + Warning: `update_log_handler = false` should only be used when implementing + other operations that need to update the params and then update the log handler + themselves, separately. + @returns The previous configuration of the logging system. */ - auto set_logging_params(LoggingParams new_params) -> LoggingParams; + auto set_logging_params(LoggingParams new_params, bool update_log_handler = true) + -> LoggingParams; // TODO: potential performance improvement: log(record_generator, log_level) where // record_generator is a callable which generates the log record but is only called AFTER we @@ -889,7 +894,7 @@ namespace mamba auto log_backtrace() -> void; /** Sends the log records in the backtrace history to the implementation's logging sinks, - but without filtering the logging level of the log records. + but with filtering the logging level of the log records as if they were logged now. If a log handler is registered, this function calls `AnyLogHandler::log_backtrace_no_guards`, otherwise this function will do nothing. @@ -1031,6 +1036,9 @@ namespace mamba::logging inline auto enable_backtrace(size_t records_buffer_size) -> void { + auto params = get_logging_params(); + params.log_backtrace = records_buffer_size; + set_logging_params(params, false); call_log_handler_if_existing(&AnyLogHandler::enable_backtrace, records_buffer_size); } diff --git a/libmamba/include/mamba/core/logging_tools.hpp b/libmamba/include/mamba/core/logging_tools.hpp index c77b6c5270..3c74fcc712 100644 --- a/libmamba/include/mamba/core/logging_tools.hpp +++ b/libmamba/include/mamba/core/logging_tools.hpp @@ -94,14 +94,15 @@ namespace mamba::logging */ auto push_if_enabled(LogRecord& record) -> bool { - if (not is_enabled()) + if (is_enabled()) + { + queue_push(backtrace, backtrace_max, std::move(record)); + return true; + } + else { return false; } - - queue_push(backtrace, backtrace_max, std::move(record)); - - return true; } /** Changes the number of log records kept in the backtrace history. @@ -249,7 +250,13 @@ namespace mamba::logging auto enable_backtrace(size_t record_buffer_size) -> void; auto disable_backtrace() -> void; - auto log_backtrace() -> void; + + /** @see `AnyLogHandler::log_backtrace()` + + @param filter_current_level If `true`, will filter out log records that would not + pass the log level filter if they were emitted right now. + */ + auto log_backtrace(bool filter_current_level = false) -> void; auto log_backtrace_no_guards() -> void; auto flush(std::optional source = {}) -> void; @@ -360,7 +367,13 @@ namespace mamba::logging auto enable_backtrace(size_t record_buffer_size) -> void; auto disable_backtrace() -> void; - auto log_backtrace() -> void; + + /** @see `AnyLogHandler::log_backtrace()` + + @param filter_current_level If `true`, will filter out log records that would not + pass the log level filter if they were emitted right now. + */ + auto log_backtrace(bool filter_current_level = false) -> void; auto log_backtrace_no_guards() -> void; auto flush(std::optional source = {}) -> void; @@ -472,12 +485,16 @@ namespace mamba::logging pimpl->data->backtrace.disable(); } - inline auto LogHandler_History::log_backtrace() -> void + inline auto LogHandler_History::log_backtrace(bool filter_current_level) -> void { assert(pimpl); auto synched_data = pimpl->data.synchronize(); for (auto& log : synched_data->backtrace) { + if (filter_current_level and details::should_be_ignored(log, pimpl->current_log_level)) + { + continue; + } details::queue_push(synched_data->history, options.max_records_count, std::move(log)); } @@ -487,7 +504,8 @@ namespace mamba::logging inline auto LogHandler_History::log_backtrace_no_guards() -> void { assert(pimpl); - log_backtrace(); // Similar in this context + log_backtrace(true); // Similar in this context but we want to filter out logs that would + // not pass the level filter now } inline auto LogHandler_History::flush(std::optional) -> void @@ -655,7 +673,7 @@ namespace mamba::logging } template - inline auto LogHandler_Stream::log_backtrace() -> void + inline auto LogHandler_Stream::log_backtrace(bool filter_current_level) -> void { assert(out); assert(pimpl); @@ -663,6 +681,11 @@ namespace mamba::logging auto synched_backtrace = pimpl->backtrace.synchronize(); for (auto& log_record : *synched_backtrace) { + if (filter_current_level + and details::should_be_ignored(log_record, pimpl->current_log_level)) + { + continue; + } details::log_to_stream(*out, log_record, { .with_location = pimpl->log_location }); } synched_backtrace->clear(); @@ -674,7 +697,8 @@ namespace mamba::logging assert(out); assert(pimpl); - log_backtrace(); // Similar in this context + log_backtrace(true); // Similar in this context but we want to filter out logs that would + // not pass the level filter now } template diff --git a/libmamba/src/core/logging.cpp b/libmamba/src/core/logging.cpp index c03e1dcd9b..841e1043bf 100644 --- a/libmamba/src/core/logging.cpp +++ b/libmamba/src/core/logging.cpp @@ -165,12 +165,12 @@ namespace mamba::logging return details::logging_params.value(); } - auto set_logging_params(LoggingParams new_params) -> LoggingParams + auto set_logging_params(LoggingParams new_params, bool update_log_handler) -> LoggingParams { auto synched_params = details::logging_params.synchronize(); LoggingParams previous_params = *synched_params; *synched_params = std::move(new_params); - if (details::current_log_handler) + if (update_log_handler and details::current_log_handler) { details::current_log_handler.set_params(*synched_params); } From 13943505cfc1ebc7f9cbda7a8e4fd715e6082c9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klaim=20=28Jo=C3=ABl=20Lamotte=29?= <142265+Klaim@users.noreply.github.com> Date: Mon, 8 Jun 2026 15:51:29 +0200 Subject: [PATCH 33/52] tweak/simplification --- libmamba/src/core/output.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/libmamba/src/core/output.cpp b/libmamba/src/core/output.cpp index 16d7b19665..ac4d784ea3 100644 --- a/libmamba/src/core/output.cpp +++ b/libmamba/src/core/output.cpp @@ -602,11 +602,7 @@ namespace mamba if (context().output_params.json and log_history_handler) { auto log_history = capture_log_history_as_json(); - set_json_output({ - .to_assign{ - {"/log_history"_json_pointer, std::move(log_history) } - } - }); + set_json_output("/log_history"_json_pointer, std::move(log_history)); } print(p_data->json_output.dump(4), true); From b8b7c126a1f25fb5fc26de350bb5720e67e6ce94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klaim=20=28Jo=C3=ABl=20Lamotte=29?= <142265+Klaim@users.noreply.github.com> Date: Mon, 8 Jun 2026 16:48:52 +0200 Subject: [PATCH 34/52] fixed: incorrect JSON pointer formation from object members names --- libmamba/src/core/output.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libmamba/src/core/output.cpp b/libmamba/src/core/output.cpp index ac4d784ea3..eb8c15b2e5 100644 --- a/libmamba/src/core/output.cpp +++ b/libmamba/src/core/output.cpp @@ -684,7 +684,8 @@ namespace mamba edit.to_assign.reserve(object.size()); for (auto&& [member_path, value] : object.items()) { - edit.to_assign.emplace_back(nlohmann::json::json_pointer{ member_path }, std::move(value)); + const nlohmann::json::json_pointer location{ fmt::format("/{}", member_path) }; + edit.to_assign.emplace_back(location, std::move(value)); } return edit; From b96224831c49a99f54df66baa11f7f4d1a623d3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klaim=20=28Jo=C3=ABl=20Lamotte=29?= <142265+Klaim@users.noreply.github.com> Date: Mon, 8 Jun 2026 16:54:23 +0200 Subject: [PATCH 35/52] mamba tests: expect the "log_history" field in json output --- micromamba/tests/test_create.py | 1 + 1 file changed, 1 insertion(+) diff --git a/micromamba/tests/test_create.py b/micromamba/tests/test_create.py index 3b3311060f..c1238a5dcc 100644 --- a/micromamba/tests/test_create.py +++ b/micromamba/tests/test_create.py @@ -2714,6 +2714,7 @@ def test_create_dry_run_json(tmp_path): "dry_run": True, "prefix": str(env_prefix), "success": True, + "log_history": [] } assert res == expected_output From 4a7857555a786011be37f3fe07e783ce8f3e51e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klaim=20=28Jo=C3=ABl=20Lamotte=29?= <142265+Klaim@users.noreply.github.com> Date: Mon, 8 Jun 2026 17:14:02 +0200 Subject: [PATCH 36/52] repoquery subcommand json output now goes through the general output system --- libmamba/src/api/repoquery.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/libmamba/src/api/repoquery.cpp b/libmamba/src/api/repoquery.cpp index 84566166f9..2302d7b7e7 100644 --- a/libmamba/src/api/repoquery.cpp +++ b/libmamba/src/api/repoquery.cpp @@ -129,7 +129,9 @@ namespace mamba switch (format) { case QueryResultFormat::Json: - out << res.groupby("name").json().dump(4); + Console::instance().set_json_output( + JSONEdit::from_json_object_members(res.groupby("name").json()) + ); break; case QueryResultFormat::Pretty: res.pretty(out, show_all_builds); @@ -158,7 +160,7 @@ namespace mamba res.tree(out, graphics_params); break; case QueryResultFormat::Json: - out << res.json().dump(4); + Console::instance().set_json_output(JSONEdit::from_json_object_members(res.json())); break; case QueryResultFormat::Table: case QueryResultFormat::RecursiveTable: @@ -185,7 +187,7 @@ namespace mamba res.tree(out, graphics_params); break; case QueryResultFormat::Json: - out << res.json().dump(4); + Console::instance().set_json_output(JSONEdit::from_json_object_members(res.json())); break; case QueryResultFormat::Table: case QueryResultFormat::RecursiveTable: From 38c3bc9d75d00412906c1af529710cede1badc50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klaim=20=28Jo=C3=ABl=20Lamotte=29?= <142265+Klaim@users.noreply.github.com> Date: Mon, 8 Jun 2026 17:45:44 +0200 Subject: [PATCH 37/52] support version output in json --- micromamba/src/umamba.cpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/micromamba/src/umamba.cpp b/micromamba/src/umamba.cpp index 0b92c0552c..b993238e9f 100644 --- a/micromamba/src/umamba.cpp +++ b/micromamba/src/umamba.cpp @@ -31,11 +31,18 @@ set_umamba_command(CLI::App* com, mamba::Configuration& config) context.command_params.caller_version = umamba::version(); - auto print_version = [](int /*count*/) + auto print_version = [&](int /*count*/) { - // TODO: if `--json` is used, output there instead - std::cout << umamba::version() << std::endl; - exit(0); + if (config.context().output_params.json) + { + Console::instance().set_json_output("/version"_json_pointer, umamba::version()); + } + else + { + std::cout << umamba::version() << std::endl; + exit(0); + } + }; com->add_flag_function("--version", print_version); From 7c7d66ac82562925d180042ca3f6cb32e84e77f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klaim=20=28Jo=C3=ABl=20Lamotte=29?= <142265+Klaim@users.noreply.github.com> Date: Mon, 8 Jun 2026 17:50:12 +0200 Subject: [PATCH 38/52] updated comment in tests --- micromamba/tests/helpers.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/micromamba/tests/helpers.py b/micromamba/tests/helpers.py index 9c9183d8b4..1950c393b1 100644 --- a/micromamba/tests/helpers.py +++ b/micromamba/tests/helpers.py @@ -335,9 +335,8 @@ def umamba_list(*args, json_as_pkgs_list=True, **kwargs): # TODO: consider checking here that there is no errors # otherwise any log is lost beyond this point packages = pkgs_list_from_json_result(j) - # empty list are currently set to null because of json flattening - # in libmamba, so we need to translate null as empty list instead - return packages if packages is not None else list() + assert(type(packages) is list) + return packages else: return j From 17e9a06a188216ea204d74de5ca8c5f2479bbd79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klaim=20=28Jo=C3=ABl=20Lamotte=29?= <142265+Klaim@users.noreply.github.com> Date: Mon, 8 Jun 2026 20:41:22 +0200 Subject: [PATCH 39/52] fixed: hide_secrets regex matching with empty names --- libmamba/src/core/util.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/libmamba/src/core/util.cpp b/libmamba/src/core/util.cpp index 29571c34b7..c0b7a22d4a 100644 --- a/libmamba/src/core/util.cpp +++ b/libmamba/src/core/util.cpp @@ -76,9 +76,11 @@ namespace mamba const std::regex& token_regex() { - // usernames on anaconda.org can have a underscore, which influences the - // first two characters - static const std::regex token_regex{ "/t/([a-zA-Z0-9-_]{0,2}[a-zA-Z0-9-]*)" }; + // Usernames on anaconda.org can have a underscore, which influences the + // first two characters. + // The `+` is there to make sure we dont match `/t/*` with `*` being litteral or anything we + // dont capture here. + static const std::regex token_regex{ "/t/([a-zA-Z0-9-_]{0,2}[a-zA-Z0-9-_]+)" }; return token_regex; } From e49511378d931b2ff15ebd9e48c0537e61db19a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klaim=20=28Jo=C3=ABl=20Lamotte=29?= <142265+Klaim@users.noreply.github.com> Date: Tue, 9 Jun 2026 13:34:55 +0200 Subject: [PATCH 40/52] increased probably too big timeout --- micromamba/tests/test_create.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/micromamba/tests/test_create.py b/micromamba/tests/test_create.py index c1238a5dcc..ed3bb4416c 100644 --- a/micromamba/tests/test_create.py +++ b/micromamba/tests/test_create.py @@ -2689,7 +2689,7 @@ def test_create_package_with_non_url_char(tmp_home, tmp_root_prefix): assert any(pkg["name"] == "x264" for pkg in res["actions"]["LINK"]) -@pytest.mark.timeout(30) +@pytest.mark.timeout(60) @pytest.mark.parametrize("shared_pkgs_dirs", [True], indirect=True) @pytest.mark.skipif( platform.system() == "Windows", reason="This test fails on Windows for unknown reasons" From 007e13ae4175c3e8cb710f460f0d04474cecfae0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klaim=20=28Jo=C3=ABl=20Lamotte=29?= <142265+Klaim@users.noreply.github.com> Date: Tue, 9 Jun 2026 14:15:09 +0200 Subject: [PATCH 41/52] formatting --- libmamba/include/mamba/core/output.hpp | 3 +-- libmamba/src/core/util.cpp | 2 +- micromamba/src/env.cpp | 3 --- micromamba/tests/helpers.py | 2 +- micromamba/tests/test_create.py | 2 +- 5 files changed, 4 insertions(+), 8 deletions(-) diff --git a/libmamba/include/mamba/core/output.hpp b/libmamba/include/mamba/core/output.hpp index 8f3f88f244..6efd639171 100644 --- a/libmamba/include/mamba/core/output.hpp +++ b/libmamba/include/mamba/core/output.hpp @@ -134,7 +134,6 @@ namespace mamba /// each member as an assignation into the json object that will be edited /// by this resulgint json edit. static auto from_json_object_members(nlohmann::json object) -> JSONEdit; - }; class Console @@ -193,7 +192,7 @@ namespace mamba This overload is a shorter version for the 1 location 1 value assignation case which is quite common. */ - template< class T > + template requires requires(const T& value) { nlohmann::json{ value }; } void set_json_output(nlohmann::json::json_pointer location, T&& value) { diff --git a/libmamba/src/core/util.cpp b/libmamba/src/core/util.cpp index c0b7a22d4a..c34db8ad1b 100644 --- a/libmamba/src/core/util.cpp +++ b/libmamba/src/core/util.cpp @@ -78,7 +78,7 @@ namespace mamba { // Usernames on anaconda.org can have a underscore, which influences the // first two characters. - // The `+` is there to make sure we dont match `/t/*` with `*` being litteral or anything we + // The `+` is there to make sure we dont match `/t/*` with `*` being literal or anything we // dont capture here. static const std::regex token_regex{ "/t/([a-zA-Z0-9-_]{0,2}[a-zA-Z0-9-_]+)" }; return token_regex; diff --git a/micromamba/src/env.cpp b/micromamba/src/env.cpp index 101699b17e..85014534b7 100644 --- a/micromamba/src/env.cpp +++ b/micromamba/src/env.cpp @@ -195,8 +195,6 @@ set_env_command(CLI::App* com, mamba::Configuration& config) dependencies << (first_dependency_printed ? "\n" : ""); - - auto deps_json = nlohmann::json::parse(fmt::format("[ {} ]", dependencies.str())); assert(deps_json.is_array()); @@ -211,7 +209,6 @@ set_env_command(CLI::App* com, mamba::Configuration& config) }; mamba::Console::instance().set_json_output(std::move(out_json)); // clang-format on - } else { diff --git a/micromamba/tests/helpers.py b/micromamba/tests/helpers.py index 1950c393b1..2f870748b1 100644 --- a/micromamba/tests/helpers.py +++ b/micromamba/tests/helpers.py @@ -335,7 +335,7 @@ def umamba_list(*args, json_as_pkgs_list=True, **kwargs): # TODO: consider checking here that there is no errors # otherwise any log is lost beyond this point packages = pkgs_list_from_json_result(j) - assert(type(packages) is list) + assert type(packages) is list return packages else: return j diff --git a/micromamba/tests/test_create.py b/micromamba/tests/test_create.py index ed3bb4416c..d0044d0d9c 100644 --- a/micromamba/tests/test_create.py +++ b/micromamba/tests/test_create.py @@ -2714,7 +2714,7 @@ def test_create_dry_run_json(tmp_path): "dry_run": True, "prefix": str(env_prefix), "success": True, - "log_history": [] + "log_history": [], } assert res == expected_output From 9d8b7bd3320761ee9a704f2526dbd5810efe6955 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klaim=20=28Jo=C3=ABl=20Lamotte=29?= <142265+Klaim@users.noreply.github.com> Date: Tue, 9 Jun 2026 14:31:47 +0200 Subject: [PATCH 42/52] formatting --- micromamba/src/umamba.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/micromamba/src/umamba.cpp b/micromamba/src/umamba.cpp index b993238e9f..97693b211f 100644 --- a/micromamba/src/umamba.cpp +++ b/micromamba/src/umamba.cpp @@ -42,7 +42,6 @@ set_umamba_command(CLI::App* com, mamba::Configuration& config) std::cout << umamba::version() << std::endl; exit(0); } - }; com->add_flag_function("--version", print_version); From 240d53dd344f513879f334fd5cf637ad83f3ce33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klaim=20=28Jo=C3=ABl=20Lamotte=29?= <142265+Klaim@users.noreply.github.com> Date: Tue, 9 Jun 2026 17:33:29 +0200 Subject: [PATCH 43/52] removed unclear comment --- libmamba/src/core/output.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libmamba/src/core/output.cpp b/libmamba/src/core/output.cpp index eb8c15b2e5..532054f0f4 100644 --- a/libmamba/src/core/output.cpp +++ b/libmamba/src/core/output.cpp @@ -302,8 +302,7 @@ namespace mamba } // We clear the history on the way in case this function is called - // more than once in the program's execution, in which case the - // history will be accumulated here. + // more than once in the program's execution. const auto history = log_history_handler->capture_history(true); for (const auto& log_record : history) { From daeb4ee7a27d956ee9a3fcbf04650129a5de2a93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klaim=20=28Jo=C3=ABl=20Lamotte=29?= <142265+Klaim@users.noreply.github.com> Date: Wed, 10 Jun 2026 18:19:35 +0200 Subject: [PATCH 44/52] improved log record filtering clarity (addressing reviews) --- libmamba/include/mamba/core/logging_tools.hpp | 20 +++++++++++-------- .../mamba/testing/test_logging_common.hpp | 3 ++- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/libmamba/include/mamba/core/logging_tools.hpp b/libmamba/include/mamba/core/logging_tools.hpp index 3c74fcc712..9fc231042d 100644 --- a/libmamba/include/mamba/core/logging_tools.hpp +++ b/libmamba/include/mamba/core/logging_tools.hpp @@ -3,6 +3,8 @@ // Distributed under the terms of the BSD 3-Clause License. // // The full license is in the file LICENSE, distributed with this software. +#ifndef MAMBA_CORE_LOGGING_TOOLS_HPP +#define MAMBA_CORE_LOGGING_TOOLS_HPP #include #include @@ -49,18 +51,18 @@ namespace mamba::logging } } - inline auto should_be_ignored(const LogRecord& record, log_level current_level) -> bool + inline auto should_be_emitted(const LogRecord& record, log_level current_level) -> bool { switch (current_level) { case log_level::off: - return true; + return false; case log_level::all: - return false; + return true; default: - return current_level > record.level; + return record.level >= current_level; } } @@ -461,7 +463,7 @@ namespace mamba::logging inline auto LogHandler_History::log(LogRecord record) -> void { assert(pimpl); - if (details::should_be_ignored(record, pimpl->current_log_level)) + if (not details::should_be_emitted(record, pimpl->current_log_level)) { return; } @@ -491,7 +493,7 @@ namespace mamba::logging auto synched_data = pimpl->data.synchronize(); for (auto& log : synched_data->backtrace) { - if (filter_current_level and details::should_be_ignored(log, pimpl->current_log_level)) + if (filter_current_level and not details::should_be_emitted(log, pimpl->current_log_level)) { continue; } @@ -636,7 +638,7 @@ namespace mamba::logging assert(out); assert(pimpl); - if (details::should_be_ignored(record, pimpl->current_log_level)) + if (not details::should_be_emitted(record, pimpl->current_log_level)) { return; } @@ -682,7 +684,7 @@ namespace mamba::logging for (auto& log_record : *synched_backtrace) { if (filter_current_level - and details::should_be_ignored(log_record, pimpl->current_log_level)) + and not details::should_be_emitted(log_record, pimpl->current_log_level)) { continue; } @@ -726,3 +728,5 @@ namespace mamba::logging } } + +#endif diff --git a/libmamba/tests/libmamba_logging/include/mamba/testing/test_logging_common.hpp b/libmamba/tests/libmamba_logging/include/mamba/testing/test_logging_common.hpp index 32fdbcbed6..4a3c1488e2 100644 --- a/libmamba/tests/libmamba_logging/include/mamba/testing/test_logging_common.hpp +++ b/libmamba/tests/libmamba_logging/include/mamba/testing/test_logging_common.hpp @@ -15,6 +15,7 @@ #include #include +#include #include #include @@ -101,7 +102,7 @@ namespace mamba::logging::testing auto log(LogRecord record) -> void { auto stats = pimpl->stats.synchronize(); - if (stats->current_params.logging_level > record.level) + if (not details::should_be_emitted(record, stats->current_params.logging_level)) { // we ignore logs that should be filtered ++stats->filtered_out_log_count; From da56561e57d24ced4389d8ba77ea42fa50ac0010 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klaim=20=28Jo=C3=ABl=20Lamotte=29?= <142265+Klaim@users.noreply.github.com> Date: Thu, 11 Jun 2026 18:25:04 +0200 Subject: [PATCH 45/52] fix typo Co-authored-by: Julien Jerphanion --- micromamba/src/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/micromamba/src/main.cpp b/micromamba/src/main.cpp index d72a0577a6..b4da100646 100644 --- a/micromamba/src/main.cpp +++ b/micromamba/src/main.cpp @@ -143,7 +143,7 @@ main(int argc, char** argv) try { - // Note: do not use CLI11_PARSE macro as it's error handling + // Note: do not use CLI11_PARSE macro as its error handling // would bypass ours. app.parse(argc, utf8argv); if (app.get_subcommands().size() == 0) From 2146be8dd1790c020d4aacf64ea148d7651c9de2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klaim=20=28Jo=C3=ABl=20Lamotte=29?= <142265+Klaim@users.noreply.github.com> Date: Thu, 11 Jun 2026 18:25:34 +0200 Subject: [PATCH 46/52] fix typo Co-authored-by: Julien Jerphanion --- libmamba/src/core/util.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libmamba/src/core/util.cpp b/libmamba/src/core/util.cpp index c34db8ad1b..30bbb52493 100644 --- a/libmamba/src/core/util.cpp +++ b/libmamba/src/core/util.cpp @@ -79,7 +79,7 @@ namespace mamba // Usernames on anaconda.org can have a underscore, which influences the // first two characters. // The `+` is there to make sure we dont match `/t/*` with `*` being literal or anything we - // dont capture here. + // don't capture here. static const std::regex token_regex{ "/t/([a-zA-Z0-9-_]{0,2}[a-zA-Z0-9-_]+)" }; return token_regex; } From 3b3ebaeee1a6cf08b52e6eaab257d6fabfaa2757 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klaim=20=28Jo=C3=ABl=20Lamotte=29?= <142265+Klaim@users.noreply.github.com> Date: Thu, 11 Jun 2026 18:38:42 +0200 Subject: [PATCH 47/52] Revert "increased probably too big timeout" This reverts commit e49511378d931b2ff15ebd9e48c0537e61db19a6. --- micromamba/tests/test_create.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/micromamba/tests/test_create.py b/micromamba/tests/test_create.py index d0044d0d9c..f970f0cbb9 100644 --- a/micromamba/tests/test_create.py +++ b/micromamba/tests/test_create.py @@ -2689,7 +2689,7 @@ def test_create_package_with_non_url_char(tmp_home, tmp_root_prefix): assert any(pkg["name"] == "x264" for pkg in res["actions"]["LINK"]) -@pytest.mark.timeout(60) +@pytest.mark.timeout(30) @pytest.mark.parametrize("shared_pkgs_dirs", [True], indirect=True) @pytest.mark.skipif( platform.system() == "Windows", reason="This test fails on Windows for unknown reasons" From ace8813b4435b6580874011b44bf0d94fac795a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klaim=20=28Jo=C3=ABl=20Lamotte=29?= <142265+Klaim@users.noreply.github.com> Date: Tue, 16 Jun 2026 13:48:27 +0200 Subject: [PATCH 48/52] rewrote basic_backtrace::push_if_enabled to take an invocable used if the backtrace is disabled --- libmamba/include/mamba/core/logging_tools.hpp | 32 ++++++------ .../libmamba_logging/test_logging_tools.cpp | 52 +++++++++++++++---- 2 files changed, 57 insertions(+), 27 deletions(-) diff --git a/libmamba/include/mamba/core/logging_tools.hpp b/libmamba/include/mamba/core/logging_tools.hpp index 9fc231042d..f9678b3c92 100644 --- a/libmamba/include/mamba/core/logging_tools.hpp +++ b/libmamba/include/mamba/core/logging_tools.hpp @@ -88,22 +88,18 @@ namespace mamba::logging } /** If the backtrace feature is enabled, moves the log record into the backtrace - history and returns `true`. Otherwise do nothing and returns `false`. - - The log record is taken by reference to allow taking ownership of it through - a move, but also not taking ownership and not forcing a copy if we actually - don't need it. + history, otherwise calls the provided invocable with the log record as argument. */ - auto push_if_enabled(LogRecord& record) -> bool + template Func> + auto push_if_enabled(LogRecord&& record, Func&& otherwise_func) -> void { if (is_enabled()) { queue_push(backtrace, backtrace_max, std::move(record)); - return true; } else { - return false; + std::invoke(std::forward(otherwise_func), std::move(record)); } } @@ -469,10 +465,13 @@ namespace mamba::logging } auto synched_data = pimpl->data.synchronize(); - if (not synched_data->backtrace.push_if_enabled(record)) - { - details::queue_push(synched_data->history, options.max_records_count, std::move(record)); - } + synched_data->backtrace.push_if_enabled( + std::move(record), + [&](LogRecord&& record) + { + details::queue_push(synched_data->history, options.max_records_count, std::move(record)); + } + ); } inline auto LogHandler_History::enable_backtrace(size_t record_buffer_size) -> void @@ -645,10 +644,11 @@ namespace mamba::logging auto level = record.level; - if (not pimpl->backtrace->push_if_enabled(record)) - { - details::log_to_stream(*out, record, { .with_location = pimpl->log_location }); - } + pimpl->backtrace->push_if_enabled( + std::move(record), + [this](LogRecord&& record) + { details::log_to_stream(*out, record, { .with_location = pimpl->log_location }); } + ); if (level <= pimpl->flush_threshold) { diff --git a/libmamba/tests/libmamba_logging/test_logging_tools.cpp b/libmamba/tests/libmamba_logging/test_logging_tools.cpp index e248549a50..8574ea2311 100644 --- a/libmamba/tests/libmamba_logging/test_logging_tools.cpp +++ b/libmamba/tests/libmamba_logging/test_logging_tools.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -68,7 +69,10 @@ namespace mamba::logging TEST_CASE("details::BasicBacktrace") { - LogRecord log_not_pushed{ .message = "must not be pushed", .source = log_source::tests }; + using namespace std::string_view_literals; + + const std::string must_not_be_published = "must not be pushed"; + LogRecord log_not_pushed{ .message{ must_not_be_published }, .source = log_source::tests }; LogRecord log_a{ .message = "A", .source = log_source::tests }; LogRecord log_b{ .message = "B", .source = log_source::tests }; LogRecord log_c{ .message = "C", .source = log_source::tests }; @@ -77,26 +81,36 @@ namespace mamba::logging LogRecord log_f{ .message = "F", .source = log_source::tests }; LogRecord log_g{ .message = "G", .source = log_source::tests }; + std::vector emitted; + auto emit = [&](LogRecord&& record) { + emitted.push_back(std::move(record)); + }; + details::BasicBacktrace b; REQUIRE(not b.is_enabled()); REQUIRE(std::distance(b.begin(), b.end()) == 0); REQUIRE(b.size() == 0); REQUIRE(b.empty()); - b.push_if_enabled(log_a); + b.push_if_enabled(std::move(log_not_pushed), emit); REQUIRE(not b.is_enabled()); REQUIRE(std::distance(b.begin(), b.end()) == 0); REQUIRE(b.size() == 0); REQUIRE(b.empty()); - REQUIRE(not log_a.message.empty()); + REQUIRE(log_not_pushed.message.empty()); + REQUIRE(not emitted.empty()); + REQUIRE(emitted.size() == 1); + REQUIRE(emitted.back().message == must_not_be_published); b.set_max_trace(2); REQUIRE(b.is_enabled()); REQUIRE(std::distance(b.begin(), b.end()) == 0); REQUIRE(b.size() == 0); REQUIRE(b.empty()); + REQUIRE(emitted.size() == 1); + REQUIRE(emitted.back().message == must_not_be_published); - b.push_if_enabled(log_a); + b.push_if_enabled(std::move(log_a), emit); REQUIRE(b.is_enabled()); REQUIRE(std::distance(b.begin(), b.end()) == 1); REQUIRE(b.size() == 1); @@ -104,8 +118,10 @@ namespace mamba::logging REQUIRE(b.begin()->message == "A"); REQUIRE(std::next(b.end(), -1)->message == "A"); REQUIRE(log_a.message.empty()); + REQUIRE(emitted.size() == 1); + REQUIRE(emitted.back().message == must_not_be_published); - b.push_if_enabled(log_b); + b.push_if_enabled(std::move(log_b), emit); REQUIRE(b.is_enabled()); REQUIRE(std::distance(b.begin(), b.end()) == 2); REQUIRE(b.size() == 2); @@ -113,8 +129,10 @@ namespace mamba::logging REQUIRE(b.begin()->message == "A"); REQUIRE(std::next(b.end(), -1)->message == "B"); REQUIRE(log_b.message.empty()); + REQUIRE(emitted.size() == 1); + REQUIRE(emitted.back().message == must_not_be_published); - b.push_if_enabled(log_c); + b.push_if_enabled(std::move(log_c), emit); REQUIRE(b.is_enabled()); REQUIRE(std::distance(b.begin(), b.end()) == 2); REQUIRE(b.size() == 2); @@ -122,6 +140,8 @@ namespace mamba::logging REQUIRE(b.begin()->message == "B"); REQUIRE(std::next(b.end(), -1)->message == "C"); REQUIRE(log_b.message.empty()); + REQUIRE(emitted.size() == 1); + REQUIRE(emitted.back().message == must_not_be_published); b.clear(); REQUIRE(b.is_enabled()); @@ -129,7 +149,7 @@ namespace mamba::logging REQUIRE(b.size() == 0); REQUIRE(b.empty()); - b.push_if_enabled(log_d); + b.push_if_enabled(std::move(log_d), emit); REQUIRE(b.is_enabled()); REQUIRE(std::distance(b.begin(), b.end()) == 1); REQUIRE(b.size() == 1); @@ -137,8 +157,10 @@ namespace mamba::logging REQUIRE(b.begin()->message == "D"); REQUIRE(std::next(b.end(), -1)->message == "D"); REQUIRE(log_d.message.empty()); + REQUIRE(emitted.size() == 1); + REQUIRE(emitted.back().message == must_not_be_published); - b.push_if_enabled(log_e); + b.push_if_enabled(std::move(log_e), emit); REQUIRE(b.is_enabled()); REQUIRE(std::distance(b.begin(), b.end()) == 2); REQUIRE(b.size() == 2); @@ -146,9 +168,11 @@ namespace mamba::logging REQUIRE(b.begin()->message == "D"); REQUIRE(std::next(b.end(), -1)->message == "E"); REQUIRE(log_e.message.empty()); + REQUIRE(emitted.size() == 1); + REQUIRE(emitted.back().message == must_not_be_published); - b.push_if_enabled(log_f); + b.push_if_enabled(std::move(log_f), emit); REQUIRE(b.is_enabled()); REQUIRE(std::distance(b.begin(), b.end()) == 2); REQUIRE(b.size() == 2); @@ -156,19 +180,25 @@ namespace mamba::logging REQUIRE(b.begin()->message == "E"); REQUIRE(std::next(b.end(), -1)->message == "F"); REQUIRE(log_f.message.empty()); + REQUIRE(emitted.size() == 1); + REQUIRE(emitted.back().message == must_not_be_published); b.disable(); REQUIRE(not b.is_enabled()); REQUIRE(std::distance(b.begin(), b.end()) == 0); REQUIRE(b.size() == 0); REQUIRE(b.empty()); + REQUIRE(emitted.size() == 1); + REQUIRE(emitted.back().message == must_not_be_published); - b.push_if_enabled(log_g); + b.push_if_enabled(std::move(log_g), emit); REQUIRE(not b.is_enabled()); REQUIRE(std::distance(b.begin(), b.end()) == 0); REQUIRE(b.size() == 0); REQUIRE(b.empty()); - REQUIRE(not log_g.message.empty()); + REQUIRE(log_g.message.empty()); + REQUIRE(emitted.size() == 2); + REQUIRE(emitted.back().message == "G"); } TEST_CASE("details::log_to_stream") From 395cbcb2be185714c765a2a6f24a55945a614c70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klaim=20=28Jo=C3=ABl=20Lamotte=29?= <142265+Klaim@users.noreply.github.com> Date: Tue, 16 Jun 2026 14:01:34 +0200 Subject: [PATCH 49/52] (micro)mamba: rewrote options intiialization for clarity --- micromamba/src/main.cpp | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/micromamba/src/main.cpp b/micromamba/src/main.cpp index b4da100646..68df8a0809 100644 --- a/micromamba/src/main.cpp +++ b/micromamba/src/main.cpp @@ -43,24 +43,32 @@ decide_preconfig_context_options(int argc, char** argv) -> mamba::ContextOptions .enable_signal_handling = true, }; - mamba::OutputParams output_params; + // We want to initialize options.output_params (which is an `std::optional`) + // only if `--json` or `--quiet` appears in the program parameters. + // Otherwise it must stay `std::nullopt`. + + const auto ready_output_params = [&] + { + if (not options.output_params.has_value()) + { + options.output_params.emplace(); + } + }; + for (const char* arg : std::ranges::subrange(argv, argv + argc)) { if (arg == "--json"sv) { - output_params.json = true; + ready_output_params(); + options.output_params->json = true; } else if (arg == "--quiet"sv) { - output_params.quiet = true; + ready_output_params(); + options.output_params->quiet = true; } } - if (output_params.json or output_params.quiet) - { - options.output_params = output_params; - } - return options; } From 05c46ac47f16153f685a9c64812536663959f24d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klaim=20=28Jo=C3=ABl=20Lamotte=29?= <142265+Klaim@users.noreply.github.com> Date: Tue, 16 Jun 2026 14:04:21 +0200 Subject: [PATCH 50/52] removed comments that is not up to date --- libmamba/src/api/configuration.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/libmamba/src/api/configuration.cpp b/libmamba/src/api/configuration.cpp index 84dccfdf44..ddb3556e20 100644 --- a/libmamba/src/api/configuration.cpp +++ b/libmamba/src/api/configuration.cpp @@ -2300,7 +2300,6 @@ namespace mamba if (this->at("print_config_only").value()) { - // TODO: fix this for the case where `--json` is used int dump_opts = MAMBA_SHOW_CONFIG_VALUES | MAMBA_SHOW_CONFIG_SRCS | MAMBA_SHOW_ALL_CONFIGS; print_dump(*this, dump_opts); From ba8cfd56263cd44c5370d32dc26c4cdc6b194277 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klaim=20=28Jo=C3=ABl=20Lamotte=29?= <142265+Klaim@users.noreply.github.com> Date: Tue, 16 Jun 2026 14:15:09 +0200 Subject: [PATCH 51/52] appease warnings that assumes developers ignores of the concept of "scope" :/ --- libmamba/include/mamba/core/logging_tools.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libmamba/include/mamba/core/logging_tools.hpp b/libmamba/include/mamba/core/logging_tools.hpp index f9678b3c92..65e7069b48 100644 --- a/libmamba/include/mamba/core/logging_tools.hpp +++ b/libmamba/include/mamba/core/logging_tools.hpp @@ -467,9 +467,9 @@ namespace mamba::logging auto synched_data = pimpl->data.synchronize(); synched_data->backtrace.push_if_enabled( std::move(record), - [&](LogRecord&& record) + [&](LogRecord&& record_) { - details::queue_push(synched_data->history, options.max_records_count, std::move(record)); + details::queue_push(synched_data->history, options.max_records_count, std::move(record_)); } ); } @@ -646,8 +646,8 @@ namespace mamba::logging pimpl->backtrace->push_if_enabled( std::move(record), - [this](LogRecord&& record) - { details::log_to_stream(*out, record, { .with_location = pimpl->log_location }); } + [this](LogRecord&& record_) + { details::log_to_stream(*out, record_, { .with_location = pimpl->log_location }); } ); if (level <= pimpl->flush_threshold) From fa0f43060377323791eb953634e7b5d335e242e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klaim=20=28Jo=C3=ABl=20Lamotte=29?= <142265+Klaim@users.noreply.github.com> Date: Tue, 16 Jun 2026 14:19:49 +0200 Subject: [PATCH 52/52] formatting --- libmamba/tests/libmamba_logging/test_logging_tools.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/libmamba/tests/libmamba_logging/test_logging_tools.cpp b/libmamba/tests/libmamba_logging/test_logging_tools.cpp index 8574ea2311..e4f8f0b5dd 100644 --- a/libmamba/tests/libmamba_logging/test_logging_tools.cpp +++ b/libmamba/tests/libmamba_logging/test_logging_tools.cpp @@ -82,9 +82,7 @@ namespace mamba::logging LogRecord log_g{ .message = "G", .source = log_source::tests }; std::vector emitted; - auto emit = [&](LogRecord&& record) { - emitted.push_back(std::move(record)); - }; + auto emit = [&](LogRecord&& record) { emitted.push_back(std::move(record)); }; details::BasicBacktrace b; REQUIRE(not b.is_enabled());