1
0
Fork 0
mirror of https://github.com/NixOS/nix synced 2025-07-07 10:11:47 +02:00

Merge pull request #13 from DeterminateSystems/logger-improvements

Logger improvements
This commit is contained in:
Eelco Dolstra 2025-03-27 18:16:08 +00:00 committed by GitHub
commit afcce6f73f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 369 additions and 31 deletions

View file

@ -129,7 +129,7 @@
- [Determinate Nix Release Notes](release-notes-determinate/index.md) - [Determinate Nix Release Notes](release-notes-determinate/index.md)
- [Changes between Nix and Determinate Nix](release-notes-determinate/changes.md) - [Changes between Nix and Determinate Nix](release-notes-determinate/changes.md)
- [Release 3.0.0 (2025-03-04)](release-notes-determinate/rl-3.0.0.md) - [Release 3.0.0 (2025-03-04)](release-notes-determinate/rl-3.0.0.md)
- [Release 3.1.0 (2025-??-??)](release-notes-determinate/rl-3.1.0.md) - [Release 3.1.0 (2025-03-27)](release-notes-determinate/rl-3.1.0.md)
- [Nix Release Notes](release-notes/index.md) - [Nix Release Notes](release-notes/index.md)
{{#include ./SUMMARY-rl-next.md}} {{#include ./SUMMARY-rl-next.md}}
- [Release 2.27 (2025-03-03)](release-notes/rl-2.27.md) - [Release 2.27 (2025-03-03)](release-notes/rl-2.27.md)

View file

@ -5,3 +5,5 @@ This section lists the differences between upstream Nix 2.24 and Determinate Nix
* In Determinate Nix, flakes are stable. You no longer need to enable the `flakes` experimental feature. * In Determinate Nix, flakes are stable. You no longer need to enable the `flakes` experimental feature.
* In Determinate Nix, the new Nix CLI (i.e. the `nix` command) is stable. You no longer need to enable the `nix-command` experimental feature. * In Determinate Nix, the new Nix CLI (i.e. the `nix` command) is stable. You no longer need to enable the `nix-command` experimental feature.
* Determinate Nix has a setting [`json-log-path`](@docroot@/command-ref/conf-file.md#conf-json-log-path) to send a copy of all Nix log messages (in JSON format) to a file or Unix domain socket.

View file

@ -3,3 +3,5 @@
* Initial release of Determinate Nix. * Initial release of Determinate Nix.
* Based on [upstream Nix 2.26.2](../release-notes/rl-2.26.md). * Based on [upstream Nix 2.26.2](../release-notes/rl-2.26.md).
* New setting `json-log-path` that sends a copy of all Nix log messages (in JSON format) to a file or Unix domain socket.

View file

@ -1,3 +1,3 @@
# Release 3.1.0 (2025-??-??) # Release 3.1.0 (2025-03-27)
* Based on [upstream Nix 2.27.1](../release-notes/rl-2.27.md). * Based on [upstream Nix 2.27.1](../release-notes/rl-2.27.md).

View file

@ -1,8 +1,49 @@
#include "build-result.hh" #include "build-result.hh"
#include <nlohmann/json.hpp>
namespace nix { namespace nix {
bool BuildResult::operator==(const BuildResult &) const noexcept = default; bool BuildResult::operator==(const BuildResult &) const noexcept = default;
std::strong_ordering BuildResult::operator<=>(const BuildResult &) const noexcept = default; std::strong_ordering BuildResult::operator<=>(const BuildResult &) const noexcept = default;
void to_json(nlohmann::json & json, const BuildResult & buildResult)
{
json = nlohmann::json::object();
json["status"] = BuildResult::statusToString(buildResult.status);
if (buildResult.errorMsg != "")
json["errorMsg"] = buildResult.errorMsg;
if (buildResult.timesBuilt)
json["timesBuilt"] = buildResult.timesBuilt;
if (buildResult.isNonDeterministic)
json["isNonDeterministic"] = buildResult.isNonDeterministic;
if (buildResult.startTime)
json["startTime"] = buildResult.startTime;
if (buildResult.stopTime)
json["stopTime"] = buildResult.stopTime;
}
void to_json(nlohmann::json & json, const KeyedBuildResult & buildResult)
{
to_json(json, (const BuildResult &) buildResult);
auto path = nlohmann::json::object();
std::visit(
overloaded{
[&](const DerivedPathOpaque & opaque) { path["opaque"] = opaque.path.to_string(); },
[&](const DerivedPathBuilt & drv) {
path["drvPath"] = drv.drvPath->getBaseStorePath().to_string();
path["outputs"] = drv.outputs;
auto outputs = nlohmann::json::object();
for (auto & [name, output] : buildResult.builtOutputs)
outputs[name] = {
{"path", output.outPath.to_string()},
{"signatures", output.signatures},
};
json["builtOutputs"] = std::move(outputs);
},
},
buildResult.path.raw());
json["path"] = std::move(path);
}
} }

View file

@ -8,6 +8,8 @@
#include <chrono> #include <chrono>
#include <optional> #include <optional>
#include <nlohmann/json_fwd.hpp>
namespace nix { namespace nix {
struct BuildResult struct BuildResult
@ -46,8 +48,8 @@ struct BuildResult
*/ */
std::string errorMsg; std::string errorMsg;
std::string toString() const { static std::string_view statusToString(Status status)
auto strStatus = [&]() { {
switch (status) { switch (status) {
case Built: return "Built"; case Built: return "Built";
case Substituted: return "Substituted"; case Substituted: return "Substituted";
@ -66,8 +68,12 @@ struct BuildResult
case NoSubstituters: return "NoSubstituters"; case NoSubstituters: return "NoSubstituters";
default: return "Unknown"; default: return "Unknown";
}; };
}(); }
return strStatus + ((errorMsg == "") ? "" : " : " + errorMsg);
std::string toString() const {
return
std::string(statusToString(status))
+ ((errorMsg == "") ? "" : " : " + errorMsg);
} }
/** /**
@ -130,4 +136,7 @@ struct KeyedBuildResult : BuildResult
{ } { }
}; };
void to_json(nlohmann::json & json, const BuildResult & buildResult);
void to_json(nlohmann::json & json, const KeyedBuildResult & buildResult);
} }

View file

@ -1540,6 +1540,14 @@ Goal::Done DerivationGoal::done(
fs << worker.store.printStorePath(drvPath) << "\t" << buildResult.toString() << std::endl; fs << worker.store.printStorePath(drvPath) << "\t" << buildResult.toString() << std::endl;
} }
logger->result(
act ? act->id : getCurActivity(),
resBuildResult,
nlohmann::json(
KeyedBuildResult(
buildResult,
DerivedPath::Built{.drvPath = makeConstantStorePathRef(drvPath), .outputs = wantedOutputs})));
return amDone(buildResult.success() ? ecSuccess : ecFailed, std::move(ex)); return amDone(buildResult.success() ? ecSuccess : ecFailed, std::move(ex));
} }

View file

@ -3,8 +3,11 @@
#include "nar-info.hh" #include "nar-info.hh"
#include "finally.hh" #include "finally.hh"
#include "signals.hh" #include "signals.hh"
#include <coroutine> #include <coroutine>
#include <nlohmann/json.hpp>
namespace nix { namespace nix {
PathSubstitutionGoal::PathSubstitutionGoal(const StorePath & storePath, Worker & worker, RepairFlag repair, std::optional<ContentAddress> ca) PathSubstitutionGoal::PathSubstitutionGoal(const StorePath & storePath, Worker & worker, RepairFlag repair, std::optional<ContentAddress> ca)
@ -35,6 +38,15 @@ Goal::Done PathSubstitutionGoal::done(
debug(*errorMsg); debug(*errorMsg);
buildResult.errorMsg = *errorMsg; buildResult.errorMsg = *errorMsg;
} }
logger->result(
getCurActivity(),
resBuildResult,
nlohmann::json(
KeyedBuildResult(
buildResult,
DerivedPath::Opaque{storePath})));
return amDone(result); return amDone(result);
} }

View file

@ -15,6 +15,7 @@
#include "derivations.hh" #include "derivations.hh"
#include "args.hh" #include "args.hh"
#include "git.hh" #include "git.hh"
#include "logging.hh"
#ifndef _WIN32 // TODO need graceful async exit support on Windows? #ifndef _WIN32 // TODO need graceful async exit support on Windows?
# include "monitor-fd.hh" # include "monitor-fd.hh"
@ -1049,6 +1050,7 @@ void processConnection(
if (!recursive) { if (!recursive) {
prevLogger_ = std::move(logger); prevLogger_ = std::move(logger);
logger = std::move(tunnelLogger_); logger = std::move(tunnelLogger_);
applyJSONLogger();
} }
unsigned int opCount = 0; unsigned int opCount = 0;

View file

@ -84,9 +84,7 @@ ref<RemoteStore::Connection> UDSRemoteStore::openConnection()
auto conn = make_ref<Connection>(); auto conn = make_ref<Connection>();
/* Connect to a daemon that does the privileged work for us. */ /* Connect to a daemon that does the privileged work for us. */
conn->fd = createUnixDomainSocket(); conn->fd = nix::connect(path);
nix::connect(toSocket(conn->fd.get()), path);
conn->from.fd = conn->fd.get(); conn->from.fd = conn->fd.get();
conn->to.fd = conn->fd.get(); conn->to.fd = conn->fd.get();

View file

@ -2654,6 +2654,12 @@ SingleDrvOutputs LocalDerivationGoal::registerOutputs()
worker.store.printStorePath(drvPath), worker.store.printStorePath(drvPath),
wanted.to_string(HashFormat::SRI, true), wanted.to_string(HashFormat::SRI, true),
got.to_string(HashFormat::SRI, true))); got.to_string(HashFormat::SRI, true)));
act->result(resHashMismatch,
{
{"storePath", worker.store.printStorePath(drvPath)},
{"wanted", wanted},
{"got", got},
});
} }
if (!newInfo0.references.empty()) { if (!newInfo0.references.empty()) {
auto numViolations = newInfo.references.size(); auto numViolations = newInfo.references.size();

View file

@ -16,6 +16,8 @@
#include <sys/stat.h> #include <sys/stat.h>
#include <fcntl.h> #include <fcntl.h>
#include <nlohmann/json.hpp>
#include <sodium.h> #include <sodium.h>
namespace nix { namespace nix {
@ -467,4 +469,13 @@ std::string_view printHashAlgo(HashAlgorithm ha)
} }
} }
void to_json(nlohmann::json & json, const Hash & hash)
{
json = nlohmann::json::object(
{
{"algo", printHashAlgo(hash.algo)},
{"base16", hash.to_string(HashFormat::Base16, false)},
});
}
} }

