Skip to content
Merged
Show file tree
Hide file tree
Changes from 45 commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
d9ac264
Don't log if `--json` or `--quiet`
Klaim Mar 19, 2026
243d928
fixed LogHandler_History incorrect filtering of log records + added r…
Klaim Mar 25, 2026
bb89645
improved logging tests
Klaim Mar 25, 2026
58dc097
json output will contain log records passing the log level filter (>=…
Klaim Mar 25, 2026
9ca3a20
attempt to get some info from tests
Klaim Mar 26, 2026
22a1177
fixed test expecting an error in cerr, looking into json instead
Klaim Mar 27, 2026
fe59cb8
LogHandler_History allows capture with clear
Klaim Mar 30, 2026
5515fb1
output a potentially empty json if `--json` is used even if no log hi…
Klaim Mar 30, 2026
f529c23
console must capture and output json after error logging
Klaim Mar 30, 2026
dee6f12
(micro)mamba always reset the console whatever the way it returns
Klaim Mar 30, 2026
4f3db35
tests: read json from process error (attempt)
Klaim Mar 30, 2026
e62c40e
do not output Console's JSON when command already outputs JSON or YAML
Klaim Mar 30, 2026
5596d03
`--json` and `--quiet` are specially handled before creating the cont…
Klaim Mar 30, 2026
91c65a8
keep "run --help" message when arguments parsing failed
Klaim Mar 30, 2026
2f7c9ae
workaround compilers incorrectly requiring explicit member initializa…
Klaim Mar 30, 2026
808be20
config sub-commands outputing json are merged with the normal json ou…
Klaim Mar 31, 2026
1e752c7
removed incorrect code and comment
Klaim Mar 31, 2026
ce45264
let cli11 handle it's ownt errors but output where we want it to depe…
Klaim Mar 31, 2026
ee538ba
fix test json output erorr checks
Klaim Apr 7, 2026
63ed7bd
fixup
Klaim Apr 8, 2026
3fc7d69
partial text fix
Klaim Apr 8, 2026
cfeadc9
list sub-command's array of packages is now part of the returned json…
Klaim Apr 13, 2026
b6736ee
fixed: tools log-handlers not properly filtering log records
Klaim Apr 14, 2026
fafe861
adapt integration tests `list` calls assuming the result is a package…
Klaim Apr 15, 2026
b3b5110
fixed: `mamba create --dry-run --json` output 2 json objects
Klaim Apr 17, 2026
139beec
fixed: `list` subcommand tests guaranteed to return an array even if …
Klaim Apr 17, 2026
7eeed5a
fixed: `mamba env list --json` outputs 2 json objects
Klaim Apr 17, 2026
30de13b
fixed: `mamba env export|list --json` outputing 2 json objects
Klaim Apr 17, 2026
b7f20cf
fix: mark `LogHandler_History::is_started` inline in header
jjerphan May 11, 2026
42aef3d
Make linters happy
jjerphan May 11, 2026
5e72263
merge origin/main + adaptations
Klaim Jun 3, 2026
a8753d3
log handlers are now comparable to check if they refer to the same in…
Klaim Jun 8, 2026
0fce75f
fixed: logging backtrace changes were not persisted in logging parame…
Klaim Jun 8, 2026
1394350
tweak/simplification
Klaim Jun 8, 2026
a5620e8
Merge remote-tracking branch 'origin/main' into no-logging-json-quiet
Klaim Jun 8, 2026
b8b7c12
fixed: incorrect JSON pointer formation from object members names
Klaim Jun 8, 2026
b962248
mamba tests: expect the "log_history" field in json output
Klaim Jun 8, 2026
4a78575
repoquery subcommand json output now goes through the general output …
Klaim Jun 8, 2026
38c3bc9
support version output in json
Klaim Jun 8, 2026
7c7d66a
updated comment in tests
Klaim Jun 8, 2026
17e9a06
fixed: hide_secrets regex matching with empty names
Klaim Jun 8, 2026
a98b70e
Merge remote-tracking branch 'origin/main' into no-logging-json-quiet
Klaim Jun 8, 2026
e495113
increased probably too big timeout
Klaim Jun 9, 2026
007e13a
formatting
Klaim Jun 9, 2026
9d8b7bd
formatting
Klaim Jun 9, 2026
240d53d
removed unclear comment
Klaim Jun 9, 2026
daeb4ee
improved log record filtering clarity (addressing reviews)
Klaim Jun 10, 2026
3e21106
Merge branch 'main' into no-logging-json-quiet
Klaim Jun 11, 2026
da56561
fix typo
Klaim Jun 11, 2026
2146be8
fix typo
Klaim Jun 11, 2026
3b3ebae
Revert "increased probably too big timeout"
Klaim Jun 11, 2026
ace8813
rewrote basic_backtrace::push_if_enabled to take an invocable used if…
Klaim Jun 16, 2026
395cbcb
(micro)mamba: rewrote options intiialization for clarity
Klaim Jun 16, 2026
05c46ac
removed comments that is not up to date
Klaim Jun 16, 2026
ba8cfd5
appease warnings that assumes developers ignores of the concept of "s…
Klaim Jun 16, 2026
fa0f430
formatting
Klaim Jun 16, 2026
fd5a8e8
Merge remote-tracking branch 'origin/main' into no-logging-json-quiet
Klaim Jun 16, 2026
855eb58
Merge branch 'main' into no-logging-json-quiet
Klaim Jun 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions libmamba/include/mamba/core/context.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<OutputParams> output_params = {};
};

