/* Ousía Copyright (C) 2014, 2015 Benjamin Paaßen, Andreas Stöckel This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include "Logger.hpp" #include "Terminal.hpp" namespace ousia { /* Class Logger */ void Logger::log(Severity severity, const std::string &msg, const SourceLocation &loc) { // Assemble the message and pass it through the filter, then process it Message message { severity, std::move(msg), loc }; if (filterMessage(message)) { processMessage(message); } } LoggerFork Logger::fork() { return LoggerFork(this); } /* Class LoggerFork */ void LoggerFork::processMessage(const Message &msg) { calls.push_back(Call(CallType::MESSAGE, messages.size())); messages.push_back(msg); } void LoggerFork::processPushFile(const File &file) { calls.push_back(Call(CallType::PUSH_FILE, files.size())); files.push_back(file); } void LoggerFork::processPopFile() { calls.push_back(Call(CallType::POP_FILE, 0)); } void LoggerFork::processSetDefaultLocation(const SourceLocation &loc) { // Check whether setDefaultLocation was called immediately before, if yes, // simply override the data if (!calls.empty() && calls.back().type == CallType::SET_DEFAULT_LOCATION) { locations.back() = loc; } else { calls.push_back(Call(CallType::SET_DEFAULT_LOCATION, locations.size())); locations.push_back(loc); } } void LoggerFork::purge() { calls.clear(); messages.clear(); files.clear(); locations.clear(); } void LoggerFork::commit() { for (const Call &call : calls) { switch (call.type) { case CallType::MESSAGE: { if (parent->filterMessage(messages[call.dataIdx])) { parent->processMessage(messages[call.dataIdx]); } break; } case CallType::PUSH_FILE: { parent->processPushFile(files[call.dataIdx]); break; } case CallType::POP_FILE: parent->processPopFile(); break; case CallType::SET_DEFAULT_LOCATION: parent->processSetDefaultLocation(locations[call.dataIdx]); break; } } purge(); } /* Class ConcreteLogger */ static const Logger::File EMPTY_FILE{"", SourceLocation{}, nullptr, nullptr}; void ConcreteLogger::processPushFile(const File &file) { files.push_back(file); } void ConcreteLogger::processPopFile() { files.pop_back(); } bool ConcreteLogger::filterMessage(const Message &msg) { // Increment the message count for this severity uint8_t sev = static_cast(msg.severity); if (sev >= messageCounts.size()) { messageCounts.resize(sev + 1); } messageCounts[sev]++; // Filter messages with too small severity return sev >= static_cast(minSeverity); } void ConcreteLogger::processSetDefaultLocation(const SourceLocation &loc) { defaultLocation = loc; } const Logger::File &ConcreteLogger::currentFile() const { if (!files.empty()) { return files.back(); } return EMPTY_FILE; } const std::string &ConcreteLogger::currentFilename() const { return currentFile().file; } const SourceLocation &ConcreteLogger::messageLocation(const Message &msg) const { if (msg.loc.valid()) { return msg.loc; } return defaultLocation; } SourceContext ConcreteLogger::messageContext(const Message &msg) const { const Logger::File &file = currentFile(); const SourceLocation &loc = messageLocation(msg); if (file.ctxCallback && loc.valid()) { return file.ctxCallback(loc, file.ctxCallbackData); } return SourceContext{}; } Severity ConcreteLogger::getMaxEncounteredSeverity() { for (ssize_t i = messageCounts.size() - 1; i >= 0; i--) { if (messageCounts[i] > 0) { return static_cast(i); } } return Severity::DEBUG; } size_t ConcreteLogger::getSeverityCount(Severity severity) { uint8_t sev = static_cast(severity); if (sev >= messageCounts.size()) { return 0; } return messageCounts[sev]; } void ConcreteLogger::reset() { files.clear(); messageCounts.clear(); } bool ConcreteLogger::hasError() { return getSeverityCount(Severity::ERROR) > 0 || getSeverityCount(Severity::FATAL_ERROR) > 0; } /* Class TerminalLogger */ void TerminalLogger::processMessage(const Message &msg) { Terminal t(useColor); // Fetch filename, position and context const std::string filename = currentFilename(); const SourceLocation pos = messageLocation(msg); const SourceContext ctx = messageContext(msg); // Print the file name bool hasFile = !filename.empty(); if (hasFile) { os << t.bright() << filename << t.reset(); } // Print line and column number if (pos.hasLine()) { if (hasFile) { os << ':'; } os << t.bright() << pos.line << t.reset(); if (pos.hasColumn()) { os << ':' << pos.column; } } // Print the optional seperator if (hasFile || pos.hasLine()) { os << ": "; } // Print the severity switch (msg.severity) { case Severity::DEBUG: break; case Severity::NOTE: os << t.color(Terminal::CYAN, true) << "note: "; break; case Severity::WARNING: os << t.color(Terminal::MAGENTA, true) << "warning: "; break; case Severity::ERROR: os << t.color(Terminal::RED, true) << "error: "; break; case Severity::FATAL_ERROR: os << t.color(Terminal::RED, true) << "fatal error: "; break; } os << t.reset(); // Print the actual message os << msg.msg << std::endl; // Print the error message context if available if (ctx.valid()) { size_t relPos = ctx.relPos; if (ctx.truncatedStart) { os << "[...] "; } os << ctx.text; if (ctx.truncatedEnd) { os << " [...]"; } os << std::endl; if (ctx.truncatedStart) { os << " "; } for (size_t i = 0; i < relPos; i++) { if (i < ctx.text.size() && ctx.text[i] == '\t') { os << '\t'; } else { os << ' '; } } os << t.color(Terminal::GREEN) << '^' << t.reset() << std::endl; } } }