View file

@ -6,6 +6,8 @@
#include "serialise.hh" #include "serialise.hh"
#include "file-system.hh" #include "file-system.hh"
#include <nlohmann/json_fwd.hpp>
namespace nix { namespace nix {
@ -210,6 +212,10 @@ std::optional<HashAlgorithm> parseHashAlgoOpt(std::string_view s);
*/ */
std::string_view printHashAlgo(HashAlgorithm ha); std::string_view printHashAlgo(HashAlgorithm ha);
/**
* Write a JSON serialisation of the format `{"algo":"<sha1|...>","base16":"<hex>"}`.
*/
void to_json(nlohmann::json & json, const Hash & hash);
union Ctx; union Ctx;

View file

@ -6,6 +6,8 @@
#include "config-global.hh" #include "config-global.hh"
#include "source-path.hh" #include "source-path.hh"
#include "position.hh" #include "position.hh"
#include "sync.hh"
#include "unix-domain-socket.hh"
#include <atomic> #include <atomic>
#include <sstream> #include <sstream>
@ -181,8 +183,12 @@ void to_json(nlohmann::json & json, std::shared_ptr<Pos> pos)
struct JSONLogger : Logger { struct JSONLogger : Logger {
Descriptor fd; Descriptor fd;
bool includeNixPrefix;
JSONLogger(Descriptor fd) : fd(fd) { } JSONLogger(Descriptor fd, bool includeNixPrefix)
: fd(fd)
, includeNixPrefix(includeNixPrefix)
{ }
bool isVerbose() override { bool isVerbose() override {
return true; return true;
@ -201,9 +207,33 @@ struct JSONLogger : Logger {
unreachable(); unreachable();
} }
struct State
{
bool enabled = true;
};
Sync<State> _state;
void write(const nlohmann::json & json) void write(const nlohmann::json & json)
{ {
writeLine(fd, "@nix " + json.dump(-1, ' ', false, nlohmann::json::error_handler_t::replace)); auto line =
(includeNixPrefix ? "@nix " : "") +
json.dump(-1, ' ', false, nlohmann::json::error_handler_t::replace);
/* Acquire a lock to prevent log messages from clobbering each
other. */
try {
auto state(_state.lock());
if (state->enabled)
writeLine(fd, line);
} catch (...) {
bool enabled = false;
std::swap(_state.lock()->enabled, enabled);
if (enabled) {
ignoreExceptionExceptInterrupt();
logger->warn("disabling JSON logger due to write errors");
}
}
} }
void log(Verbosity lvl, std::string_view s) override void log(Verbosity lvl, std::string_view s) override
@ -273,11 +303,61 @@ struct JSONLogger : Logger {
addFields(json, fields); addFields(json, fields);
write(json); write(json);
} }
void result(ActivityId act, ResultType type, const nlohmann::json & j) override
{
nlohmann::json json;
json["action"] = "result";
json["id"] = act;
json["type"] = type;
json["payload"] = j;
write(json);
}
}; };
std::unique_ptr<Logger> makeJSONLogger(Descriptor fd) std::unique_ptr<Logger> makeJSONLogger(Descriptor fd, bool includeNixPrefix)
{ {
return std::make_unique<JSONLogger>(fd); return std::make_unique<JSONLogger>(fd, includeNixPrefix);
}
std::unique_ptr<Logger> makeJSONLogger(const std::filesystem::path & path, bool includeNixPrefix)
{
struct JSONFileLogger : JSONLogger {
AutoCloseFD fd;
JSONFileLogger(AutoCloseFD && fd, bool includeNixPrefix)
: JSONLogger(fd.get(), includeNixPrefix)
, fd(std::move(fd))
{ }
};
AutoCloseFD fd =
std::filesystem::is_socket(path)
? connect(path)
: toDescriptor(open(path.c_str(), O_CREAT | O_APPEND | O_WRONLY, 0644));
if (!fd)
throw SysError("opening log file %1%", path);
return std::make_unique<JSONFileLogger>(std::move(fd), includeNixPrefix);
}
void applyJSONLogger()
{
if (!loggerSettings.jsonLogPath.get().empty()) {
try {
std::vector<std::unique_ptr<Logger>> loggers;
loggers.push_back(makeJSONLogger(std::filesystem::path(loggerSettings.jsonLogPath.get()), false));
try {
logger = makeTeeLogger(std::move(logger), std::move(loggers));
} catch (...) {
// `logger` is now gone so give up.
abort();
}
} catch (...) {
ignoreExceptionExceptInterrupt();
}
}
} }
static Logger::Fields getFields(nlohmann::json & json) static Logger::Fields getFields(nlohmann::json & json)

View file

@ -6,6 +6,8 @@
#include "file-descriptor.hh" #include "file-descriptor.hh"
#include "finally.hh" #include "finally.hh"
#include <filesystem>
#include <nlohmann/json_fwd.hpp> #include <nlohmann/json_fwd.hpp>
namespace nix { namespace nix {
@ -37,6 +39,8 @@ typedef enum {
resSetExpected = 106, resSetExpected = 106,
resPostBuildLogLine = 107, resPostBuildLogLine = 107,
resFetchStatus = 108, resFetchStatus = 108,
resHashMismatch = 109,
resBuildResult = 110,
} ResultType; } ResultType;
typedef uint64_t ActivityId; typedef uint64_t ActivityId;
@ -49,6 +53,14 @@ struct LoggerSettings : Config
Whether Nix should print out a stack trace in case of Nix Whether Nix should print out a stack trace in case of Nix
expression evaluation errors. expression evaluation errors.
)"}; )"};
Setting<Path> jsonLogPath{
this, "", "json-log-path",
R"(
A path to which JSON records of Nix's log output will be
written, in the same format as `--log-format internal-json`
(without the `@nix ` prefixes on each line).
)"};
}; };
extern LoggerSettings loggerSettings; extern LoggerSettings loggerSettings;
@ -117,6 +129,8 @@ public:
virtual void result(ActivityId act, ResultType type, const Fields & fields) { }; virtual void result(ActivityId act, ResultType type, const Fields & fields) { };
virtual void result(ActivityId act, ResultType type, const nlohmann::json & json) { };
virtual void writeToStdout(std::string_view s); virtual void writeToStdout(std::string_view s);
template<typename... Args> template<typename... Args>
@ -169,6 +183,11 @@ struct Activity
void setExpected(ActivityType type2, uint64_t expected) const void setExpected(ActivityType type2, uint64_t expected) const
{ result(resSetExpected, type2, expected); } { result(resSetExpected, type2, expected); }
void result(ResultType type, const nlohmann::json & json) const
{
logger.result(id, type, json);
}
template<typename... Args> template<typename... Args>
void result(ResultType type, const Args & ... args) const void result(ResultType type, const Args & ... args) const
{ {
@ -196,7 +215,20 @@ extern std::unique_ptr<Logger> logger;
std::unique_ptr<Logger> makeSimpleLogger(bool printBuildLogs = true); std::unique_ptr<Logger> makeSimpleLogger(bool printBuildLogs = true);
std::unique_ptr<Logger> makeJSONLogger(Descriptor fd); /**
* Create a logger that sends log messages to `mainLogger` and the
* list of loggers in `extraLoggers`. Only `mainLogger` is used for
* writing to stdout and getting user input.
*/
std::unique_ptr<Logger> makeTeeLogger(
std::unique_ptr<Logger> mainLogger,
std::vector<std::unique_ptr<Logger>> && extraLoggers);
std::unique_ptr<Logger> makeJSONLogger(Descriptor fd, bool includeNixPrefix = true);
std::unique_ptr<Logger> makeJSONLogger(const std::filesystem::path & path, bool includeNixPrefix = true);
void applyJSONLogger();
/** /**
* @param source A noun phrase describing the source of the message, e.g. "the builder". * @param source A noun phrase describing the source of the message, e.g. "the builder".

View file

@ -166,6 +166,7 @@ sources = files(
'strings.cc', 'strings.cc',
'suggestions.cc', 'suggestions.cc',
'tarfile.cc', 'tarfile.cc',
'tee-logger.cc',
'terminal.cc', 'terminal.cc',
'thread-pool.cc', 'thread-pool.cc',
'union-source-accessor.cc', 'union-source-accessor.cc',

113
src/libutil/tee-logger.cc Normal file
View file

@ -0,0 +1,113 @@
#include "logging.hh"
namespace nix {
struct TeeLogger : Logger
{
std::vector<std::unique_ptr<Logger>> loggers;
TeeLogger(std::vector<std::unique_ptr<Logger>> && loggers)
: loggers(std::move(loggers))
{
}
void stop() override
{
for (auto & logger : loggers)
logger->stop();
};
void pause() override
{
for (auto & logger : loggers)
logger->pause();
};
void resume() override
{
for (auto & logger : loggers)
logger->resume();
};
void log(Verbosity lvl, std::string_view s) override
{
for (auto & logger : loggers)
logger->log(lvl, s);
}
void logEI(const ErrorInfo & ei) override
{
for (auto & logger : loggers)
logger->logEI(ei);
}
void startActivity(
ActivityId act,
Verbosity lvl,
ActivityType type,
const std::string & s,
const Fields & fields,
ActivityId parent) override
{
for (auto & logger : loggers)
logger->startActivity(act, lvl, type, s, fields, parent);
}
void stopActivity(ActivityId act) override
{
for (auto & logger : loggers)
logger->stopActivity(act);
}
void result(ActivityId act, ResultType type, const Fields & fields) override
{
for (auto & logger : loggers)
logger->result(act, type, fields);
}
void result(ActivityId act, ResultType type, const nlohmann::json & json) override
{
for (auto & logger : loggers)
logger->result(act, type, json);
}
void writeToStdout(std::string_view s) override
{
for (auto & logger : loggers) {
/* Let only the first logger write to stdout to avoid
duplication. This means that the first logger needs to
be the one managing stdout/stderr
(e.g. `ProgressBar`). */
logger->writeToStdout(s);
break;
}
}
std::optional<char> ask(std::string_view s) override
{
for (auto & logger : loggers) {
auto c = logger->ask(s);
if (c)
return c;
}
return std::nullopt;
}
void setPrintBuildLogs(bool printBuildLogs) override
{
for (auto & logger : loggers)
logger->setPrintBuildLogs(printBuildLogs);
}
};
std::unique_ptr<Logger>
makeTeeLogger(std::unique_ptr<Logger> mainLogger, std::vector<std::unique_ptr<Logger>> && extraLoggers)
{
std::vector<std::unique_ptr<Logger>> allLoggers;
allLoggers.push_back(std::move(mainLogger));
for (auto & l : extraLoggers)
allLoggers.push_back(std::move(l));
return std::make_unique<TeeLogger>(std::move(allLoggers));
}
}

