2016-04-25 16:26:07 +03:00
|
|
|
#include "logging.hh"
|
2023-10-25 07:43:36 +03:00
|
|
|
#include "file-descriptor.hh"
|
|
|
|
#include "environment-variables.hh"
|
|
|
|
#include "terminal.hh"
|
2016-04-25 16:26:07 +03:00
|
|
|
#include "util.hh"
|
2020-07-02 18:04:31 +03:00
|
|
|
#include "config.hh"
|
2023-12-18 23:14:42 +02:00
|
|
|
#include "source-path.hh"
|
|
|
|
#include "position.hh"
|
2016-04-25 16:26:07 +03:00
|
|
|
|
2017-05-16 17:09:57 +03:00
|
|
|
#include <atomic>
|
2017-10-24 14:41:52 +03:00
|
|
|
#include <nlohmann/json.hpp>
|
2020-04-16 14:12:58 +03:00
|
|
|
#include <iostream>
|
2017-05-16 17:09:57 +03:00
|
|
|
|
2016-04-25 16:26:07 +03:00
|
|
|
namespace nix {
|
|
|
|
|
2020-07-02 18:04:31 +03:00
|
|
|
LoggerSettings loggerSettings;
|
|
|
|
|
2020-10-06 14:36:55 +03:00
|
|
|
static GlobalConfig::Register rLoggerSettings(&loggerSettings);
|
2020-07-02 18:04:31 +03:00
|
|
|
|
2018-03-12 06:56:41 +02:00
|
|
|
static thread_local ActivityId curActivity = 0;
|
|
|
|
|
|
|
|
ActivityId getCurActivity()
|
|
|
|
{
|
|
|
|
return curActivity;
|
|
|
|
}
|
|
|
|
void setCurActivity(const ActivityId activityId)
|
|
|
|
{
|
|
|
|
curActivity = activityId;
|
|
|
|
}
|
2017-08-25 18:49:40 +03:00
|
|
|
|
2020-07-02 18:04:31 +03:00
|
|
|
Logger * logger = makeSimpleLogger(true);
|
2016-04-25 16:26:07 +03:00
|
|
|
|
2017-04-12 15:53:10 +03:00
|
|
|
void Logger::warn(const std::string & msg)
|
|
|
|
{
|
2021-09-14 11:38:10 +03:00
|
|
|
log(lvlWarn, ANSI_WARNING "warning:" ANSI_NORMAL " " + msg);
|
2017-04-12 15:53:10 +03:00
|
|
|
}
|
|
|
|
|
2020-04-16 14:12:58 +03:00
|
|
|
void Logger::writeToStdout(std::string_view s)
|
|
|
|
{
|
2023-03-02 15:46:28 +02:00
|
|
|
writeFull(STDOUT_FILENO, s);
|
|
|
|
writeFull(STDOUT_FILENO, "\n");
|
2020-04-16 14:12:58 +03:00
|
|
|
}
|
|
|
|
|
2016-04-25 16:26:07 +03:00
|
|
|
class SimpleLogger : public Logger
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
|
|
|
|
bool systemd, tty;
|
2020-06-05 19:20:11 +03:00
|
|
|
bool printBuildLogs;
|
2016-04-25 16:26:07 +03:00
|
|
|
|
2020-07-02 18:04:31 +03:00
|
|
|
SimpleLogger(bool printBuildLogs)
|
|
|
|
: printBuildLogs(printBuildLogs)
|
2016-04-25 16:26:07 +03:00
|
|
|
{
|
|
|
|
systemd = getEnv("IN_SYSTEMD") == "1";
|
2021-07-02 03:19:01 +03:00
|
|
|
tty = shouldANSI();
|
2016-04-25 16:26:07 +03:00
|
|
|
}
|
|
|
|
|
2020-06-05 19:20:11 +03:00
|
|
|
bool isVerbose() override {
|
|
|
|
return printBuildLogs;
|
|
|
|
}
|
|
|
|
|
2023-03-02 16:44:19 +02:00
|
|
|
void log(Verbosity lvl, std::string_view s) override
|
2016-04-25 16:26:07 +03:00
|
|
|
{
|
|
|
|
if (lvl > verbosity) return;
|
|
|
|
|
|
|
|
std::string prefix;
|
|
|
|
|
|
|
|
if (systemd) {
|
|
|
|
char c;
|
|
|
|
switch (lvl) {
|
|
|
|
case lvlError: c = '3'; break;
|
2019-06-17 10:12:03 +03:00
|
|
|
case lvlWarn: c = '4'; break;
|
2023-04-03 19:01:30 +03:00
|
|
|
case lvlNotice: case lvlInfo: c = '5'; break;
|
2016-04-25 16:26:07 +03:00
|
|
|
case lvlTalkative: case lvlChatty: c = '6'; break;
|
2023-08-11 13:05:45 +03:00
|
|
|
case lvlDebug: case lvlVomit: c = '7'; break;
|
2023-04-03 19:11:42 +03:00
|
|
|
default: c = '7'; break; // should not happen, and missing enum case is reported by -Werror=switch-enum
|
2016-04-25 16:26:07 +03:00
|
|
|
}
|
|
|
|
prefix = std::string("<") + c + ">";
|
|
|
|
}
|
|
|
|
|
2023-03-02 16:44:19 +02:00
|
|
|
writeToStderr(prefix + filterANSIEscapes(s, !tty) + "\n");
|
2016-04-25 16:26:07 +03:00
|
|
|
}
|
2017-08-28 20:13:24 +03:00
|
|
|
|
2020-04-18 00:07:44 +03:00
|
|
|
void logEI(const ErrorInfo & ei) override
|
|
|
|
{
|
2020-04-29 19:14:32 +03:00
|
|
|
std::stringstream oss;
|
2020-07-02 18:04:31 +03:00
|
|
|
showErrorInfo(oss, ei, loggerSettings.showTrace.get());
|
2020-04-18 00:07:44 +03:00
|
|
|
|
|
|
|
log(ei.level, oss.str());
|
|
|
|
}
|
|
|
|
|
2017-08-28 20:13:24 +03:00
|
|
|
void startActivity(ActivityId act, Verbosity lvl, ActivityType type,
|
|
|
|
const std::string & s, const Fields & fields, ActivityId parent)
|
2023-03-02 15:46:28 +02:00
|
|
|
override
|
2017-08-28 20:13:24 +03:00
|
|
|
{
|
|
|
|
if (lvl <= verbosity && !s.empty())
|
|
|
|
log(lvl, s + "...");
|
|
|
|
}
|
2020-06-05 19:20:11 +03:00
|
|
|
|
|
|
|
void result(ActivityId act, ResultType type, const Fields & fields) override
|
|
|
|
{
|
|
|
|
if (type == resBuildLogLine && printBuildLogs) {
|
|
|
|
auto lastLine = fields[0].s;
|
|
|
|
printError(lastLine);
|
|
|
|
}
|
|
|
|
else if (type == resPostBuildLogLine && printBuildLogs) {
|
|
|
|
auto lastLine = fields[0].s;
|
|
|
|
printError("post-build-hook: " + lastLine);
|
|
|
|
}
|
|
|
|
}
|
2016-04-25 16:26:07 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
Verbosity verbosity = lvlInfo;
|
|
|
|
|
2022-02-25 17:00:00 +02:00
|
|
|
void writeToStderr(std::string_view s)
|
2016-04-25 16:26:07 +03:00
|
|
|
{
|
|
|
|
try {
|
2016-09-16 19:52:42 +03:00
|
|
|
writeFull(STDERR_FILENO, s, false);
|
2023-12-02 00:03:28 +02:00
|
|
|
} catch (SystemError & e) {
|
2016-09-16 19:52:42 +03:00
|
|
|
/* Ignore failing writes to stderr. We need to ignore write
|
|
|
|
errors to ensure that cleanup code that logs to stderr runs
|
|
|
|
to completion if the other side of stderr has been closed
|
|
|
|
unexpectedly. */
|
2016-04-25 16:26:07 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-02 18:04:31 +03:00
|
|
|
Logger * makeSimpleLogger(bool printBuildLogs)
|
2016-04-25 16:26:07 +03:00
|
|
|
{
|
2020-07-02 18:04:31 +03:00
|
|
|
return new SimpleLogger(printBuildLogs);
|
2016-04-25 16:26:07 +03:00
|
|
|
}
|
|
|
|
|
2022-12-07 13:58:58 +02:00
|
|
|
std::atomic<uint64_t> nextId{0};
|
2017-05-16 17:09:57 +03:00
|
|
|
|
2017-08-28 20:13:24 +03:00
|
|
|
Activity::Activity(Logger & logger, Verbosity lvl, ActivityType type,
|
2017-08-25 18:49:40 +03:00
|
|
|
const std::string & s, const Logger::Fields & fields, ActivityId parent)
|
2022-12-07 13:58:58 +02:00
|
|
|
: logger(logger), id(nextId++ + (((uint64_t) getpid()) << 32))
|
2017-08-14 23:42:17 +03:00
|
|
|
{
|
2017-08-28 20:13:24 +03:00
|
|
|
logger.startActivity(id, lvl, type, s, fields, parent);
|
2017-08-14 23:42:17 +03:00
|
|
|
}
|
|
|
|
|
2023-12-18 23:14:42 +02:00
|
|
|
void to_json(nlohmann::json & json, std::shared_ptr<Pos> pos)
|
2022-12-13 01:48:04 +02:00
|
|
|
{
|
|
|
|
if (pos) {
|
|
|
|
json["line"] = pos->line;
|
|
|
|
json["column"] = pos->column;
|
|
|
|
std::ostringstream str;
|
2023-12-18 23:14:42 +02:00
|
|
|
pos->print(str, true);
|
2022-12-13 01:48:04 +02:00
|
|
|
json["file"] = str.str();
|
|
|
|
} else {
|
|
|
|
json["line"] = nullptr;
|
|
|
|
json["column"] = nullptr;
|
|
|
|
json["file"] = nullptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-29 19:14:32 +03:00
|
|
|
struct JSONLogger : Logger {
|
2017-10-24 14:41:52 +03:00
|
|
|
Logger & prevLogger;
|
|
|
|
|
|
|
|
JSONLogger(Logger & prevLogger) : prevLogger(prevLogger) { }
|
|
|
|
|
2020-06-05 19:20:11 +03:00
|
|
|
bool isVerbose() override {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2017-10-24 14:41:52 +03:00
|
|
|
void addFields(nlohmann::json & json, const Fields & fields)
|
|
|
|
{
|
|
|
|
if (fields.empty()) return;
|
|
|
|
auto & arr = json["fields"] = nlohmann::json::array();
|
|
|
|
for (auto & f : fields)
|
|
|
|
if (f.type == Logger::Field::tInt)
|
|
|
|
arr.push_back(f.i);
|
|
|
|
else if (f.type == Logger::Field::tString)
|
|
|
|
arr.push_back(f.s);
|
|
|
|
else
|
|
|
|
abort();
|
|
|
|
}
|
|
|
|
|
|
|
|
void write(const nlohmann::json & json)
|
|
|
|
{
|
2021-08-22 00:26:22 +03:00
|
|
|
prevLogger.log(lvlError, "@nix " + json.dump(-1, ' ', false, nlohmann::json::error_handler_t::replace));
|
2017-10-24 14:41:52 +03:00
|
|
|
}
|
|
|
|
|
2023-03-02 16:44:19 +02:00
|
|
|
void log(Verbosity lvl, std::string_view s) override
|
2017-10-24 14:41:52 +03:00
|
|
|
{
|
|
|
|
nlohmann::json json;
|
|
|
|
json["action"] = "msg";
|
|
|
|
json["level"] = lvl;
|
2023-03-02 16:44:19 +02:00
|
|
|
json["msg"] = s;
|
2017-10-24 14:41:52 +03:00
|
|
|
write(json);
|
|
|
|
}
|
|
|
|
|
2020-04-18 00:07:44 +03:00
|
|
|
void logEI(const ErrorInfo & ei) override
|
|
|
|
{
|
2020-04-18 00:50:46 +03:00
|
|
|
std::ostringstream oss;
|
2020-07-02 18:04:31 +03:00
|
|
|
showErrorInfo(oss, ei, loggerSettings.showTrace.get());
|
2020-04-18 00:50:46 +03:00
|
|
|
|
|
|
|
nlohmann::json json;
|
|
|
|
json["action"] = "msg";
|
|
|
|
json["level"] = ei.level;
|
|
|
|
json["msg"] = oss.str();
|
2021-01-21 01:27:36 +02:00
|
|
|
json["raw_msg"] = ei.msg.str();
|
2022-12-13 01:48:04 +02:00
|
|
|
to_json(json, ei.errPos);
|
libutil/logging: extend `internal-json` logger to make it more machine-readable
The new error-format is pretty nice from a UX point-of-view, however
it's fairly hard to parse the output e.g. for editor plugins such as
vim-ale[1] that use `nix-instantiate --parse` to determine syntax errors in
Nix expression files.
This patch extends the `internal-json` logger by adding the fields
`line`, `column` and `file` to easily locate an error in a file and the
field `raw_msg` which contains the error-message itself without
code-lines and additional helpers.
An exemplary output may look like this:
```
[nix-shell]$ ./inst/bin/nix-instantiate ~/test.nix --log-format minimal
{"action":"msg","column":1,"file":"/home/ma27/test.nix","level":0,"line":4,"raw_msg":"syntax error, unexpected IF, expecting $end","msg":"<full error-msg with code-lines etc>"}
```
[1] https://github.com/dense-analysis/ale
2020-07-22 00:38:18 +03:00
|
|
|
|
|
|
|
if (loggerSettings.showTrace.get() && !ei.traces.empty()) {
|
|
|
|
nlohmann::json traces = nlohmann::json::array();
|
|
|
|
for (auto iter = ei.traces.rbegin(); iter != ei.traces.rend(); ++iter) {
|
|
|
|
nlohmann::json stackFrame;
|
|
|
|
stackFrame["raw_msg"] = iter->hint.str();
|
2022-12-13 01:48:04 +02:00
|
|
|
to_json(stackFrame, iter->pos);
|
libutil/logging: extend `internal-json` logger to make it more machine-readable
The new error-format is pretty nice from a UX point-of-view, however
it's fairly hard to parse the output e.g. for editor plugins such as
vim-ale[1] that use `nix-instantiate --parse` to determine syntax errors in
Nix expression files.
This patch extends the `internal-json` logger by adding the fields
`line`, `column` and `file` to easily locate an error in a file and the
field `raw_msg` which contains the error-message itself without
code-lines and additional helpers.
An exemplary output may look like this:
```
[nix-shell]$ ./inst/bin/nix-instantiate ~/test.nix --log-format minimal
{"action":"msg","column":1,"file":"/home/ma27/test.nix","level":0,"line":4,"raw_msg":"syntax error, unexpected IF, expecting $end","msg":"<full error-msg with code-lines etc>"}
```
[1] https://github.com/dense-analysis/ale
2020-07-22 00:38:18 +03:00
|
|
|
traces.push_back(stackFrame);
|
|
|
|
}
|
|
|
|
|
|
|
|
json["trace"] = traces;
|
|
|
|
}
|
2020-04-18 00:50:46 +03:00
|
|
|
|
|
|
|
write(json);
|
2020-04-18 00:07:44 +03:00
|
|
|
}
|
|
|
|
|
2017-10-24 14:41:52 +03:00
|
|
|
void startActivity(ActivityId act, Verbosity lvl, ActivityType type,
|
|
|
|
const std::string & s, const Fields & fields, ActivityId parent) override
|
|
|
|
{
|
|
|
|
nlohmann::json json;
|
|
|
|
json["action"] = "start";
|
|
|
|
json["id"] = act;
|
|
|
|
json["level"] = lvl;
|
|
|
|
json["type"] = type;
|
|
|
|
json["text"] = s;
|
2023-09-09 19:01:10 +03:00
|
|
|
json["parent"] = parent;
|
2017-10-24 14:41:52 +03:00
|
|
|
addFields(json, fields);
|
|
|
|
write(json);
|
|
|
|
}
|
|
|
|
|
|
|
|
void stopActivity(ActivityId act) override
|
|
|
|
{
|
|
|
|
nlohmann::json json;
|
|
|
|
json["action"] = "stop";
|
|
|
|
json["id"] = act;
|
|
|
|
write(json);
|
|
|
|
}
|
|
|
|
|
|
|
|
void result(ActivityId act, ResultType type, const Fields & fields) override
|
|
|
|
{
|
|
|
|
nlohmann::json json;
|
|
|
|
json["action"] = "result";
|
|
|
|
json["id"] = act;
|
|
|
|
json["type"] = type;
|
|
|
|
addFields(json, fields);
|
|
|
|
write(json);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
Logger * makeJSONLogger(Logger & prevLogger)
|
|
|
|
{
|
|
|
|
return new JSONLogger(prevLogger);
|
|
|
|
}
|
|
|
|
|
|
|
|
static Logger::Fields getFields(nlohmann::json & json)
|
|
|
|
{
|
|
|
|
Logger::Fields fields;
|
|
|
|
for (auto & f : json) {
|
|
|
|
if (f.type() == nlohmann::json::value_t::number_unsigned)
|
|
|
|
fields.emplace_back(Logger::Field(f.get<uint64_t>()));
|
|
|
|
else if (f.type() == nlohmann::json::value_t::string)
|
|
|
|
fields.emplace_back(Logger::Field(f.get<std::string>()));
|
|
|
|
else throw Error("unsupported JSON type %d", (int) f.type());
|
|
|
|
}
|
|
|
|
return fields;
|
|
|
|
}
|
|
|
|
|
2022-02-19 23:34:50 +02:00
|
|
|
std::optional<nlohmann::json> parseJSONMessage(const std::string & msg)
|
2017-10-24 14:41:52 +03:00
|
|
|
{
|
2022-02-19 23:34:50 +02:00
|
|
|
if (!hasPrefix(msg, "@nix ")) return std::nullopt;
|
2017-10-24 14:41:52 +03:00
|
|
|
try {
|
2022-02-19 23:34:50 +02:00
|
|
|
return nlohmann::json::parse(std::string(msg, 5));
|
|
|
|
} catch (std::exception & e) {
|
|
|
|
printError("bad JSON log message from builder: %s", e.what());
|
|
|
|
}
|
|
|
|
return std::nullopt;
|
|
|
|
}
|
2017-10-24 14:41:52 +03:00
|
|
|
|
2022-02-19 23:34:50 +02:00
|
|
|
bool handleJSONLogMessage(nlohmann::json & json,
|
|
|
|
const Activity & act, std::map<ActivityId, Activity> & activities,
|
|
|
|
bool trusted)
|
|
|
|
{
|
|
|
|
std::string action = json["action"];
|
|
|
|
|
|
|
|
if (action == "start") {
|
|
|
|
auto type = (ActivityType) json["type"];
|
|
|
|
if (trusted || type == actFileTransfer)
|
|
|
|
activities.emplace(std::piecewise_construct,
|
|
|
|
std::forward_as_tuple(json["id"]),
|
|
|
|
std::forward_as_tuple(*logger, (Verbosity) json["level"], type,
|
|
|
|
json["text"], getFields(json["fields"]), act.id));
|
|
|
|
}
|
2017-10-24 14:41:52 +03:00
|
|
|
|
2022-02-19 23:34:50 +02:00
|
|
|
else if (action == "stop")
|
|
|
|
activities.erase((ActivityId) json["id"]);
|
2017-10-24 14:41:52 +03:00
|
|
|
|
2022-02-19 23:34:50 +02:00
|
|
|
else if (action == "result") {
|
|
|
|
auto i = activities.find((ActivityId) json["id"]);
|
|
|
|
if (i != activities.end())
|
|
|
|
i->second.result((ResultType) json["type"], getFields(json["fields"]));
|
|
|
|
}
|
2017-10-24 14:41:52 +03:00
|
|
|
|
2022-02-19 23:34:50 +02:00
|
|
|
else if (action == "setPhase") {
|
|
|
|
std::string phase = json["phase"];
|
|
|
|
act.result(resSetPhase, phase);
|
|
|
|
}
|
2017-10-24 14:41:52 +03:00
|
|
|
|
2022-02-19 23:34:50 +02:00
|
|
|
else if (action == "msg") {
|
|
|
|
std::string msg = json["msg"];
|
|
|
|
logger->log((Verbosity) json["level"], msg);
|
2017-10-24 14:41:52 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-02-19 23:34:50 +02:00
|
|
|
bool handleJSONLogMessage(const std::string & msg,
|
|
|
|
const Activity & act, std::map<ActivityId, Activity> & activities, bool trusted)
|
|
|
|
{
|
|
|
|
auto json = parseJSONMessage(msg);
|
|
|
|
if (!json) return false;
|
|
|
|
|
|
|
|
return handleJSONLogMessage(*json, act, activities, trusted);
|
|
|
|
}
|
|
|
|
|
2020-04-29 19:14:32 +03:00
|
|
|
Activity::~Activity()
|
|
|
|
{
|
2018-03-03 22:39:04 +02:00
|
|
|
try {
|
|
|
|
logger.stopActivity(id);
|
|
|
|
} catch (...) {
|
|
|
|
ignoreException();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-25 16:26:07 +03:00
|
|
|
}
|