/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*! * \file tvm/runtime/logging.h * \brief logging utilities * * We use the following facilities from tvm/ffi/error.h for * error handling and checking: * * - TVM_FFI_THROW(ErrorKind) << "msg"; * - TVM_FFI_CHECK(cond, ErrorKind) << "msg"; * - TVM_FFI_CHECK_EQ(x, y, ErrorKind) << "msg"; * - TVM_FFI_ICHECK(x) << "msg"; // InternalError * - TVM_FFI_ICHECK_EQ(x, y) << "msg"; * - TVM_FFI_DCHECK(x) << "msg"; // Debug-only InternalError * * LOG(INFO), LOG(WARNING), LOG(ERROR) are kept for logging. * LOG(FATAL) is kept for completeness, it throws InternalError. */ #ifndef TVM_RUNTIME_LOGGING_H_ #define TVM_RUNTIME_LOGGING_H_ #include #include #include #include #include #include #include #include #include /*! * \brief Whether or not customize the logging output. * If log customize is enabled, the user must implement * tvm::runtime::detail::LogFatalImpl and tvm::runtime::detail::LogMessageImpl. */ #ifndef TVM_LOG_CUSTOMIZE #define TVM_LOG_CUSTOMIZE 0 #endif namespace tvm { namespace runtime { /*! \brief Internal implementation */ namespace detail { // Provide support for customized logging. #if TVM_LOG_CUSTOMIZE /*! * \brief Custom implementations of LogFatal. * * \sa TVM_LOG_CUSTOMIZE */ [[noreturn]] TVM_RUNTIME_DLL void LogFatalImpl(const std::string& file, int lineno, const std::string& message); /*! * \brief Custom implementations of LogMessage. * * \sa TVM_LOG_CUSTOMIZE */ TVM_RUNTIME_DLL void LogMessageImpl(const std::string& file, int lineno, int level, const std::string& message); /*! * \brief Class to accumulate an error message and throw it. Do not use * directly, instead use LOG(FATAL). */ class LogFatal { public: LogFatal(const std::string& file, int lineno) : file_(file), lineno_(lineno) {} #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable : 4722) #endif [[noreturn]] ~LogFatal() noexcept(false) { LogFatalImpl(file_, lineno_, stream_.str()); } #ifdef _MSC_VER #pragma warning(pop) #endif std::ostringstream& stream() { return stream_; } private: std::ostringstream stream_; std::string file_; int lineno_; }; /*! * \brief Class to accumulate an log message. Do not use directly, instead use * LOG(INFO), LOG(WARNING), LOG(ERROR). */ class LogMessage { public: LogMessage(const std::string& file, int lineno, int level) : file_(file), lineno_(lineno), level_(level) {} ~LogMessage() { LogMessageImpl(file_, lineno_, level_, stream_.str()); } std::ostringstream& stream() { return stream_; } private: std::string file_; int lineno_; int level_; std::ostringstream stream_; }; #else /*! * \brief Class to accumulate an error message and throw it. Do not use * directly, instead use LOG(FATAL). * \note The `LogFatal` class is designed to be an empty class to reduce stack size usage. * To play this trick, we use the thread-local storage to store its internal data. */ class LogFatal { public: TVM_FFI_NO_INLINE LogFatal(const char* file, int lineno) { GetEntry().Init(file, lineno); } #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable : 4722) #endif [[noreturn]] ~LogFatal() noexcept(false) { GetEntry().Finalize(); throw; } #ifdef _MSC_VER #pragma warning(pop) #endif std::ostringstream& stream() { return GetEntry().stream_; } private: struct Entry { void Init(const char* file, int lineno) { this->stream_.str(""); this->file_ = file; this->lineno_ = lineno; } [[noreturn]] TVM_FFI_NO_INLINE ffi::Error Finalize() noexcept(false) { ffi::Error error("InternalError", stream_.str(), TVMFFIBacktrace(file_.c_str(), lineno_, "", 0)); throw error; } std::ostringstream stream_; std::string file_; int lineno_; }; TVM_FFI_NO_INLINE TVM_RUNTIME_DLL static Entry& GetEntry(); }; /*! * \brief Class to accumulate an log message. Do not use directly, instead use * LOG(INFO), LOG(WARNING), LOG(ERROR). */ class LogMessage { public: LogMessage(const std::string& file, int lineno, int level) { // Use inline constexpr to avoid ODR-issues with hidden static members // when libtvm_runtime.so is built with -fvisibility=hidden. static constexpr const char* kLevelStrings[] = { ": Debug: ", // TVM_LOG_LEVEL_DEBUG ": ", // TVM_LOG_LEVEL_INFO ": Warning: ", // TVM_LOG_LEVEL_WARNING ": Error: ", // TVM_LOG_LEVEL_ERROR }; std::time_t t = std::time(nullptr); stream_ << "[" << std::put_time(std::localtime(&t), "%H:%M:%S") << "] " << file << ":" << lineno << kLevelStrings[level]; } TVM_FFI_NO_INLINE ~LogMessage() { std::cerr << stream_.str() << std::endl; } std::ostringstream& stream() { return stream_; } private: std::ostringstream stream_; }; #endif // This class is used to explicitly ignore values in the conditional // logging macros. This avoids compiler warnings like "value computed // is not used" and "statement has no effect". class LogMessageVoidify { public: LogMessageVoidify() {} // This has to be an operator with a precedence lower than << but // higher than "?:". See its usage. void operator&(std::ostream&) {} }; /*! \brief Captures the state of the \p TVM_LOG_DEBUG environment flag. */ class TVM_RUNTIME_DLL TvmLogDebugSettings { public: /*! * \brief Parses the \p TVM_LOG_DEBUG environment flag as per the specification given by * \p DebugLoggingEnabled and \p VerboseLoggingEnabled, and caches the result. */ inline static const TvmLogDebugSettings& FromFlag() { // Parse and cache the verbosity level map. static const auto* settings = new TvmLogDebugSettings(TvmLogDebugSettings::ParseSpec(std::getenv("TVM_LOG_DEBUG"))); return *settings; } /*! * \brief Parses \p opt_spec as per specification for \p TVM_LOG_DEBUG given by * \p DebugLoggingEnabled and \p VerboseLoggingEnabled. Throws if specification is ill-formed. */ static TvmLogDebugSettings ParseSpec(const char* opt_spec); /*! * \brief Implements \p VerboseLoggingEnabled below w.r.t. the already parsed \p TVM_LOG_DEBUG * environment variable. */ inline bool VerboseEnabled(const char* opt_filename, int level) const { if (opt_filename == nullptr || level < 0 || vlog_level_map_.empty()) { return false; } return VerboseEnabledImpl(opt_filename, level); } /*! \brief Returns true if \p DLOG statements should be executed. */ bool dlog_enabled() const { return dlog_enabled_; } private: // Slow path for VerboseEnabled. bool VerboseEnabledImpl(const std::string& filename, int level) const; /*! \brief If true, DLOG statements are enabled. */ bool dlog_enabled_ = false; /*! * \brief A map from canonicalized filenames to the maximum VLOG verbosity level for that file. * May also contain the 'wildcard' entry \p "DEFAULT" representing the level for all other files. */ std::unordered_map vlog_level_map_; }; /*! * \brief Returns true if a DLOG statement is enabled by the \p TVM_LOG_DEBUG environment * variable. Requires: * \code * TVM_LOG_DEBUG=1 * \endcode * or a valid setting as described by \p VerboseLoggingEnabled below. */ inline bool DebugLoggingEnabled() { static int state = 0; if (state == 0) { state = TvmLogDebugSettings::FromFlag().dlog_enabled() ? 1 : -1; } return state == 1; } /*! * \brief Returns true if a VLOG statement in \p filename is enabled by the \p TVM_LOG_DEBUG * environment variable for logging at verbosity \p level. Levels should be non-negative. * * Filenames are canonicalized to be w.r.t. the src/ dir of the TVM tree. (VLOG's should not * appear under include/). * * To enable file \p ir/bar.cc for level 0 only set: * \code * TVM_LOG_DEBUG="ir/bar.cc=0" * \endcode * * To enable all files up to level 3 but disable \p ir/bar.cc set: * \code * TVM_LOG_DEBUG="DEFAULT=2,ir/bar.cc=-1" * \endcode * * Any of these settings will also enable DLOG statements. */ inline bool VerboseLoggingEnabled(const char* opt_filename, int level) { return TvmLogDebugSettings::FromFlag().VerboseEnabled(opt_filename, level); } /*! * \brief A stack of VLOG context messages. * * For use by \p VLOG_CONTEXT macro only. */ class VLogContext { public: void Push(std::stringstream* stream) { context_stack_.push_back(stream); } void Pop() { if (!context_stack_.empty()) { context_stack_.pop_back(); } } std::string str() const; private: std::vector context_stack_; }; /*! \brief Get thread local \p VLogContext for tracking a stack of VLOG context messages. */ inline VLogContext* ThreadLocalVLogContext() { static thread_local VLogContext inst; return &inst; } /*! * \brief A RAII class to push/pos a VLOG context message onto the thread-local stack. * * For use by \p VLOG_CONTEXT macro only. */ class VLogContextEntry { public: VLogContextEntry() { ThreadLocalVLogContext()->Push(&sstream_); } ~VLogContextEntry() { ThreadLocalVLogContext()->Pop(); } std::ostream& stream() { return sstream_; } private: std::stringstream sstream_; }; } // namespace detail #define TVM_LOG_LEVEL_DEBUG 0 #define TVM_LOG_LEVEL_INFO 1 #define TVM_LOG_LEVEL_WARNING 2 #define TVM_LOG_LEVEL_ERROR 3 #define TVM_LOG_LEVEL_FATAL 4 #define LOG(level) LOG_##level #define LOG_DEBUG \ ::tvm::runtime::detail::LogMessage(__FILE__, __LINE__, TVM_LOG_LEVEL_DEBUG).stream() #define LOG_FATAL ::tvm::runtime::detail::LogFatal(__FILE__, __LINE__).stream() #define LOG_INFO ::tvm::runtime::detail::LogMessage(__FILE__, __LINE__, TVM_LOG_LEVEL_INFO).stream() #define LOG_ERROR \ ::tvm::runtime::detail::LogMessage(__FILE__, __LINE__, TVM_LOG_LEVEL_ERROR).stream() #define LOG_WARNING \ ::tvm::runtime::detail::LogMessage(__FILE__, __LINE__, TVM_LOG_LEVEL_WARNING).stream() #define LOG_IF(severity, condition) \ !(condition) ? (void)0 : ::tvm::runtime::detail::LogMessageVoidify() & LOG(severity) #if TVM_LOG_DEBUG #define LOG_DFATAL LOG_FATAL #define DFATAL FATAL #define DLOG(severity) LOG_IF(severity, ::tvm::runtime::detail::DebugLoggingEnabled()) #define DLOG_IF(severity, condition) \ LOG_IF(severity, ::tvm::runtime::detail::DebugLoggingEnabled() && (condition)) /*! * \brief If the \p TVM_LOG_DEBUG build flag is enabled, push a context message onto an internal * stack. All VLOG messages will include this stack in their prefix to help with debugging. E.g.: * \code * VLOG_CONTEXT << "my context"; * VLOG(1) << "my log message"; * \endcode * Thread safe. No-op with no execution overhead if the \p TVM_LOG_DEBUG build flag is not enabled. */ #define VLOG_CONTEXT \ ::tvm::runtime::detail::VLogContextEntry vlog_entry_; \ vlog_entry_.stream() #else #define LOG_DFATAL LOG_ERROR #define DFATAL ERROR #define DLOG(severity) true ? (void)0 : ::tvm::runtime::detail::LogMessageVoidify() & LOG(severity) #define DLOG_IF(severity, condition) \ (true || !(condition)) ? (void)0 : ::tvm::runtime::detail::LogMessageVoidify() & LOG(severity) #define VLOG_CONTEXT true ? (void)0 : ::tvm::runtime::detail::LogMessageVoidify() & LOG(INFO) #endif /*! * \brief If the \p TVM_LOG_DEBUG build flag is enabled, and the containing file has been enabled * at \p level or greater in the \p TVM_LOG_DEBUG environment variable, then log a message at * \p INFO severity. * * See \p VerboseLoggingEnabled for the format of the \p TVM_LOG_DEBUG environment variable. * Thread safe. No-op with no execution overhead if the \p TVM_LOG_DEBUG build flag is not enabled. * No-op with some execution overhead if the \p TVM_LOG_DEBUG build flag is enabled but the * containing file is not enabled. */ #define VLOG(level) \ DLOG_IF(INFO, ::tvm::runtime::detail::VerboseLoggingEnabled(__FILE__, (level))) \ << ::tvm::runtime::detail::ThreadLocalVLogContext()->str() } // namespace runtime } // namespace tvm #endif // TVM_RUNTIME_LOGGING_H_