View file

@ -114,4 +114,11 @@ void connect(Socket fd, const std::string & path)
bindConnectProcHelper("connect", ::connect, fd, path); bindConnectProcHelper("connect", ::connect, fd, path);
} }
AutoCloseFD connect(const std::filesystem::path & path)
{
auto fd = createUnixDomainSocket();
nix::connect(toSocket(fd.get()), path);
return fd;
}
} }

View file

@ -80,4 +80,9 @@ void bind(Socket fd, const std::string & path);
*/ */
void connect(Socket fd, const std::string & path); void connect(Socket fd, const std::string & path);
/**
* Connect to a Unix domain socket.
*/
AutoCloseFD connect(const std::filesystem::path & path);
} }

View file

@ -5,6 +5,7 @@
#include "eval.hh" #include "eval.hh"
#include "eval-settings.hh" #include "eval-settings.hh"
#include "globals.hh" #include "globals.hh"
#include "config-global.hh"
#include "legacy.hh" #include "legacy.hh"
#include "shared.hh" #include "shared.hh"
#include "store-api.hh" #include "store-api.hh"
@ -486,6 +487,8 @@ void mainWrapped(int argc, char * * argv)
if (!args.helpRequested && !args.completions) throw; if (!args.helpRequested && !args.completions) throw;
} }
applyJSONLogger();
if (args.helpRequested) { if (args.helpRequested) {
std::vector<std::string> subcommand; std::vector<std::string> subcommand;
MultiCommand * command = &args; MultiCommand * command = &args;