// Copyright (c) 2020-now by the Zeek Project. See LICENSE for details. #pragma once #include #include #include #include #include #include #include /** Macro around `Logger::__debug` that avoids evaluation of the arguments if nothing is going to get logged. */ #define HILTI_DEBUG(dbg, ...) \ { \ if ( ::hilti::logger().isEnabled(dbg) ) \ ::hilti::logger()._debug(dbg, __VA_ARGS__); \ } namespace hilti { namespace logging { /** * A named debug stream. Debugging output can be send to it and will be * written out during runtime by the `Logger` if it has enabled the stream. */ class DebugStream { public: /** * @param name name of the stream, which must be unique across all stream */ explicit DebugStream(const std::string& name); bool operator<(const DebugStream& other) const { return _id < other._id; } const auto& name() const { return _name; } /** Returns the names of all available debug streams. */ static std::vector all(); /** Returns the stream for a given name. The stream must exist. */ static const auto& streamForName(const std::string& s) { return _streams().at(s); } private: uint64_t _id; std::string _name; static std::map& _streams(); }; namespace debug {} // namespace debug /** Logging level. */ enum class Level { Debug, Info, Warning, Error, FatalError, InternalError }; namespace detail { constexpr util::enum_::Value Levels[] = { {Level::Debug, "debug"}, {Level::Info, "info"}, {Level::Warning, "warning"}, {Level::Error, "error"}, {Level::FatalError, "fatal-error"}, {Level::InternalError, "internal-error"}, }; } // namespace detail constexpr auto to_string(Level m) { return util::enum_::to_string(m, detail::Levels); } namespace level { constexpr auto from_string(std::string_view s) { return util::enum_::from_string(s, detail::Levels); } } // namespace level /** Ostream-variant that forwards output to the central logger. */ class Stream : public std::ostream { private: class Buffer : public std::stringbuf { public: Buffer(logging::Level level); Buffer(logging::DebugStream dbg); private: int overflow(int ch) final; int sync() final; Level _level; std::optional _dbg; std::string _buffer; }; public: /** Creates a stream that sends output to a given logging level. */ Stream(logging::Level level) : std::ostream(&_buf), _buf(level) {} /** Creates a stream that sends output to a given debug stream. */ Stream(logging::DebugStream dbg) : std::ostream(&_buf), _buf(std::move(dbg)) {} private: Buffer _buf; }; } // namespace logging class Logger; /** * Returns the global logger. A default logger singleton is created at * startup. A custom logger can be set through `setLogger()`. */ inline Logger& logger(); /** * Sets a new logger as the global singleton. Returns the previous one. */ extern std::unique_ptr setLogger(std::unique_ptr logger); /** Logging system. */ class Logger { public: Logger(std::ostream& output_std = std::cerr, std::ostream& output_debug = std::cerr) : _output_std(output_std), _output_debug(output_debug) {} void log(logging::Level level, const std::string& msg, const Location& l = location::None); void info(const std::string& msg, const Location& l = location::None); void warning(const std::string& msg, const Location& l = location::None); void deprecated(const std::string& msg, const Location& l = location::None); void error(const std::string& msg, const Location& l = location::None); void error(const std::string& msg, const std::vector& context, const Location& l = location::None); void fatalError(const std::string& msg, const Location& l = location::None) __attribute__((noreturn)); void internalError(const std::string& msg, const Location& l = location::None) __attribute__((noreturn)); void info(const std::string& msg, const Node* n) { info(msg, n->location()); } void warning(const std::string& msg, const Node* n) { warning(msg, n->location()); } void deprecated(const std::string& msg, const Node* n) { deprecated(msg, n->location()); } void error(const std::string& msg, const Node* n) { error(msg, n->location()); } void fatalError(const std::string& msg, const Node* n) __attribute__((noreturn)) { fatalError(msg, n->location()); } void internalError(const std::string& msg, const Node* n) __attribute__((noreturn)) { internalError(msg, n->location()); } /** Use HILTI_DEBUG(...) instead. */ void _debug(const logging::DebugStream& dbg, const std::string& msg, const Location& l = location::None); template void log(std::string msg, const std::shared_ptr& n) { log(msg, n->location()); } template void info(std::string msg, const std::shared_ptr& n) { info(msg, n->location()); } template void warning(std::string msg, const std::shared_ptr& n) { warning(msg, n->location()); } template void error(std::string msg, const std::shared_ptr& n) { error(msg, n->location()); } template void error(std::string msg, std::vector context, const std::shared_ptr& n) { error(msg, context, n.location()); } template void error(Result r, const std::shared_ptr& n) { error(r.error().description(), n.location()); } template __attribute__((noreturn)) void fatalError(std::string msg, const std::shared_ptr& n) { fatalError(msg, n->location()); } template __attribute__((noreturn)) void internalError(std::string msg, const std::shared_ptr& n) { internalError(msg, n->location()); } template void debug(const logging::DebugStream& dbg, std::string msg, const std::shared_ptr& n) { debug(dbg, msg, n->location()); } void debugEnable(const logging::DebugStream& dbg); bool debugEnable(const std::string& dbg); void debugDisable(const logging::DebugStream& dbg) { _debug_streams.erase(dbg); } bool debugDisable(const std::string& dbg); bool isEnabled(const logging::DebugStream& dbg) { return _debug_streams.find(dbg) != _debug_streams.end(); } void debugPushIndent(const logging::DebugStream& dbg) { if ( isEnabled(dbg) ) _debug_streams[dbg] += 1; } void debugPopIndent(const logging::DebugStream& dbg) { if ( isEnabled(dbg) ) _debug_streams[dbg] -= 1; } void debugSetIndent(const logging::DebugStream& dbg, size_t indent) { if ( isEnabled(dbg) ) _debug_streams[dbg] = indent; } int errors() const { return _errors; } int warnings() const { return _warnings; } void reset() { _errors = _warnings = 0; } protected: void report(std::ostream& output, logging::Level level, size_t indent, const std::string& addl, const std::string& msg, const Location& l) const; private: friend Logger& logger(); // NOLINT friend std::unique_ptr setLogger(std::unique_ptr logger); // NOLINT std::ostream& _output_std = std::cerr; std::ostream& _output_debug = std::cerr; int _warnings = 0; int _errors = 0; std::map _debug_streams; static std::unique_ptr _singleton; }; inline Logger& logger() { if ( ! Logger::_singleton ) Logger::_singleton = std::make_unique(); return *Logger::_singleton; } namespace logging { /** * Helper class that increases debug indent on construction, and decreases it * again on destruction. */ class DebugPushIndent { public: DebugPushIndent(const logging::DebugStream& dbg) : _dbg(dbg) { logger().debugPushIndent(_dbg); } ~DebugPushIndent() { logger().debugPopIndent(_dbg); } DebugPushIndent() = delete; DebugPushIndent(const DebugPushIndent&) = delete; DebugPushIndent(DebugPushIndent&&) noexcept = delete; DebugPushIndent& operator=(const DebugPushIndent&) = delete; DebugPushIndent& operator=(DebugPushIndent&&) noexcept = delete; private: const logging::DebugStream& _dbg; }; } // namespace logging } // namespace hilti