labels: improve logging for import

Clear up the code a bit using the new logging functions.

Don't log a failure for "plan A" as an error, it just scares user unnecessarily.
This commit is contained in:
Dirk-Jan C. Binnema
2025-12-13 13:22:12 +02:00
committed by Seth Ladygo
parent f66b58cd41
commit dc5bbd9884
3 changed files with 48 additions and 36 deletions

View File

@ -30,7 +30,8 @@ Mu::Labels::validate_label(const std::string &label)
if (label.empty()) if (label.empty())
return Err(Error{Error::Code::InvalidArgument, return Err(Error{Error::Code::InvalidArgument,
"labels cannot be empty"}); "labels cannot be empty"});
else if (!g_utf8_validate(label.c_str(), label.size(), {})) // perhaps put hex in err str? else if (!g_utf8_validate(label.c_str(), label.size(), {}))
// perhaps put hex in err str?
return Err(Error{Error::Code::InvalidArgument, return Err(Error{Error::Code::InvalidArgument,
"labels must be valid UTF-8"}); "labels must be valid UTF-8"});
@ -43,7 +44,7 @@ Mu::Labels::validate_label(const std::string &label)
return Err(Error{Error::Code::InvalidArgument, return Err(Error{Error::Code::InvalidArgument,
"labels cannot start with '+' or '-' ({})", label}); "labels cannot start with '+' or '-' ({})", label});
for (auto cur = cstr; cur && *cur; cur = g_utf8_next_char(cur)) { for (auto cur = cstr; cur && *cur; cur = g_utf8_next_char(cur)) { // NOLINT
const gunichar uc = g_utf8_get_char(cur); const gunichar uc = g_utf8_get_char(cur);
if (g_unichar_isalnum(uc)) if (g_unichar_isalnum(uc))
@ -117,7 +118,7 @@ Mu::Labels::updated_labels(const LabelVec& labels, const DeltaLabelVec& deltas)
// First, the delta; put in a set for uniqueness; and use a special // First, the delta; put in a set for uniqueness; and use a special
// comparison operator so "add" and "remove" deltas are considered "the same" // comparison operator so "add" and "remove" deltas are considered "the same"
// for the set; then fill the set from the end of the deltas vec to the begining, // for the set; then fill the set from the end of the deltas vec to the beginning,
// so "the last one wins", as we want. // so "the last one wins", as we want.
// only one change per label, last one wins // only one change per label, last one wins

View File

@ -76,6 +76,7 @@ Mu::export_labels(const Store& store, const std::string& query, Option<std::stri
auto output_res = export_output(path); auto output_res = export_output(path);
if (!output_res) if (!output_res)
return Err(std::move(output_res.error())); return Err(std::move(output_res.error()));
auto&[output, output_path] = *output_res; auto&[output, output_path] = *output_res;
@ -97,43 +98,45 @@ Mu::export_labels(const Store& store, const std::string& query, Option<std::stri
return Ok(std::move(output_path)); return Ok(std::move(output_path));
} }
static void namespace Levels {
log_import(bool quiet, bool verbose, const std::string& msg, bool is_err=false) using Level = size_t;
{ constexpr Level quiet = 1 << 0;
if (is_err) constexpr Level verbose = 1 << 1;
mu_debug("{}", msg); constexpr Level error = 1 << 0;
else
mu_warning("{}", msg);
if (is_err && !quiet)
mu_printerrln("{}", msg);
else if (verbose)
mu_println("{}", msg);
} }
static void using Level = Levels::Level;
log_import_err(bool quiet, bool verbose, const std::string& msg)
{
log_import(quiet, verbose, msg, true);
}
template<typename...T>
static void output(Level level,
fmt::format_string<T...> frm, T&&... args) noexcept {
GLogLevelFlags lflags = level & Levels::error ?
G_LOG_LEVEL_WARNING : G_LOG_LEVEL_DEBUG;
mu_log(lflags, frm, std::forward<T>(args)...);
if ((level & Levels::error))
mu_printerrln(frm, std::forward<T>(args)... );
else if ((level & Levels::verbose))
mu_println(frm, std::forward<T>(args)... );
}
static Result<QueryResults> static Result<QueryResults>
log_import_get_matching(Mu::Store& store, const std::string& query, int max=1) import_get_matching(Mu::Store& store, const std::string& query, int max=1)
{ {
if (auto qres = store.run_query(query, {}, {}, max); !qres) if (auto qres = store.run_query(query, {}, {}, max); !qres)
return Err(std::move(qres.error())); return Err(std::move(qres.error()));
else if (qres->empty()) else if (qres->empty())
return Err(Error{Error::Code::Query, return Err(Error{Error::Code::Query,
"no matching messages for {}", query}); "no match for or '{}'", query});
else else
return Ok(std::move(*qres)); return Ok(std::move(*qres));
} }
static void static void
import_labels_for_message(Mu::Store& store, import_labels_for_message(Mu::Store& store, bool dry_run, Level level,
bool dry_run, bool quiet, bool verbose,
const std::string& path, const std::string& msgid, const std::string& path, const std::string& msgid,
const std::vector<std::string> labels) const std::vector<std::string> labels)
{ {
@ -147,33 +150,38 @@ import_labels_for_message(Mu::Store& store,
const auto qres = [&]()->Result<QueryResults>{ const auto qres = [&]()->Result<QueryResults>{
// plan A: match by path // plan A: match by path
if (auto qres_a{log_import_get_matching(store, "path:" + path)}; !qres_a) { if (auto qres_a{import_get_matching(store, "path:" + path)}; !qres_a) {
log_import_err(quiet, verbose, mu_format("failed to find by path: {}; try with message-id", output(level, "path '{}' does not match; try message-id",
qres_a.error().what())); qres_a.error().what());
// plan B: try the message-id // plan B: try the message-id
return log_import_get_matching(store, "msgid:" + msgid, -1/*all matching*/); auto qres_b{import_get_matching(store, "msgid:" + msgid, -1/*all matching*/)};
if (!qres_b) { // plan-B failed too?
output(level | Levels::error,
"import failed: cannot find message by path '{}' or message-id '{}'",
path, msgid);
}
return qres_b;
} else } else
return qres_a; return qres_a;
}(); }();
// neither plan a or b worked? we have to give up... // neither plan a or b worked? we have to give up...
if (!qres) { if (!qres) {
log_import_err(quiet, verbose, qres.error().what());
return; return;
} }
mu_println("{} matches", qres->size());
// we have match(es)! // we have match(es)!
for (auto&& item: *qres) { for (auto&& item: *qres) {
auto msg{*item.message()}; auto msg{*item.message()};
if (dry_run ) if (dry_run )
mu_println("labels: would apply label '{}' to {}", join(labels, ","), path); output(level, "{}: would have applied labels {}", msg.path(), join(labels, ","));
else if (const auto res = store.update_labels(msg, delta_labels); !res) else if (const auto res = store.update_labels(msg, delta_labels); !res)
log_import_err(quiet, verbose, output(level | Levels::error, "failed to update labels for {}: {}",
mu_format("failed to update labels for {}: {}", msg.path(), res.error().what());
msg.path(), res.error().what()));
else else
log_import(quiet, verbose, output(level, "{}: applied labels {}", msg.path(), join(labels, ","));
mu_format("applied labels {} to {}", join(labels, ","), path));
} }
} }
@ -190,6 +198,8 @@ Mu::import_labels(Mu::Store& store, const std::string& path, bool dry_run, bool
std::string current_path, current_msgid; std::string current_path, current_msgid;
std::vector<std::string> current_labels; std::vector<std::string> current_labels;
const Level level{(quiet ? Levels::quiet : 0) | (verbose ? Levels::verbose : 0)};
while (std::getline(input, line)) { while (std::getline(input, line)) {
if (line.find(path_key) == 0) if (line.find(path_key) == 0)
@ -199,7 +209,7 @@ Mu::import_labels(Mu::Store& store, const std::string& path, bool dry_run, bool
else if (line.find(labels_key) == 0) { else if (line.find(labels_key) == 0) {
current_labels = split(line.substr(labels_key.length()), ','); current_labels = split(line.substr(labels_key.length()), ',');
if (!current_labels.empty()) if (!current_labels.empty())
import_labels_for_message(store, dry_run, quiet, verbose, import_labels_for_message(store, dry_run, level,
current_path, current_msgid, current_path, current_msgid,
current_labels); current_labels);
current_path.clear(); current_path.clear();

View File

@ -43,6 +43,7 @@ class Store; // fwd declaration
Result<std::string> export_labels(const Store& store, Result<std::string> export_labels(const Store& store,
const std::string& query="", const std::string& query="",
Option<std::string> path={}); Option<std::string> path={});
/** /**
* Import labels from a file * Import labels from a file
* *