diff --git a/src/libutil/logging.cc b/src/libutil/logging.cc index 9caa83efe..0bffe40e3 100644 --- a/src/libutil/logging.cc +++ b/src/libutil/logging.cc @@ -267,6 +267,24 @@ Logger * makeJSONLogger(Descriptor fd) return new JSONLogger(fd); } +Logger * makeJSONLogger(const std::filesystem::path & path) +{ + struct JSONFileLogger : JSONLogger { + AutoCloseFD fd; + + JSONFileLogger(AutoCloseFD && fd) + : JSONLogger(fd.get()) + , fd(std::move(fd)) + { } + }; + + auto fd{toDescriptor(open(path.c_str(), O_CREAT | O_APPEND | O_WRONLY, 0644))}; + if (!fd) + throw SysError("opening log file '%1%'", path); + + return new JSONFileLogger(std::move(fd)); +} + static Logger::Fields getFields(nlohmann::json & json) { Logger::Fields fields; diff --git a/src/libutil/logging.hh b/src/libutil/logging.hh index 21493b969..cadeafea4 100644 --- a/src/libutil/logging.hh +++ b/src/libutil/logging.hh @@ -5,6 +5,8 @@ #include "config.hh" #include "file-descriptor.hh" +#include + #include namespace nix { @@ -185,8 +187,12 @@ extern Logger * logger; Logger * makeSimpleLogger(bool printBuildLogs = true); +Logger * makeTeeLogger(std::vector loggers); + Logger * makeJSONLogger(Descriptor fd); +Logger * makeJSONLogger(const std::filesystem::path & path); + /** * @param source A noun phrase describing the source of the message, e.g. "the builder". */ diff --git a/src/libutil/meson.build b/src/libutil/meson.build index ac701d8fd..d5855442d 100644 --- a/src/libutil/meson.build +++ b/src/libutil/meson.build @@ -158,6 +158,7 @@ sources = files( 'strings.cc', 'suggestions.cc', 'tarfile.cc', + 'tee-logger.cc', 'terminal.cc', 'thread-pool.cc', 'unix-domain-socket.cc', diff --git a/src/libutil/tee-logger.cc b/src/libutil/tee-logger.cc new file mode 100644 index 000000000..7a5115ea7 --- /dev/null +++ b/src/libutil/tee-logger.cc @@ -0,0 +1,102 @@ +#include "logging.hh" + +namespace nix { + +struct TeeLogger : Logger +{ + std::vector loggers; + + TeeLogger(std::vector 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 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 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); + } +}; + +Logger * makeTeeLogger(std::vector loggers) +{ + return new TeeLogger(std::move(loggers)); +} + +} diff --git a/src/nix/main.cc b/src/nix/main.cc index f8f9d03a4..5f83e997c 100644 --- a/src/nix/main.cc +++ b/src/nix/main.cc @@ -485,6 +485,10 @@ void mainWrapped(int argc, char * * argv) if (!args.helpRequested && !args.completions) throw; } + if (auto logFile = getEnv("NIX_LOG_FILE")) { + logger = makeTeeLogger({logger, makeJSONLogger(*logFile)}); + } + if (args.helpRequested) { std::vector subcommand; MultiCommand * command = &args;