feat: make log-format a setting

Vaguely one half of #186.
Fixes #827.

Change-Id: Ie6a296465beb089bf812ea27091648ca6a6a6964
This commit is contained in:
Qyriad
2026-01-06 16:35:49 +01:00
parent c3b70a8968
commit b97b2e858b
15 changed files with 115 additions and 32 deletions
+4 -2
View File
@@ -1,8 +1,8 @@
#include "lix/libmain/common-args.hh"
#include "lix/libutil/args/root.hh"
#include "lix/libutil/config-impl.hh" // IWYU pragma: keep
#include "lix/libutil/error.hh"
#include "lix/libstore/globals.hh"
#include "lix/libmain/loggers.hh"
#include "lix/libutil/logging.hh"
namespace nix {
@@ -62,7 +62,9 @@ MixCommonArgs::MixCommonArgs(const std::string & programName)
.description = "Set the format of log output; one of `raw`, `internal-json`, `bar`, `bar-with-logs`, `multiline` or `multiline-with-logs`.",
.category = loggingCategory,
.labels = {"format"},
.handler = {[](std::string format) { setLogFormat(format); }},
.handler = {[&](std::string format) {
loggerSettings.logFormat.set(format);
}},
});
addFlag({
+6 -18
View File
@@ -1,29 +1,17 @@
#include "lix/libutil/environment-variables.hh"
#include "lix/libmain/loggers.hh"
#include "lix/libmain/progress-bar.hh"
#include "lix/libutil/log-format.hh"
#include "lix/libutil/config-impl.hh" // IWYU pragma: keep
namespace nix {
LogFormat defaultLogFormat = LogFormat::Auto;
[[deprecated]]
LogFormat parseLogFormat(const std::string & logFormatStr) {
if (auto const parsed = LogFormat::parse(logFormatStr)) {
return *parsed;
}
throw Error("setting 'log-format' has an invalid value '%s'", logFormatStr);
}
Logger * makeDefaultLogger() {
return getLoggerByFormat(defaultLogFormat);
}
void setLogFormat(const std::string & logFormatStr) {
setLogFormat(parseLogFormat(logFormatStr));
static Logger * makeDefaultLogger() {
return getLoggerByFormat(loggerSettings.logFormat);
}
void setLogFormat(const LogFormat & logFormat) {
defaultLogFormat = logFormat;
loggerSettings.logFormat.override(logFormat);
createDefaultLogger();
}
@@ -36,7 +24,7 @@ Logger * getLoggerByFormat(LogFormat logFormat)
using enum LogFormatValue;
switch (logFormat) {
case LogFormat::Auto:
return getLoggerByFormat(defaultLogFormat);
return getLoggerByFormat(loggerSettings.logFormat.autoValue);
case LogFormat::Raw:
return makeSimpleLogger(false);
case LogFormat::RawWithLogs:
+1 -3
View File
@@ -8,9 +8,7 @@ namespace nix {
class Logger;
[[deprecated]]
void setLogFormat(const std::string & logFormatStr);
[[deprecated]]
/** Overrides the current log format, and re-creates the current logger. */
void setLogFormat(const LogFormat & logFormat);
void createDefaultLogger();
+4 -1
View File
@@ -200,7 +200,10 @@ LegacyArgs::LegacyArgs(AsyncIoRoot & aio, const std::string & programName,
.longName = "no-build-output",
.shortName = 'Q',
.description = "Do not show build output.",
.handler = {[&]() {setLogFormat(LogFormat::Raw); }},
.handler = {[&]() {
loggerSettings.logFormat.setDefault(loggerSettings.logFormat.get().withoutLogs());
loggerSettings.logFormat.autoValue = loggerSettings.logFormat.get().withoutLogs();
}},
});
addFlag({
+1
View File
@@ -133,6 +133,7 @@ DECLARE_CONFIG_SERIALISER(StringSet)
DECLARE_CONFIG_SERIALISER(StringMap)
DECLARE_CONFIG_SERIALISER(ExperimentalFeatures)
DECLARE_CONFIG_SERIALISER(DeprecatedFeatures)
DECLARE_CONFIG_SERIALISER(LogFormat)
template<typename T>
T BaseSetting<T>::parse(const std::string & str, const ApplyConfigOptions & options) const
+44
View File
@@ -1,4 +1,48 @@
#include "lix/libutil/log-format.hh"
#include "lix/libutil/abstract-setting-to-json.hh" // IWYU pragma: keep
#include "lix/libutil/json.hh"
#include "lix/libutil/log-format.hh"
#include <format>
namespace nix {
template<>
std::string BaseSetting<LogFormat>::to_string() const
{
return std::format("{}", value);
}
template<>
LogFormat
BaseSetting<LogFormat>::parse(const std::string & str, const ApplyConfigOptions & options) const
{
if (auto const parsed = LogFormat::parse(str)) {
return *parsed;
}
throw UsageError("setting '%s' has invalid value '%s'", name, str);
}
void to_json(JSON & j, const LogFormat & self)
{
j = std::format("{}", self);
}
void from_json(const JSON & j, LogFormat & self)
{
std::string asStr = ensureType(j, JSON::value_t::string);
auto const parsed = LogFormat::parse(asStr);
if (!parsed) {
throw Error("invalid json for 'log-format': %s", j);
}
self = *parsed;
}
// Explicitly instantiate the non-specialized templates.
// `abstract-setting-to-json.hh` is IWYU-kept so this line also instantiates that template.
template class BaseSetting<LogFormat>;
}
static_assert(std::formattable<nix::LogFormat, char>);
static_assert(std::formattable<nix::LogFormatValue, char>);
+25
View File
@@ -1,6 +1,7 @@
#pragma once
///@file
#include "lix/libutil/config.hh"
#include "lix/libutil/fmt.hh"
#include "lix/libutil/json-fwd.hh"
@@ -156,6 +157,30 @@ struct json::is_integral_enum<nix::LogFormat> : std::true_type {};
template<>
struct json::is_integral_enum<nix::LogFormatValue> : std::true_type {};
/** Note: you'll have to include `config-impl.hh` when you want to use methods from this type. */
struct LogFormatSetting : public BaseSetting<LogFormat>
{
// I hate global state, man.
LogFormat autoValue = LogFormat::RawWithLogs;
LogFormatSetting(
Config * options,
const LogFormat & def,
const std::string & name,
const std::string & description,
const std::set<std::string> & aliases = {},
bool documentDefault = true,
std::optional<ExperimentalFeature> experimentalFeature = std::nullopt,
bool deprecated = false
) : BaseSetting<LogFormat>(def, true, name, description, aliases, experimentalFeature, deprecated)
{
options->addSetting(this);
}
};
void to_json(JSON & j, const LogFormat & self);
void from_json(const JSON & j, LogFormat & self);
}
template<>
@@ -0,0 +1,11 @@
---
name: log-format
internalName: logFormat
settingType: LogFormatSetting
defaultExpr: 'LogFormat::Auto'
defaultText: auto
---
Set the format of log output; one of `raw`, `internal-json`, `bar`, `bar-with-logs`, `multiline` or `multiline-with-logs`.
For legacy reasons, the default value "auto" makes the actual log format depend on which command you're using.
The legacy `nix-` CLI will use `raw-with-logs` (or `raw` with `-Q`/`--no-build-output`), and `nix3` commands will use `bar-with-logs`.
+1
View File
@@ -1,4 +1,5 @@
#include "c-calls.hh"
#include "lix/libutil/config-impl.hh"
#include "lix/libutil/environment-variables.hh"
#include "lix/libutil/file-descriptor.hh"
#include "lix/libutil/logging.hh"
+1 -1
View File
@@ -4,7 +4,7 @@
#include "lix/libutil/types.hh"
#include "lix/libutil/error.hh"
#include "lix/libutil/config.hh"
#include "lix/libutil/log-format.hh"
#include "lix/libutil/log-format.hh" // IWYU pragma: keep
#include "result.hh"
#include "serialise.hh"
#include <kj/async.h>
+3
View File
@@ -282,7 +282,10 @@ libutil_settings_headers += custom_target(
)
logging_setting_definitions = files(
# keep-sorted start
'logging-settings/log-format.md',
'logging-settings/show-trace.md',
# keep-sorted end
)
libutil_settings_headers += custom_target(
command : [
+7 -2
View File
@@ -228,7 +228,9 @@ struct NixArgs : virtual MultiCommand, virtual MixCommonArgs, virtual RootArgs
.shortName = 'L',
.description = "Print full build logs on standard error.",
.category = loggingCategory,
.handler = {[&]() { logger->setPrintBuildLogs(true); }},
.handler = {[&]() {
loggerSettings.logFormat.setDefault(loggerSettings.logFormat.get().withLogs());
}},
.experimentalFeature = Xp::NixCommand,
});
@@ -500,7 +502,7 @@ int mainWrapped(AsyncIoRoot & aio, int argc, char ** argv)
evalSettings.pureEval.setDefault(true);
setLogFormat(LogFormat::BarWithLogs);
loggerSettings.logFormat.autoValue = LogFormat::BarWithLogs;
// FIXME: stop messing about with log verbosity depending on if it is interactive use
if (isatty(STDERR_FILENO)) {
@@ -578,6 +580,9 @@ int mainWrapped(AsyncIoRoot & aio, int argc, char ** argv)
if (!args.helpRequested && !args.completions) throw;
}
// HACK: after args.parseCmdline() we re-create the default logger, to apply --option flags.
createDefaultLogger();
if (args.completions) {
switch (args.completions->type) {
case Completions::Type::Normal:
+3 -2
View File
@@ -188,8 +188,9 @@ static int main_nix_prefetch_url(AsyncIoRoot & aio, std::string programName, Str
if (args.size() > 2)
throw UsageError("too many arguments");
if (isOutputARealTerminal(StandardOutputStream::Stderr))
setLogFormat(LogFormat::Bar);
if (isOutputARealTerminal(StandardOutputStream::Stderr)) {
loggerSettings.logFormat.autoValue = LogFormat::Bar;
}
auto store = aio.blockOn(openStore());
auto evaluator = std::make_unique<Evaluator>(aio, myArgs.searchPath, store);
+3 -3
View File
@@ -131,9 +131,9 @@ def test_completions_flake_update(nix: Nix, files: Path):
def test_flag_completion(nix: Nix):
nix.env["NIX_GET_COMPLETIONS"] = "2"
res = nix.nix(["build", "--log-form"]).run().ok()
assert "--log-format" in res.stdout_plain
assert "Set the format of log output; one of" in res.stdout_plain
res = nix.nix(["build", "--dry"]).run().ok()
assert "--dry-run" in res.stdout_plain
assert "Show what this command would do without doing it" in res.stdout_plain
def test_option_completion(nix: Nix):
+1
View File
@@ -3,6 +3,7 @@
#include "lix/libexpr/eval.hh"
#include "lix/libmain/progress-bar.hh"
#include "lix/libmain/loggers.hh"
#include "lix/libutil/config-impl.hh"
#include "lix/libutil/logging.hh"
#include "lix/libmain/shared.hh"