Program Listing for File errors.hpp

Return to documentation for file (src/navtk/errors.hpp)

#pragma once

#include <mutex>
#include <ostream>
#include <stdexcept>

#include <spdlog/spdlog.h>

// These includes must appear after #include <spdlog/spdlog.h> to prevent linker errors when
// formatting complex types such as std::vector.
#include <spdlog/fmt/bundled/ostream.h>
#include <spdlog/fmt/bundled/ranges.h>

namespace navtk {

enum class ErrorMode {
    OFF,
    LOG,
    DIE
};

std::ostream& operator<<(std::ostream& os, ErrorMode error_mode);

typedef std::runtime_error DefaultLogOrThrowException;

constexpr spdlog::level::level_enum DEFAULT_LOG_OR_THROW_LEVEL = spdlog::level::level_enum::err;

ErrorMode get_global_error_mode();

class ErrorModeLock {
public:
    ErrorModeLock(ErrorMode target_mode, bool enable_restore = true);

    ErrorModeLock(ErrorModeLock&& src);

    ErrorModeLock(const ErrorModeLock&)            = delete;
    ErrorModeLock& operator=(ErrorModeLock&&)      = delete;
    ErrorModeLock& operator=(const ErrorModeLock&) = delete;

    virtual ~ErrorModeLock();

protected:
    void unlock();

    void relock();

private:
    bool restore_enabled;
    bool restore_needed;
    std::unique_lock<std::recursive_mutex> lock;
    ErrorMode restore_mode;
    ErrorMode target_mode;
};


// TODO(PNTOS-387): optional timeout parameter to prevent deadlocks
void set_global_error_mode(ErrorMode mode);

#ifndef NEED_DOXYGEN_EXHALE_WORKAROUND
// we hide the 'full' implementation of log_or_throw in a detail namespace to prevent infinitely
// recursive template evaluation when trying to resolve the overloads -- this allows the other
// overloads to invoke detail::log_or_throw and we know we're always invoking the "main" one.
// End users can also invoke the main one because of the `using detail::log_or_throw` line below.
namespace detail {
#endif

template <typename Exc                    = DefaultLogOrThrowException,
          spdlog::level::level_enum Level = DEFAULT_LOG_OR_THROW_LEVEL,
          typename... FormatArgs>
void log_or_throw(ErrorMode mode,
                  spdlog::format_string_t<FormatArgs...> fmt,
                  FormatArgs&&... args) {
    auto message = fmt::format(fmt, std::forward<FormatArgs>(args)...);
    if (mode != ErrorMode::OFF) spdlog::log(Level, "{}", message);
    if (mode == ErrorMode::DIE) throw Exc(std::move(message));
}

#ifndef NEED_DOXYGEN_EXHALE_WORKAROUND
}

using detail::log_or_throw;

template <typename Exc                    = DefaultLogOrThrowException,
          spdlog::level::level_enum Level = DEFAULT_LOG_OR_THROW_LEVEL,
          typename... FormatArgs>
void log_or_throw(spdlog::format_string_t<FormatArgs...> fmt, FormatArgs&&... args) {
    detail::log_or_throw<Exc, Level>(
        get_global_error_mode(), fmt, std::forward<FormatArgs>(args)...);
}

template <spdlog::level::level_enum Level, typename... FormatArgs>
void log_or_throw(spdlog::format_string_t<FormatArgs...> fmt, FormatArgs&&... args) {
    detail::log_or_throw<DefaultLogOrThrowException, Level>(
        get_global_error_mode(), fmt, std::forward<FormatArgs>(args)...);
}

template <spdlog::level::level_enum Level, typename... FormatArgs>
void log_or_throw(ErrorMode mode,
                  spdlog::format_string_t<FormatArgs...> fmt,
                  FormatArgs&&... args) {
    detail::log_or_throw<DefaultLogOrThrowException, Level>(
        mode, fmt, std::forward<FormatArgs>(args)...);
}

// SEE ALSO: py_log_or_throw_ in bindings/python/navtk.cpp, a re-implementation that uses runtime
// values instead of template parameters.

#endif  // NEED_DOXYGEN_EXHALE_WORKAROUND

}  // namespace navtk