Improved logging abstraction

This also gets rid of --log-type, since the nested log type isn't
useful in a multi-threaded situation, and nobody cares about the
"pretty" log type.
This commit is contained in:
Eelco Dolstra 2016-04-25 15:26:07 +02:00
parent c879a20850
commit 41633f9f73
29 changed files with 394 additions and 456 deletions

View file

@ -31,7 +31,6 @@
<arg><option>-K</option></arg> <arg><option>-K</option></arg>
<arg><option>--fallback</option></arg> <arg><option>--fallback</option></arg>
<arg><option>--readonly-mode</option></arg> <arg><option>--readonly-mode</option></arg>
<arg><option>--log-type</option> <replaceable>type</replaceable></arg>
<arg><option>--show-trace</option></arg> <arg><option>--show-trace</option></arg>
<arg> <arg>
<option>-I</option> <option>-I</option>

View file

@ -201,61 +201,6 @@
</varlistentry> </varlistentry>
<varlistentry xml:id="opt-log-type"><term><option>--log-type</option>
<replaceable>type</replaceable></term>
<listitem>
<para>This option determines how the output written to standard
error is formatted. Nixs diagnostic messages are typically
<emphasis>nested</emphasis>. For instance, when tracing Nix
expression evaluation (<command>nix-env -vvvvv</command>, messages
from subexpressions are nested inside their parent expressions. Nix
builder output is also often nested. For instance, the Nix Packages
generic builder nests the various build tasks (unpack, configure,
compile, etc.), and the GNU Make in <literal>stdenv-linux</literal>
has been patched to provide nesting for recursive Make
invocations.</para>
<para><replaceable>type</replaceable> can be one of the
following:
<variablelist>
<varlistentry><term><literal>pretty</literal></term>
<listitem><para>Pretty-print the output, indicating different
nesting levels using spaces. This is the
default.</para></listitem>
</varlistentry>
<varlistentry><term><literal>escapes</literal></term>
<listitem><para>Indicate nesting using escape codes that can be
interpreted by the <command>nix-log2xml</command> tool in the
Nix source distribution. The resulting XML file can be fed into
the <command>log2html.xsl</command> stylesheet to create an HTML
file that can be browsed interactively, using JavaScript to
expand and collapse parts of the output.</para></listitem>
</varlistentry>
<varlistentry><term><literal>flat</literal></term>
<listitem><para>Remove all nesting.</para></listitem>
</varlistentry>
</variablelist>
</para>
</listitem>
</varlistentry>
<varlistentry><term><option>--arg</option> <replaceable>name</replaceable> <replaceable>value</replaceable></term> <varlistentry><term><option>--arg</option> <replaceable>name</replaceable> <replaceable>value</replaceable></term>
<listitem><para>This option is accepted by <listitem><para>This option is accepted by

View file

@ -110,13 +110,6 @@ for (my $n = 0; $n < scalar @ARGV; $n++) {
$n += 2; $n += 2;
} }
elsif ($arg eq "--log-type") {
$n++;
die "$0: $arg requires an argument\n" unless $n < scalar @ARGV;
push @instArgs, ($arg, $ARGV[$n]);
push @buildArgs, ($arg, $ARGV[$n]);
}
elsif ($arg eq "--option") { elsif ($arg eq "--option") {
die "$0: $arg requires two arguments\n" unless $n + 2 < scalar @ARGV; die "$0: $arg requires two arguments\n" unless $n + 2 < scalar @ARGV;
push @instArgs, ($arg, $ARGV[$n + 1], $ARGV[$n + 2]); push @instArgs, ($arg, $ARGV[$n + 1], $ARGV[$n + 2]);
@ -124,7 +117,7 @@ for (my $n = 0; $n < scalar @ARGV; $n++) {
$n += 2; $n += 2;
} }
elsif ($arg eq "--max-jobs" || $arg eq "-j" || $arg eq "--max-silent-time" || $arg eq "--log-type" || $arg eq "--cores" || $arg eq "--timeout" || $arg eq '--add-root') { elsif ($arg eq "--max-jobs" || $arg eq "-j" || $arg eq "--max-silent-time" || $arg eq "--cores" || $arg eq "--timeout" || $arg eq '--add-root') {
$n++; $n++;
die "$0: $arg requires an argument\n" unless $n < scalar @ARGV; die "$0: $arg requires an argument\n" unless $n < scalar @ARGV;
push @buildArgs, ($arg, $ARGV[$n]); push @buildArgs, ($arg, $ARGV[$n]);

View file

@ -641,7 +641,7 @@ void EvalState::evalFile(const Path & path, Value & v)
return; return;
} }
startNest(nest, lvlTalkative, format("evaluating file %1%") % path2); Activity act(*logger, lvlTalkative, format("evaluating file %1%") % path2);
Expr * e = parseExprFromFile(checkSourcePath(path2)); Expr * e = parseExprFromFile(checkSourcePath(path2));
try { try {
eval(e, v); eval(e, v);

View file

@ -290,7 +290,7 @@ static void getDerivations(EvalState & state, Value & vIn,
attrs.insert(std::pair<string, Symbol>(i.name, i.name)); attrs.insert(std::pair<string, Symbol>(i.name, i.name));
for (auto & i : attrs) { for (auto & i : attrs) {
startNest(nest, lvlDebug, format("evaluating attribute %1%") % i.first); Activity act(*logger, lvlDebug, format("evaluating attribute %1%") % i.first);
string pathPrefix2 = addToPath(pathPrefix, i.first); string pathPrefix2 = addToPath(pathPrefix, i.first);
Value & v2(*v.attrs->find(i.second)->value); Value & v2(*v.attrs->find(i.second)->value);
if (combineChannels) if (combineChannels)
@ -310,8 +310,7 @@ static void getDerivations(EvalState & state, Value & vIn,
else if (v.isList()) { else if (v.isList()) {
for (unsigned int n = 0; n < v.listSize(); ++n) { for (unsigned int n = 0; n < v.listSize(); ++n) {
startNest(nest, lvlDebug, Activity act(*logger, lvlDebug, "evaluating list element");
format("evaluating list element"));
string pathPrefix2 = addToPath(pathPrefix, (format("%1%") % n).str()); string pathPrefix2 = addToPath(pathPrefix, (format("%1%") % n).str());
if (getDerivation(state, *v.listElems()[n], pathPrefix2, drvs, done, ignoreAssertionFailures)) if (getDerivation(state, *v.listElems()[n], pathPrefix2, drvs, done, ignoreAssertionFailures))
getDerivations(state, *v.listElems()[n], pathPrefix2, autoArgs, drvs, done, ignoreAssertionFailures); getDerivations(state, *v.listElems()[n], pathPrefix2, autoArgs, drvs, done, ignoreAssertionFailures);

View file

@ -124,7 +124,7 @@ static void prim_scopedImport(EvalState & state, const Pos & pos, Value * * args
env->values[displ++] = attr.value; env->values[displ++] = attr.value;
} }
startNest(nest, lvlTalkative, format("evaluating file %1%") % path); Activity act(*logger, lvlTalkative, format("evaluating file %1%") % path);
Expr * e = state.parseExprFromFile(resolveExprPath(path), staticEnv); Expr * e = state.parseExprFromFile(resolveExprPath(path), staticEnv);
e->eval(state, *env, v); e->eval(state, *env, v);
@ -284,7 +284,7 @@ typedef list<Value *> ValueList;
static void prim_genericClosure(EvalState & state, const Pos & pos, Value * * args, Value & v) static void prim_genericClosure(EvalState & state, const Pos & pos, Value * * args, Value & v)
{ {
startNest(nest, lvlDebug, "finding dependencies"); Activity act(*logger, lvlDebug, "finding dependencies");
state.forceAttrs(*args[0], pos); state.forceAttrs(*args[0], pos);
@ -457,7 +457,7 @@ void prim_valueSize(EvalState & state, const Pos & pos, Value * * args, Value &
derivation. */ derivation. */
static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * args, Value & v) static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * args, Value & v)
{ {
startNest(nest, lvlVomit, "evaluating derivation"); Activity act(*logger, lvlVomit, "evaluating derivation");
state.forceAttrs(*args[0], pos); state.forceAttrs(*args[0], pos);
@ -494,7 +494,7 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
for (auto & i : *args[0]->attrs) { for (auto & i : *args[0]->attrs) {
if (i.name == state.sIgnoreNulls) continue; if (i.name == state.sIgnoreNulls) continue;
string key = i.name; string key = i.name;
startNest(nest, lvlVomit, format("processing attribute %1%") % key); Activity act(*logger, lvlVomit, format("processing attribute %1%") % key);
try { try {

View file

@ -18,15 +18,6 @@ MixCommonArgs::MixCommonArgs(const string & programName)
verbosity = lvlDebug; verbosity = lvlDebug;
}); });
mkFlag1(0, "log-type", "type", "set logging format ('pretty', 'flat', 'systemd')",
[](std::string s) {
if (s == "pretty") logType = ltPretty;
else if (s == "escapes") logType = ltEscapes;
else if (s == "flat") logType = ltFlat;
else if (s == "systemd") logType = ltSystemd;
else throw UsageError("unknown log type");
});
mkFlag(0, "option", {"name", "value"}, "set a Nix configuration option (overriding nix.conf)", 2, mkFlag(0, "option", {"name", "value"}, "set a Nix configuration option (overriding nix.conf)", 2,
[](Strings ss) { [](Strings ss) {
auto name = ss.front(); ss.pop_front(); auto name = ss.front(); ss.pop_front();

View file

@ -111,8 +111,7 @@ void initNix()
std::cerr.rdbuf()->pubsetbuf(buf, sizeof(buf)); std::cerr.rdbuf()->pubsetbuf(buf, sizeof(buf));
#endif #endif
if (getEnv("IN_SYSTEMD") == "1") logger = makeDefaultLogger();
logType = ltSystemd;
/* Initialise OpenSSL locking. */ /* Initialise OpenSSL locking. */
opensslLocks = std::vector<std::mutex>(CRYPTO_num_locks()); opensslLocks = std::vector<std::mutex>(CRYPTO_num_locks());

View file

@ -1657,7 +1657,7 @@ void DerivationGoal::startBuilder()
nrRounds > 1 ? "building path(s) %1% (round %2%/%3%)" : nrRounds > 1 ? "building path(s) %1% (round %2%/%3%)" :
"building path(s) %1%"); "building path(s) %1%");
f.exceptions(boost::io::all_error_bits ^ boost::io::too_many_args_bit); f.exceptions(boost::io::all_error_bits ^ boost::io::too_many_args_bit);
startNest(nest, lvlInfo, f % showPaths(missingPaths) % curRound % nrRounds); printMsg(lvlInfo, f % showPaths(missingPaths) % curRound % nrRounds);
/* Right platform? */ /* Right platform? */
if (!drv->canBuildLocally()) { if (!drv->canBuildLocally()) {
@ -2192,8 +2192,6 @@ void DerivationGoal::runChild()
try { /* child */ try { /* child */
logType = ltFlat;
commonChildInit(builderOut); commonChildInit(builderOut);
#if __linux__ #if __linux__
@ -2535,7 +2533,6 @@ void DerivationGoal::runChild()
/* Execute the program. This should not return. */ /* Execute the program. This should not return. */
if (drv->isBuiltin()) { if (drv->isBuiltin()) {
try { try {
logType = ltFlat;
if (drv->builder == "builtin:fetchurl") if (drv->builder == "builtin:fetchurl")
builtinFetchurl(*drv); builtinFetchurl(*drv);
else else
@ -2667,8 +2664,7 @@ void DerivationGoal::registerOutputs()
rewritten = true; rewritten = true;
} }
startNest(nest, lvlTalkative, Activity act(*logger, lvlTalkative, format("scanning for references inside %1%") % path);
format("scanning for references inside %1%") % path);
/* Check that fixed-output derivations produced the right /* Check that fixed-output derivations produced the right
outputs (i.e., the content hash should match the specified outputs (i.e., the content hash should match the specified
@ -2955,7 +2951,7 @@ void DerivationGoal::handleChildOutput(int fd, const string & data)
return; return;
} }
if (verbosity >= settings.buildVerbosity) if (verbosity >= settings.buildVerbosity)
writeToStderr(filterANSIEscapes(data, true)); printMsg(lvlError, filterANSIEscapes(data, true)); // FIXME
if (bzLogFile) { if (bzLogFile) {
int err; int err;
BZ2_bzWrite(&err, bzLogFile, (unsigned char *) data.data(), data.size()); BZ2_bzWrite(&err, bzLogFile, (unsigned char *) data.data(), data.size());
@ -2965,7 +2961,7 @@ void DerivationGoal::handleChildOutput(int fd, const string & data)
} }
if (hook && fd == hook->fromHook.readSide) if (hook && fd == hook->fromHook.readSide)
writeToStderr(data); printMsg(lvlError, data); // FIXME?
} }
@ -3388,7 +3384,8 @@ void SubstitutionGoal::finished()
void SubstitutionGoal::handleChildOutput(int fd, const string & data) void SubstitutionGoal::handleChildOutput(int fd, const string & data)
{ {
assert(fd == logPipe.readSide); assert(fd == logPipe.readSide);
if (verbosity >= settings.buildVerbosity) writeToStderr(data); if (verbosity >= settings.buildVerbosity)
printMsg(lvlError, data); // FIXME
/* Don't write substitution output to a log file for now. We /* Don't write substitution output to a log file for now. We
probably should, though. */ probably should, though. */
} }
@ -3586,7 +3583,7 @@ void Worker::run(const Goals & _topGoals)
{ {
for (auto & i : _topGoals) topGoals.insert(i); for (auto & i : _topGoals) topGoals.insert(i);
startNest(nest, lvlDebug, format("entered goal loop")); Activity act(*logger, lvlDebug, "entered goal loop");
while (1) { while (1) {
@ -3804,8 +3801,6 @@ void Worker::markContentsGood(const Path & path)
void LocalStore::buildPaths(const PathSet & drvPaths, BuildMode buildMode) void LocalStore::buildPaths(const PathSet & drvPaths, BuildMode buildMode)
{ {
startNest(nest, lvlDebug, format("building %1%") % showPaths(drvPaths));
Worker worker(*this); Worker worker(*this);
Goals goals; Goals goals;
@ -3835,8 +3830,6 @@ void LocalStore::buildPaths(const PathSet & drvPaths, BuildMode buildMode)
BuildResult LocalStore::buildDerivation(const Path & drvPath, const BasicDerivation & drv, BuildResult LocalStore::buildDerivation(const Path & drvPath, const BasicDerivation & drv,
BuildMode buildMode) BuildMode buildMode)
{ {
startNest(nest, lvlDebug, format("building %1%") % showPaths({drvPath}));
Worker worker(*this); Worker worker(*this);
auto goal = worker.makeBasicDerivationGoal(drvPath, drv, buildMode); auto goal = worker.makeBasicDerivationGoal(drvPath, drv, buildMode);

View file

@ -514,7 +514,7 @@ void LocalStore::tryToDelete(GCState & state, const Path & path)
if (path == linksDir || path == state.trashDir) return; if (path == linksDir || path == state.trashDir) return;
startNest(nest, lvlDebug, format("considering whether to delete %1%") % path); Activity act(*logger, lvlDebug, format("considering whether to delete %1%") % path);
if (!isValidPath(path)) { if (!isValidPath(path)) {
/* A lock file belonging to a path that we're building right /* A lock file belonging to a path that we're building right

View file

@ -1,6 +1,7 @@
#pragma once #pragma once
#include "types.hh" #include "types.hh"
#include "logging.hh"
#include <map> #include <map>
#include <sys/types.h> #include <sys/types.h>

View file

@ -228,7 +228,7 @@ void LocalStore::optimiseStore(OptimiseStats & stats)
for (auto & i : paths) { for (auto & i : paths) {
addTempRoot(i); addTempRoot(i);
if (!isValidPath(i)) continue; /* path was GC'ed, probably */ if (!isValidPath(i)) continue; /* path was GC'ed, probably */
startNest(nest, lvlChatty, format("hashing files in %1%") % i); Activity act(*logger, lvlChatty, format("hashing files in %1%") % i);
optimisePath_(stats, i, inodeHash); optimisePath_(stats, i, inodeHash);
} }
} }

View file

@ -121,8 +121,8 @@ void RemoteStore::setOptions(ref<Connection> conn)
conn->to << settings.useBuildHook; conn->to << settings.useBuildHook;
if (GET_PROTOCOL_MINOR(conn->daemonVersion) >= 4) if (GET_PROTOCOL_MINOR(conn->daemonVersion) >= 4)
conn->to << settings.buildVerbosity conn->to << settings.buildVerbosity
<< logType << 0 // obsolete log type
<< settings.printBuildTrace; << settings.printBuildTrace;
if (GET_PROTOCOL_MINOR(conn->daemonVersion) >= 6) if (GET_PROTOCOL_MINOR(conn->daemonVersion) >= 6)
conn->to << settings.buildCores; conn->to << settings.buildCores;
if (GET_PROTOCOL_MINOR(conn->daemonVersion) >= 10) if (GET_PROTOCOL_MINOR(conn->daemonVersion) >= 10)
@ -561,10 +561,8 @@ void RemoteStore::Connection::processStderr(Sink * sink, Source * source)
writeString(buf, source->read(buf, len), to); writeString(buf, source->read(buf, len), to);
to.flush(); to.flush();
} }
else { else
string s = readString(from); printMsg(lvlError, chomp(readString(from)));
writeToStderr(s);
}
} }
if (msg == STDERR_ERROR) { if (msg == STDERR_ERROR) {
string error = readString(from); string error = readString(from);

79
src/libutil/logging.cc Normal file
View file

@ -0,0 +1,79 @@
#include "logging.hh"
#include "util.hh"
namespace nix {
Logger * logger = 0;
class SimpleLogger : public Logger
{
public:
bool systemd, tty;
SimpleLogger()
{
systemd = getEnv("IN_SYSTEMD") == "1";
tty = isatty(STDERR_FILENO);
}
void log(Verbosity lvl, const FormatOrString & fs) override
{
if (lvl > verbosity) return;
std::string prefix;
if (systemd) {
char c;
switch (lvl) {
case lvlError: c = '3'; break;
case lvlInfo: c = '5'; break;
case lvlTalkative: case lvlChatty: c = '6'; break;
default: c = '7';
}
prefix = std::string("<") + c + ">";
}
writeToStderr(prefix + (tty ? fs.s : filterANSIEscapes(fs.s)) + "\n");
}
void startActivity(Activity & activity, Verbosity lvl, const FormatOrString & fs) override
{
log(lvl, fs);
}
void stopActivity(Activity & activity) override
{
}
};
Verbosity verbosity = lvlInfo;
void warnOnce(bool & haveWarned, const FormatOrString & fs)
{
if (!haveWarned) {
printMsg(lvlError, format("warning: %1%") % fs.s);
haveWarned = true;
}
}
void writeToStderr(const string & s)
{
try {
writeFull(STDERR_FILENO, s);
} catch (SysError & e) {
/* Ignore failing writes to stderr if we're in an exception
handler, otherwise throw an exception. We need to ignore
write errors in exception handlers to ensure that cleanup
code runs to completion if the other side of stderr has
been closed unexpectedly. */
if (!std::uncaught_exception()) throw;
}
}
Logger * makeDefaultLogger()
{
return new SimpleLogger();
}
}

82
src/libutil/logging.hh Normal file
View file

@ -0,0 +1,82 @@
#pragma once
#include "types.hh"
namespace nix {
typedef enum {
lvlError = 0,
lvlInfo,
lvlTalkative,
lvlChatty,
lvlDebug,
lvlVomit
} Verbosity;
class Activity;
class Logger
{
friend class Activity;
public:
virtual ~Logger() { }
virtual void log(Verbosity lvl, const FormatOrString & fs) = 0;
void log(const FormatOrString & fs)
{
log(lvlInfo, fs);
}
virtual void setExpected(const std::string & label, uint64_t value = 1) { }
virtual void setProgress(const std::string & label, uint64_t value = 1) { }
virtual void incExpected(const std::string & label, uint64_t value = 1) { }
virtual void incProgress(const std::string & label, uint64_t value = 1) { }
private:
virtual void startActivity(Activity & activity, Verbosity lvl, const FormatOrString & fs) = 0;
virtual void stopActivity(Activity & activity) = 0;
};
class Activity
{
public:
Logger & logger;
Activity(Logger & logger, Verbosity lvl, const FormatOrString & fs)
: logger(logger)
{
logger.startActivity(*this, lvl, fs);
}
~Activity()
{
logger.stopActivity(*this);
}
};
extern Logger * logger;
Logger * makeDefaultLogger();
extern Verbosity verbosity; /* suppress msgs > this */
#define printMsg(level, f) \
do { \
if (level <= nix::verbosity) { \
logger->log(level, (f)); \
} \
} while (0)
#define debug(f) printMsg(lvlDebug, f)
void warnOnce(bool & haveWarned, const FormatOrString & fs);
void writeToStderr(const string & s);
}

View file

@ -89,14 +89,4 @@ typedef list<Path> Paths;
typedef set<Path> PathSet; typedef set<Path> PathSet;
typedef enum {
lvlError = 0,
lvlInfo,
lvlTalkative,
lvlChatty,
lvlDebug,
lvlVomit
} Verbosity;
} }

View file

@ -356,8 +356,7 @@ void deletePath(const Path & path)
void deletePath(const Path & path, unsigned long long & bytesFreed) void deletePath(const Path & path, unsigned long long & bytesFreed)
{ {
startNest(nest, lvlDebug, Activity act(*logger, lvlDebug, format("recursively deleting path %1%") % path);
format("recursively deleting path %1%") % path);
bytesFreed = 0; bytesFreed = 0;
_deletePath(path, bytesFreed); _deletePath(path, bytesFreed);
} }
@ -456,113 +455,6 @@ void replaceSymlink(const Path & target, const Path & link)
} }
LogType logType = ltPretty;
Verbosity verbosity = lvlInfo;
static int nestingLevel = 0;
Nest::Nest()
{
nest = false;
}
Nest::~Nest()
{
close();
}
static string escVerbosity(Verbosity level)
{
return std::to_string((int) level);
}
void Nest::open(Verbosity level, const FormatOrString & fs)
{
if (level <= verbosity) {
if (logType == ltEscapes)
std::cerr << "\033[" << escVerbosity(level) << "p"
<< fs.s << "\n";
else
printMsg_(level, fs);
nest = true;
nestingLevel++;
}
}
void Nest::close()
{
if (nest) {
nestingLevel--;
if (logType == ltEscapes)
std::cerr << "\033[q";
nest = false;
}
}
void printMsg_(Verbosity level, const FormatOrString & fs)
{
checkInterrupt();
if (level > verbosity) return;
string prefix;
if (logType == ltPretty)
for (int i = 0; i < nestingLevel; i++)
prefix += "| ";
else if (logType == ltEscapes && level != lvlInfo)
prefix = "\033[" + escVerbosity(level) + "s";
else if (logType == ltSystemd) {
char c;
switch (level) {
case lvlError: c = '3'; break;
case lvlInfo: c = '5'; break;
case lvlTalkative: case lvlChatty: c = '6'; break;
default: c = '7';
}
prefix = string("<") + c + ">";
}
string s = (format("%1%%2%\n") % prefix % fs.s).str();
if (!isatty(STDERR_FILENO)) s = filterANSIEscapes(s);
writeToStderr(s);
}
void warnOnce(bool & haveWarned, const FormatOrString & fs)
{
if (!haveWarned) {
printMsg(lvlError, format("warning: %1%") % fs.s);
haveWarned = true;
}
}
void writeToStderr(const string & s)
{
try {
if (_writeToStderr)
_writeToStderr((const unsigned char *) s.data(), s.size());
else
writeFull(STDERR_FILENO, s);
} catch (SysError & e) {
/* Ignore failing writes to stderr if we're in an exception
handler, otherwise throw an exception. We need to ignore
write errors in exception handlers to ensure that cleanup
code runs to completion if the other side of stderr has
been closed unexpectedly. */
if (!std::uncaught_exception()) throw;
}
}
std::function<void(const unsigned char * buf, size_t count)> _writeToStderr;
void readFull(int fd, unsigned char * buf, size_t count) void readFull(int fd, unsigned char * buf, size_t count)
{ {
while (count) { while (count) {
@ -953,7 +845,8 @@ static pid_t doFork(bool allowVfork, std::function<void()> fun)
pid_t startProcess(std::function<void()> fun, const ProcessOptions & options) pid_t startProcess(std::function<void()> fun, const ProcessOptions & options)
{ {
auto wrapper = [&]() { auto wrapper = [&]() {
if (!options.allowVfork) _writeToStderr = 0; if (!options.allowVfork)
logger = makeDefaultLogger();
try { try {
#if __linux__ #if __linux__
if (options.dieWithParent && prctl(PR_SET_PDEATHSIG, SIGKILL) == -1) if (options.dieWithParent && prctl(PR_SET_PDEATHSIG, SIGKILL) == -1)

View file

@ -1,6 +1,7 @@
#pragma once #pragma once
#include "types.hh" #include "types.hh"
#include "logging.hh"
#include <sys/types.h> #include <sys/types.h>
#include <sys/stat.h> #include <sys/stat.h>
@ -125,54 +126,6 @@ T singleton(const A & a)
} }
/* Messages. */
typedef enum {
ltPretty, /* nice, nested output */
ltEscapes, /* nesting indicated using escape codes (for log2xml) */
ltFlat, /* no nesting */
ltSystemd, /* use systemd severity prefixes */
} LogType;
extern LogType logType;
extern Verbosity verbosity; /* suppress msgs > this */
class Nest
{
private:
bool nest;
public:
Nest();
~Nest();
void open(Verbosity level, const FormatOrString & fs);
void close();
};
void printMsg_(Verbosity level, const FormatOrString & fs);
#define startNest(varName, level, f) \
Nest varName; \
if (level <= verbosity) { \
varName.open(level, (f)); \
}
#define printMsg(level, f) \
do { \
if (level <= nix::verbosity) { \
nix::printMsg_(level, (f)); \
} \
} while (0)
#define debug(f) printMsg(lvlDebug, f)
void warnOnce(bool & haveWarned, const FormatOrString & fs);
void writeToStderr(const string & s);
extern std::function<void(const unsigned char * buf, size_t count)> _writeToStderr;
/* Wrappers arount read()/write() that read/write exactly the /* Wrappers arount read()/write() that read/write exactly the
requested number of bytes. */ requested number of bytes. */
void readFull(int fd, unsigned char * buf, size_t count); void readFull(int fd, unsigned char * buf, size_t count);

View file

@ -33,29 +33,43 @@ using namespace nix;
static FdSource from(STDIN_FILENO); static FdSource from(STDIN_FILENO);
static FdSink to(STDOUT_FILENO); static FdSink to(STDOUT_FILENO);
bool canSendStderr; static bool canSendStderr;
static Logger * defaultLogger;
/* This function is called anytime we want to write something to /* Logger that forwards log messages to the client, *if* we're in a
stderr. If we're in a state where the protocol allows it (i.e., state where the protocol allows it (i.e., when canSendStderr is
when canSendStderr), send the message to the client over the true). */
socket. */ class TunnelLogger : public Logger
static void tunnelStderr(const unsigned char * buf, size_t count)
{ {
if (canSendStderr) { void log(Verbosity lvl, const FormatOrString & fs) override
try { {
to << STDERR_NEXT; if (lvl > verbosity) return;
writeString(buf, count, to);
to.flush(); if (canSendStderr) {
} catch (...) { try {
/* Write failed; that means that the other side is to << STDERR_NEXT << (fs.s + "\n");
gone. */ to.flush();
canSendStderr = false; } catch (...) {
throw; /* Write failed; that means that the other side is
} gone. */
} else canSendStderr = false;
writeFull(STDERR_FILENO, buf, count); throw;
} }
} else
defaultLogger->log(lvl, fs);
}
void startActivity(Activity & activity, Verbosity lvl, const FormatOrString & fs) override
{
log(lvl, fs);
}
void stopActivity(Activity & activity) override
{
}
};
/* startWork() means that we're starting an operation for which we /* startWork() means that we're starting an operation for which we
@ -430,7 +444,7 @@ static void performOp(ref<LocalStore> store, bool trusted, unsigned int clientVe
settings.useBuildHook = readInt(from) != 0; settings.useBuildHook = readInt(from) != 0;
if (GET_PROTOCOL_MINOR(clientVersion) >= 4) { if (GET_PROTOCOL_MINOR(clientVersion) >= 4) {
settings.buildVerbosity = (Verbosity) readInt(from); settings.buildVerbosity = (Verbosity) readInt(from);
logType = (LogType) readInt(from); readInt(from); // obsolete logType
settings.printBuildTrace = readInt(from) != 0; settings.printBuildTrace = readInt(from) != 0;
} }
if (GET_PROTOCOL_MINOR(clientVersion) >= 6) if (GET_PROTOCOL_MINOR(clientVersion) >= 6)
@ -557,7 +571,8 @@ static void processConnection(bool trusted)
MonitorFdHup monitor(from.fd); MonitorFdHup monitor(from.fd);
canSendStderr = false; canSendStderr = false;
_writeToStderr = tunnelStderr; defaultLogger = logger;
logger = new TunnelLogger();
/* Exchange the greeting. */ /* Exchange the greeting. */
unsigned int magic = readInt(from); unsigned int magic = readInt(from);

View file

@ -996,7 +996,7 @@ static void opQuery(Globals & globals, Strings opFlags, Strings opArgs)
try { try {
if (i.hasFailed()) continue; if (i.hasFailed()) continue;
startNest(nest, lvlDebug, format("outputting query result %1%") % i.attrPath); Activity act(*logger, lvlDebug, format("outputting query result %1%") % i.attrPath);
if (globals.prebuiltOnly && if (globals.prebuiltOnly &&
validPaths.find(i.queryOutPath()) == validPaths.end() && validPaths.find(i.queryOutPath()) == validPaths.end() &&

View file

@ -19,7 +19,7 @@ using namespace nix;
static Expr * parseStdin(EvalState & state) static Expr * parseStdin(EvalState & state)
{ {
startNest(nest, lvlTalkative, format("parsing standard input")); Activity act(*logger, lvlTalkative, format("parsing standard input"));
return state.parseExprFromString(drainFD(0), absPath(".")); return state.parseExprFromString(drainFD(0), absPath("."));
} }

View file

@ -1,5 +1,4 @@
#include "command.hh" #include "command.hh"
#include "progress-bar.hh"
#include "shared.hh" #include "shared.hh"
#include "store-api.hh" #include "store-api.hh"
#include "sync.hh" #include "sync.hh"
@ -47,16 +46,9 @@ struct CmdCopy : StorePathsCommand
ref<Store> srcStore = srcUri.empty() ? store : openStoreAt(srcUri); ref<Store> srcStore = srcUri.empty() ? store : openStoreAt(srcUri);
ref<Store> dstStore = dstUri.empty() ? store : openStoreAt(dstUri); ref<Store> dstStore = dstUri.empty() ? store : openStoreAt(dstUri);
ProgressBar progressBar; std::string copiedLabel = "copied";
std::atomic<size_t> done{0}; logger->setExpected(copiedLabel, storePaths.size());
std::atomic<size_t> total{storePaths.size()};
auto showProgress = [&]() {
return (format("[%d/%d copied]") % done % total).str();
};
progressBar.updateStatus(showProgress());
ThreadPool pool; ThreadPool pool;
@ -71,7 +63,7 @@ struct CmdCopy : StorePathsCommand
checkInterrupt(); checkInterrupt();
if (!dstStore->isValidPath(storePath)) { if (!dstStore->isValidPath(storePath)) {
auto activity(progressBar.startActivity(format("copying %s...") % storePath)); Activity act(*logger, lvlInfo, format("copying %s...") % storePath);
StringSink sink; StringSink sink;
srcStore->exportPaths({storePath}, false, sink); srcStore->exportPaths({storePath}, false, sink);
@ -79,16 +71,12 @@ struct CmdCopy : StorePathsCommand
StringSource source(*sink.s); StringSource source(*sink.s);
dstStore->importPaths(false, source, 0); dstStore->importPaths(false, source, 0);
done++; logger->incProgress(copiedLabel);
} else } else
total--; logger->incExpected(copiedLabel, -1);
progressBar.updateStatus(showProgress());
}); });
pool.process(); pool.process();
progressBar.done();
} }
}; };

View file

@ -7,6 +7,7 @@
#include "legacy.hh" #include "legacy.hh"
#include "shared.hh" #include "shared.hh"
#include "store-api.hh" #include "store-api.hh"
#include "progress-bar.hh"
namespace nix { namespace nix {
@ -42,6 +43,8 @@ void mainWrapped(int argc, char * * argv)
assert(args.command); assert(args.command);
StartProgressBar bar;
args.command->prepare(); args.command->prepare();
args.command->run(); args.command->run();
} }

View file

@ -1,5 +1,4 @@
#include "command.hh" #include "command.hh"
#include "progress-bar.hh"
#include "shared.hh" #include "shared.hh"
#include "store-api.hh" #include "store-api.hh"

View file

@ -1,72 +1,157 @@
#include "progress-bar.hh" #include "progress-bar.hh"
#include "util.hh"
#include "sync.hh"
#include <iostream> #include <map>
namespace nix { namespace nix {
ProgressBar::ProgressBar() class ProgressBar : public Logger
{ {
_writeToStderr = [&](const unsigned char * buf, size_t count) { private:
auto state_(state.lock());
assert(!state_->done); struct ActInfo
std::cerr << "\r\e[K" << std::string((const char *) buf, count); {
render(*state_); Activity * activity;
Verbosity lvl;
std::string s;
}; };
}
ProgressBar::~ProgressBar() struct Progress
{ {
done(); uint64_t expected = 0, progress = 0;
} };
void ProgressBar::updateStatus(const std::string & s) struct State
{ {
auto state_(state.lock()); std::list<ActInfo> activities;
assert(!state_->done); std::map<Activity *, std::list<ActInfo>::iterator> its;
state_->status = s; std::map<std::string, Progress> progress;
render(*state_); };
}
void ProgressBar::done() Sync<State> state_;
{
_writeToStderr = decltype(_writeToStderr)();
auto state_(state.lock());
assert(state_->activities.empty());
state_->done = true;
std::cerr << "\r\e[K";
std::cerr.flush();
}
void ProgressBar::render(State & state_) public:
{
std::cerr << '\r' << state_.status; ~ProgressBar()
if (!state_.activities.empty()) { {
if (!state_.status.empty()) std::cerr << ' '; auto state(state_.lock());
std::cerr << *state_.activities.rbegin(); assert(state->activities.empty());
writeToStderr("\r\e[K");
} }
std::cerr << "\e[K";
std::cerr.flush();
}
void log(Verbosity lvl, const FormatOrString & fs) override
{
auto state(state_.lock());
log(*state, lvl, fs.s);
}
ProgressBar::Activity ProgressBar::startActivity(const FormatOrString & fs) void log(State & state, Verbosity lvl, const std::string & s)
{
writeToStderr("\r\e[K" + s + "\n");
update(state);
}
void startActivity(Activity & activity, Verbosity lvl, const FormatOrString & fs) override
{
if (lvl > verbosity) return;
auto state(state_.lock());
state->activities.emplace_back(ActInfo{&activity, lvl, fs.s});
state->its.emplace(&activity, std::prev(state->activities.end()));
update(*state);
}
void stopActivity(Activity & activity) override
{
auto state(state_.lock());
auto i = state->its.find(&activity);
if (i == state->its.end()) return;
state->activities.erase(i->second);
state->its.erase(i);
update(*state);
}
void setExpected(const std::string & label, uint64_t value) override
{
auto state(state_.lock());
state->progress[label].expected = value;
}
void setProgress(const std::string & label, uint64_t value) override
{
auto state(state_.lock());
state->progress[label].progress = value;
}
void incExpected(const std::string & label, uint64_t value) override
{
auto state(state_.lock());
state->progress[label].expected += value;
}
void incProgress(const std::string & label, uint64_t value)
{
auto state(state_.lock());
state->progress[label].progress += value;
}
void update()
{
auto state(state_.lock());
}
void update(State & state)
{
std::string line = "\r";
std::string status = getStatus(state);
if (!status.empty()) {
line += '[';
line += status;
line += "]";
}
if (!state.activities.empty()) {
if (!status.empty()) line += " ";
line += state.activities.rbegin()->s;
}
line += "\e[K";
writeToStderr(line);
}
std::string getStatus(State & state)
{
std::string res;
for (auto & p : state.progress)
if (p.second.expected || p.second.progress) {
if (!res.empty()) res += ", ";
res += std::to_string(p.second.progress);
if (p.second.expected) {
res += "/";
res += std::to_string(p.second.expected);
}
res += " "; res += p.first;
}
return res;
}
};
StartProgressBar::StartProgressBar()
{ {
return Activity(*this, fs); if (isatty(STDERR_FILENO)) {
prev = logger;
logger = new ProgressBar();
}
} }
ProgressBar::Activity::Activity(ProgressBar & pb, const FormatOrString & fs) StartProgressBar::~StartProgressBar()
: pb(pb)
{ {
auto state_(pb.state.lock()); if (prev) {
state_->activities.push_back(fs.s); auto bar = logger;
it = state_->activities.end(); --it; logger = prev;
pb.render(*state_); delete bar;
} }
ProgressBar::Activity::~Activity()
{
auto state_(pb.state.lock());
state_->activities.erase(it);
} }
} }

View file

@ -1,49 +1,15 @@
#pragma once #pragma once
#include "sync.hh" #include "logging.hh"
#include "util.hh"
namespace nix { namespace nix {
class ProgressBar class StartProgressBar
{ {
private: Logger * prev = 0;
struct State
{
std::string status;
bool done = false;
std::list<std::string> activities;
};
Sync<State> state;
public: public:
StartProgressBar();
ProgressBar(); ~StartProgressBar();
~ProgressBar();
void updateStatus(const std::string & s);
void done();
class Activity
{
friend class ProgressBar;
private:
ProgressBar & pb;
std::list<std::string>::iterator it;
Activity(ProgressBar & pb, const FormatOrString & fs);
public:
~Activity();
};
Activity startActivity(const FormatOrString & fs);
private:
void render(State & state_);
}; };
} }

View file

@ -1,5 +1,4 @@
#include "command.hh" #include "command.hh"
#include "progress-bar.hh"
#include "shared.hh" #include "shared.hh"
#include "store-api.hh" #include "store-api.hh"
#include "thread-pool.hh" #include "thread-pool.hh"
@ -38,21 +37,15 @@ struct CmdCopySigs : StorePathsCommand
for (auto & s : substituterUris) for (auto & s : substituterUris)
substituters.push_back(openStoreAt(s)); substituters.push_back(openStoreAt(s));
ProgressBar progressBar;
ThreadPool pool; ThreadPool pool;
std::atomic<size_t> done{0}; std::string doneLabel = "done";
std::atomic<size_t> added{0}; std::atomic<size_t> added{0};
auto showProgress = [&]() { logger->setExpected(doneLabel, storePaths.size());
return (format("[%d/%d done]") % done % storePaths.size()).str();
};
progressBar.updateStatus(showProgress());
auto doPath = [&](const Path & storePath) { auto doPath = [&](const Path & storePath) {
auto activity(progressBar.startActivity(format("getting signatures for %s") % storePath)); Activity act(*logger, lvlInfo, format("getting signatures for %s") % storePath);
checkInterrupt(); checkInterrupt();
@ -83,8 +76,7 @@ struct CmdCopySigs : StorePathsCommand
added += newSigs.size(); added += newSigs.size();
} }
done++; logger->incProgress(doneLabel);
progressBar.updateStatus(showProgress());
}; };
for (auto & storePath : storePaths) for (auto & storePath : storePaths)
@ -92,8 +84,6 @@ struct CmdCopySigs : StorePathsCommand
pool.process(); pool.process();
progressBar.done();
printMsg(lvlInfo, format("imported %d signatures") % added); printMsg(lvlInfo, format("imported %d signatures") % added);
} }
}; };

View file

@ -1,5 +1,4 @@
#include "command.hh" #include "command.hh"
#include "progress-bar.hh"
#include "shared.hh" #include "shared.hh"
#include "store-api.hh" #include "store-api.hh"
#include "sync.hh" #include "sync.hh"
@ -57,30 +56,16 @@ struct CmdVerify : StorePathsCommand
auto publicKeys = getDefaultPublicKeys(); auto publicKeys = getDefaultPublicKeys();
std::atomic<size_t> done{0};
std::atomic<size_t> untrusted{0}; std::atomic<size_t> untrusted{0};
std::atomic<size_t> corrupted{0}; std::atomic<size_t> corrupted{0};
std::atomic<size_t> done{0};
std::atomic<size_t> failed{0}; std::atomic<size_t> failed{0};
ProgressBar progressBar; std::string doneLabel("paths checked");
std::string untrustedLabel("untrusted");
auto showProgress = [&](bool final) { std::string corruptedLabel("corrupted");
std::string s; std::string failedLabel("failed");
if (final) logger->setExpected(doneLabel, storePaths.size());
s = (format("checked %d paths") % storePaths.size()).str();
else
s = (format("[%d/%d checked") % done % storePaths.size()).str();
if (corrupted > 0)
s += (format(", %d corrupted") % corrupted).str();
if (untrusted > 0)
s += (format(", %d untrusted") % untrusted).str();
if (failed > 0)
s += (format(", %d failed") % failed).str();
if (!final) s += "]";
return s;
};
progressBar.updateStatus(showProgress(false));
ThreadPool pool; ThreadPool pool;
@ -88,7 +73,7 @@ struct CmdVerify : StorePathsCommand
try { try {
checkInterrupt(); checkInterrupt();
auto activity(progressBar.startActivity(format("checking %s") % storePath)); Activity act(*logger, lvlInfo, format("checking %s") % storePath);
auto info = store->queryPathInfo(storePath); auto info = store->queryPathInfo(storePath);
@ -100,6 +85,7 @@ struct CmdVerify : StorePathsCommand
auto hash = sink.finish(); auto hash = sink.finish();
if (hash.first != info->narHash) { if (hash.first != info->narHash) {
logger->incProgress(corruptedLabel);
corrupted = 1; corrupted = 1;
printMsg(lvlError, printMsg(lvlError,
format("path %s was modified! expected hash %s, got %s") format("path %s was modified! expected hash %s, got %s")
@ -147,18 +133,19 @@ struct CmdVerify : StorePathsCommand
} }
if (!good) { if (!good) {
logger->incProgress(untrustedLabel);
untrusted++; untrusted++;
printMsg(lvlError, format("path %s is untrusted") % info->path); printMsg(lvlError, format("path %s is untrusted") % info->path);
} }
} }
logger->incProgress(doneLabel);
done++; done++;
progressBar.updateStatus(showProgress(false));
} catch (Error & e) { } catch (Error & e) {
printMsg(lvlError, format(ANSI_RED "error:" ANSI_NORMAL " %s") % e.what()); printMsg(lvlError, format(ANSI_RED "error:" ANSI_NORMAL " %s") % e.what());
logger->incProgress(failedLabel);
failed++; failed++;
} }
}; };
@ -168,9 +155,8 @@ struct CmdVerify : StorePathsCommand
pool.process(); pool.process();
progressBar.done(); printMsg(lvlInfo, format("%d paths checked, %d untrusted, %d corrupted, %d failed")
% done % untrusted % corrupted % failed);
printMsg(lvlInfo, showProgress(true));
throw Exit( throw Exit(
(corrupted ? 1 : 0) | (corrupted ? 1 : 0) |

View file

@ -2,16 +2,7 @@ source common.sh
clearStore clearStore
# Produce an escaped log file. path=$(nix-build dependencies.nix --no-out-link)
path=$(nix-build --log-type escapes -vv dependencies.nix --no-out-link 2> $TEST_ROOT/log.esc)
# Convert it to an XML representation.
nix-log2xml < $TEST_ROOT/log.esc > $TEST_ROOT/log.xml
# Is this well-formed XML?
if test "$xmllint" != "false"; then
$xmllint --noout $TEST_ROOT/log.xml || fail "malformed XML"
fi
# Test nix-store -l. # Test nix-store -l.
[ "$(nix-store -l $path)" = FOO ] [ "$(nix-store -l $path)" = FOO ]