// Context singleton class
Expand All @@ -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
{
Expand Down
23 changes: 19 additions & 4 deletions libmamba/include/mamba/core/logging.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -694,6 +694,13 @@ namespace mamba
auto unsafe_get() const -> const std::remove_pointer_t<P>*;
///@}

/** `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;
Expand Down Expand Up @@ -792,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
Expand Down Expand Up @@ -882,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.
Expand Down Expand Up @@ -1024,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);
}

Expand Down
118 changes: 86 additions & 32 deletions libmamba/include/mamba/core/logging_tools.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,18 @@ 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<LogRecord>& queue, size_t max_elements, LogRecord record)
Expand All @@ -37,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;
Comment thread
jjerphan marked this conversation as resolved.
Outdated
}
}

/** Backtrace feature implementation in it's most basic form.

This is the simplest implementation for a backtrace feature
Expand Down Expand Up @@ -67,14 +94,15 @@ namespace mamba::logging
*/
auto push_if_enabled(LogRecord& record) -> bool

@JohanMabille JohanMabille Jun 11, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I am still uncomfortabe with this API and the move below. Given that this function is called here and here only, that the LogRecord is passed by value and is not used after the call to push_if_enabled, I think it is safe to accept it by rvalue reference here instead of lvalue reference.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

When this function returns false, in both usage we move the record in another container, so I can't just take by r-value-reference without at least returning the object again so that it can be used by the caller, otherwise at the call point it would look like we move the object twice, which is even weirder.

if (not backtrace.push_if_enabled(std::move(record)))
{
    something_else(std::move(record)); // looks like UB
}

My goal here is to have this algorithm written only once, as having the same logic in both place lead to some annoying errors while developing. But I also agree it's quite weird api.

I'll try to find another way to achieve that goal with a nicer api. I was thinking maybe something in that direction:

// free function:
auto push_to_backtrace_or(Record&& record, BasicBacktrace& backtrace, std::invocable<Record&&> auto or_func);

// in call site:
push_to_backtrace_or(std::move(record), backtrace, [&](LogRecord&& record){ 
   // do the work if not pushed to backtrace
   something_else(std::move(record));
});

The passing of the same object to the lambda is a bit weird but maybe less weird than this current api?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I was thinking of having push_to_backtrace returning the log record if not pushed (can be an optional, or a pair<bool, LogRecord>), but that would make the syntax heavier. Passing a lambda as you suggest seems the best option in the end, I don't find it weird to pass the same object to the lambda; and it is not that obvious at the call site.

{
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.
Expand Down Expand Up @@ -138,17 +166,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;
Expand All @@ -158,9 +175,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(" ({})", details::as_log(record.location))
: std::string{};
auto location_str = options.with_location ? fmt::format(" ({})", as_log(record.location))
: std::string{};

out << fmt::format(
"\n{} {}{} : {}",
Expand Down Expand Up @@ -234,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<log_source> source = {}) -> void;
Expand All @@ -245,12 +267,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<LogRecord>;
auto capture_history(bool and_clear = false) const -> std::vector<LogRecord>;

/** Clears the internal history.

Expand Down Expand Up @@ -343,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<log_source> source = {}) -> void;
Expand Down Expand Up @@ -431,7 +461,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;
}
Expand All @@ -455,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));
}

Expand All @@ -470,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<log_source>) -> void
Expand All @@ -485,12 +520,25 @@ namespace mamba::logging
// nothing to do, we keep history, there is no flush
}

inline auto LogHandler_History::capture_history() const -> std::vector<LogRecord>
inline auto LogHandler_History::capture_history(bool and_clear) const -> std::vector<LogRecord>
{
if (pimpl)
{
auto synched_data = pimpl->data.synchronize();
return std::vector<LogRecord>(synched_data->history.begin(), synched_data->history.end());
if (and_clear)
{
std::vector<LogRecord> result(synched_data->history.size());
std::ranges::move(synched_data->history, result.begin());
synched_data->history.clear();
Comment thread
jjerphan marked this conversation as resolved.
return result;
}
else
{
return std::vector<LogRecord>(
synched_data->history.begin(),
synched_data->history.end()
);
}
}

return {};
Expand All @@ -504,7 +552,7 @@ namespace mamba::logging
}
}

auto LogHandler_History::is_started() const -> bool
inline auto LogHandler_History::is_started() const -> bool
{
return pimpl != nullptr;
}
Expand Down Expand Up @@ -588,7 +636,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;
}
Expand Down Expand Up @@ -625,14 +673,19 @@ namespace mamba::logging
}

template <OutputStream T>
inline auto LogHandler_Stream<T>::log_backtrace() -> void
inline auto LogHandler_Stream<T>::log_backtrace(bool filter_current_level) -> void
{
assert(out);
assert(pimpl);

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();
Expand All @@ -644,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 <OutputStream T>
Expand Down
25 changes: 25 additions & 0 deletions libmamba/include/mamba/core/output.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,11 @@ namespace mamba
///< object to mark it as a success will be
///< done once the other changes are
///< applied.

/// Construct a json edit based on the members of a json object by using
/// 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
Expand Down Expand Up @@ -183,11 +188,31 @@ namespace mamba
*/
void set_json_output(JSONEdit edit);

/** Assigns the provided json value to the associated location in the json output object.
This overload is a shorter version for the 1 location 1 value assignation case
which is quite common.
*/
template <class T>
requires requires(const T& value) { nlohmann::json{ value }; }
void set_json_output(nlohmann::json::json_pointer location, T&& value)
{
// clang-format off
set_json_output({
.to_assign{
{ std::move(location), std::forward<T>(value) }
}
});
// clang-format on
}

/** If json output was requested, calling this before destroying a `Console` instance will
not lead to a json output.
*/
void cancel_json_print();

// FIXME: documentation

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Did you mean to introduce a small docstring?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I forgot to add the docstring yes, will add.

static void setup_log_handling_for_json();

static void print_buffer(std::ostream& ostream);

const Context& context() const;
Expand Down
Loading
Loading