| #include <c10/util/Backtrace.h> |
| #include <c10/util/Flags.h> |
| #include <c10/util/Logging.h> |
| #ifdef FBCODE_CAFFE2 |
| #include <folly/synchronization/SanitizeThread.h> |
| #endif |
| |
| #include <algorithm> |
| #include <cstdlib> |
| #include <cstring> |
| #include <iostream> |
| #include <numeric> |
| |
| // Common code that we use regardless of whether we use glog or not. |
| |
| C10_DEFINE_bool( |
| caffe2_use_fatal_for_enforce, |
| false, |
| "If set true, when CAFFE_ENFORCE is not met, abort instead " |
| "of throwing an exception."); |
| |
| namespace c10 { |
| |
| namespace { |
| std::function<string()>* GetFetchStackTrace() { |
| static std::function<string()> func = []() { |
| return get_backtrace(/*frames_to_skip=*/1); |
| }; |
| return &func; |
| }; |
| } // namespace |
| |
| void SetStackTraceFetcher(std::function<string(void)> fetcher) { |
| *GetFetchStackTrace() = std::move(fetcher); |
| } |
| |
| void ThrowEnforceNotMet( |
| const char* file, |
| const int line, |
| const char* condition, |
| const std::string& msg, |
| const void* caller) { |
| c10::Error e(file, line, condition, msg, (*GetFetchStackTrace())(), caller); |
| if (FLAGS_caffe2_use_fatal_for_enforce) { |
| LOG(FATAL) << e.msg(); |
| } |
| throw std::move(e); |
| } |
| |
| void ThrowEnforceNotMet( |
| const char* file, |
| const int line, |
| const char* condition, |
| const char* msg, |
| const void* caller) { |
| ThrowEnforceNotMet(file, line, condition, std::string(msg), caller); |
| } |
| |
| void ThrowEnforceFiniteNotMet( |
| const char* file, |
| const int line, |
| const char* condition, |
| const std::string& msg, |
| const void* caller) { |
| throw c10::EnforceFiniteError( |
| file, line, condition, msg, (*GetFetchStackTrace())(), caller); |
| } |
| |
| void ThrowEnforceFiniteNotMet( |
| const char* file, |
| const int line, |
| const char* condition, |
| const char* msg, |
| const void* caller) { |
| ThrowEnforceFiniteNotMet(file, line, condition, std::string(msg), caller); |
| } |
| // PyTorch-style error message |
| // (This must be defined here for access to GetFetchStackTrace) |
| Error::Error(SourceLocation source_location, std::string msg) |
| : Error( |
| std::move(msg), |
| str("Exception raised from ", |
| source_location, |
| " (most recent call first):\n", |
| (*GetFetchStackTrace())())) {} |
| |
| using APIUsageLoggerType = std::function<void(const std::string&)>; |
| using APIUsageMetadataLoggerType = std::function<void( |
| const std::string&, |
| const std::map<std::string, std::string>& metadata_map)>; |
| using DDPUsageLoggerType = std::function<void(const DDPLoggingData&)>; |
| |
| namespace { |
| bool IsAPIUsageDebugMode() { |
| const char* val = getenv("PYTORCH_API_USAGE_STDERR"); |
| return val && *val; // any non-empty value |
| } |
| |
| void APIUsageDebug(const string& event) { |
| // use stderr to avoid messing with glog |
| std::cerr << "PYTORCH_API_USAGE " << event << std::endl; |
| } |
| |
| APIUsageLoggerType* GetAPIUsageLogger() { |
| static APIUsageLoggerType func = |
| IsAPIUsageDebugMode() ? &APIUsageDebug : [](const string&) {}; |
| return &func; |
| }; |
| |
| APIUsageMetadataLoggerType* GetAPIUsageMetadataLogger() { |
| static APIUsageMetadataLoggerType func = |
| [](const std::string&, |
| const std::map<std::string, std::string>& metadata_map) {}; |
| return &func; |
| }; |
| |
| DDPUsageLoggerType* GetDDPUsageLogger() { |
| static DDPUsageLoggerType func = [](const DDPLoggingData&) {}; |
| return &func; |
| }; |
| } // namespace |
| |
| void SetAPIUsageLogger(std::function<void(const std::string&)> logger) { |
| TORCH_CHECK(logger); |
| *GetAPIUsageLogger() = std::move(logger); |
| } |
| |
| void SetAPIUsageMetadataLogger( |
| std::function<void( |
| const std::string&, |
| const std::map<std::string, std::string>& metadata_map)> logger) { |
| TORCH_CHECK(logger); |
| *GetAPIUsageMetadataLogger() = std::move(logger); |
| } |
| |
| void SetPyTorchDDPUsageLogger( |
| std::function<void(const DDPLoggingData&)> logger) { |
| TORCH_CHECK(logger); |
| *GetDDPUsageLogger() = std::move(logger); |
| } |
| |
| void LogAPIUsage(const std::string& event) try { |
| if (auto logger = GetAPIUsageLogger()) |
| (*logger)(event); |
| } catch (std::bad_function_call&) { |
| // static destructor race |
| } |
| |
| void LogAPIUsageMetadata( |
| const std::string& context, |
| const std::map<std::string, std::string>& metadata_map) try { |
| if (auto logger = GetAPIUsageMetadataLogger()) |
| (*logger)(context, metadata_map); |
| } catch (std::bad_function_call&) { |
| // static destructor race |
| } |
| |
| void LogPyTorchDDPUsage(const DDPLoggingData& ddpData) try { |
| if (auto logger = GetDDPUsageLogger()) |
| (*logger)(ddpData); |
| } catch (std::bad_function_call&) { |
| // static destructor race |
| } |
| |
| namespace detail { |
| bool LogAPIUsageFakeReturn(const std::string& event) try { |
| if (auto logger = GetAPIUsageLogger()) |
| (*logger)(event); |
| return true; |
| } catch (std::bad_function_call&) { |
| // static destructor race |
| return true; |
| } |
| |
| namespace { |
| |
| void setLogLevelFlagFromEnv(); |
| |
| } // namespace |
| } // namespace detail |
| } // namespace c10 |
| |
| #if defined(C10_USE_GFLAGS) && defined(C10_USE_GLOG) |
| // When GLOG depends on GFLAGS, these variables are being defined in GLOG |
| // directly via the GFLAGS definition, so we will use DECLARE_* to declare |
| // them, and use them in Caffe2. |
| // GLOG's minloglevel |
| DECLARE_int32(minloglevel); |
| // GLOG's verbose log value. |
| DECLARE_int32(v); |
| // GLOG's logtostderr value |
| DECLARE_bool(logtostderr); |
| #endif // defined(C10_USE_GFLAGS) && defined(C10_USE_GLOG) |
| |
| #if !defined(C10_USE_GLOG) |
| // This backward compatibility flags are in order to deal with cases where |
| // Caffe2 are not built with glog, but some init flags still pass in these |
| // flags. They may go away in the future. |
| C10_DEFINE_int32(minloglevel, 0, "Equivalent to glog minloglevel"); |
| C10_DEFINE_int32(v, 0, "Equivalent to glog verbose"); |
| C10_DEFINE_bool(logtostderr, false, "Equivalent to glog logtostderr"); |
| #endif // !defined(c10_USE_GLOG) |
| |
| #ifdef C10_USE_GLOG |
| |
| // Provide easy access to the above variables, regardless whether GLOG is |
| // dependent on GFLAGS or not. Note that the namespace (fLI, fLB) is actually |
| // consistent between GLOG and GFLAGS, so we can do the below declaration |
| // consistently. |
| namespace c10 { |
| using fLB::FLAGS_logtostderr; |
| using fLI::FLAGS_minloglevel; |
| using fLI::FLAGS_v; |
| } // namespace c10 |
| |
| C10_DEFINE_int( |
| caffe2_log_level, |
| google::GLOG_WARNING, |
| "The minimum log level that caffe2 will output."); |
| |
| // Google glog's api does not have an external function that allows one to check |
| // if glog is initialized or not. It does have an internal function - so we are |
| // declaring it here. This is a hack but has been used by a bunch of others too |
| // (e.g. Torch). |
| namespace google { |
| namespace glog_internal_namespace_ { |
| bool IsGoogleLoggingInitialized(); |
| } // namespace glog_internal_namespace_ |
| } // namespace google |
| |
| namespace c10 { |
| namespace { |
| |
| void initGoogleLogging(char const* name) { |
| #if !defined(_MSC_VER) |
| // This trick can only be used on UNIX platforms |
| if (!::google::glog_internal_namespace_::IsGoogleLoggingInitialized()) |
| #endif |
| { |
| ::google::InitGoogleLogging(name); |
| #if !defined(_MSC_VER) |
| // This is never defined on Windows |
| ::google::InstallFailureSignalHandler(); |
| #endif |
| } |
| } |
| |
| } // namespace |
| |
| void initLogging() { |
| detail::setLogLevelFlagFromEnv(); |
| |
| UpdateLoggingLevelsFromFlags(); |
| } |
| |
| bool InitCaffeLogging(int* argc, char** argv) { |
| if (*argc == 0) { |
| return true; |
| } |
| |
| initGoogleLogging(argv[0]); |
| |
| UpdateLoggingLevelsFromFlags(); |
| |
| return true; |
| } |
| |
| void UpdateLoggingLevelsFromFlags() { |
| #ifdef FBCODE_CAFFE2 |
| // TODO(T82645998): Fix data race exposed by TSAN. |
| folly::annotate_ignore_thread_sanitizer_guard g(__FILE__, __LINE__); |
| #endif |
| // If caffe2_log_level is set and is lower than the min log level by glog, |
| // we will transfer the caffe2_log_level setting to glog to override that. |
| FLAGS_minloglevel = std::min(FLAGS_caffe2_log_level, FLAGS_minloglevel); |
| // If caffe2_log_level is explicitly set, let's also turn on logtostderr. |
| if (FLAGS_caffe2_log_level < google::GLOG_WARNING) { |
| FLAGS_logtostderr = 1; |
| } |
| // Also, transfer the caffe2_log_level verbose setting to glog. |
| if (FLAGS_caffe2_log_level < 0) { |
| FLAGS_v = std::min(FLAGS_v, -FLAGS_caffe2_log_level); |
| } |
| } |
| |
| void ShowLogInfoToStderr() { |
| FLAGS_logtostderr = 1; |
| FLAGS_minloglevel = std::min(FLAGS_minloglevel, google::GLOG_INFO); |
| } |
| } // namespace c10 |
| |
| #else // !C10_USE_GLOG |
| |
| #ifdef ANDROID |
| #include <android/log.h> |
| #endif // ANDROID |
| |
| C10_DEFINE_int( |
| caffe2_log_level, |
| c10::GLOG_WARNING, |
| "The minimum log level that caffe2 will output."); |
| |
| namespace c10 { |
| |
| void initLogging() { |
| detail::setLogLevelFlagFromEnv(); |
| } |
| |
| bool InitCaffeLogging(int* argc, char** argv) { |
| // When doing InitCaffeLogging, we will assume that caffe's flag parser has |
| // already finished. |
| if (*argc == 0) |
| return true; |
| if (!c10::CommandLineFlagsHasBeenParsed()) { |
| std::cerr << "InitCaffeLogging() has to be called after " |
| "c10::ParseCommandLineFlags. Modify your program to make sure " |
| "of this." |
| << std::endl; |
| return false; |
| } |
| if (FLAGS_caffe2_log_level > GLOG_FATAL) { |
| std::cerr << "The log level of Caffe2 has to be no larger than GLOG_FATAL(" |
| << GLOG_FATAL << "). Capping it to GLOG_FATAL." << std::endl; |
| FLAGS_caffe2_log_level = GLOG_FATAL; |
| } |
| return true; |
| } |
| |
| void UpdateLoggingLevelsFromFlags() {} |
| |
| void ShowLogInfoToStderr() { |
| FLAGS_caffe2_log_level = GLOG_INFO; |
| } |
| |
| MessageLogger::MessageLogger(const char* file, int line, int severity) |
| : severity_(severity) { |
| if (severity_ < FLAGS_caffe2_log_level) { |
| // Nothing needs to be logged. |
| return; |
| } |
| #ifdef ANDROID |
| tag_ = "native"; |
| #else // !ANDROID |
| tag_ = ""; |
| #endif // ANDROID |
| /* |
| time_t rawtime; |
| struct tm * timeinfo; |
| time(&rawtime); |
| timeinfo = localtime(&rawtime); |
| std::chrono::nanoseconds ns = |
| std::chrono::duration_cast<std::chrono::nanoseconds>( |
| std::chrono::high_resolution_clock::now().time_since_epoch()); |
| */ |
| stream_ << "[" |
| << CAFFE2_SEVERITY_PREFIX[std::min(4, GLOG_FATAL - severity_)] |
| //<< (timeinfo->tm_mon + 1) * 100 + timeinfo->tm_mday |
| //<< std::setfill('0') |
| //<< " " << std::setw(2) << timeinfo->tm_hour |
| //<< ":" << std::setw(2) << timeinfo->tm_min |
| //<< ":" << std::setw(2) << timeinfo->tm_sec |
| //<< "." << std::setw(9) << ns.count() % 1000000000 |
| << " " << c10::detail::StripBasename(std::string(file)) << ":" << line |
| << "] "; |
| } |
| |
| // Output the contents of the stream to the proper channel on destruction. |
| MessageLogger::~MessageLogger() { |
| if (severity_ < FLAGS_caffe2_log_level) { |
| // Nothing needs to be logged. |
| return; |
| } |
| stream_ << "\n"; |
| #ifdef ANDROID |
| static const int android_log_levels[] = { |
| ANDROID_LOG_FATAL, // LOG_FATAL |
| ANDROID_LOG_ERROR, // LOG_ERROR |
| ANDROID_LOG_WARN, // LOG_WARNING |
| ANDROID_LOG_INFO, // LOG_INFO |
| ANDROID_LOG_DEBUG, // VLOG(1) |
| ANDROID_LOG_VERBOSE, // VLOG(2) .. VLOG(N) |
| }; |
| int android_level_index = GLOG_FATAL - std::min(GLOG_FATAL, severity_); |
| int level = android_log_levels[std::min(android_level_index, 5)]; |
| // Output the log string the Android log at the appropriate level. |
| __android_log_print(level, tag_, "%s", stream_.str().c_str()); |
| // Indicate termination if needed. |
| if (severity_ == GLOG_FATAL) { |
| __android_log_print(ANDROID_LOG_FATAL, tag_, "terminating.\n"); |
| } |
| #else // !ANDROID |
| if (severity_ >= FLAGS_caffe2_log_level) { |
| // If not building on Android, log all output to std::cerr. |
| std::cerr << stream_.str(); |
| // Simulating the glog default behavior: if the severity is above INFO, |
| // we flush the stream so that the output appears immediately on std::cerr. |
| // This is expected in some of our tests. |
| if (severity_ > GLOG_INFO) { |
| std::cerr << std::flush; |
| } |
| } |
| #endif // ANDROID |
| if (severity_ == GLOG_FATAL) { |
| DealWithFatal(); |
| } |
| } |
| |
| } // namespace c10 |
| |
| #endif // !C10_USE_GLOG |
| |
| namespace c10 { |
| namespace detail { |
| namespace { |
| |
| void setLogLevelFlagFromEnv() { |
| const char* level_str = std::getenv("TORCH_CPP_LOG_LEVEL"); |
| |
| // Not set, fallback to the default level (i.e. WARNING). |
| std::string level{level_str != nullptr ? level_str : ""}; |
| if (level.empty()) { |
| return; |
| } |
| |
| std::transform( |
| level.begin(), level.end(), level.begin(), [](unsigned char c) { |
| return toupper(c); |
| }); |
| |
| if (level == "0" || level == "INFO") { |
| FLAGS_caffe2_log_level = 0; |
| |
| return; |
| } |
| if (level == "1" || level == "WARNING") { |
| FLAGS_caffe2_log_level = 1; |
| |
| return; |
| } |
| if (level == "2" || level == "ERROR") { |
| FLAGS_caffe2_log_level = 2; |
| |
| return; |
| } |
| if (level == "3" || level == "FATAL") { |
| FLAGS_caffe2_log_level = 3; |
| |
| return; |
| } |
| |
| std::cerr |
| << "`TORCH_CPP_LOG_LEVEL` environment variable cannot be parsed. Valid values are " |
| "`INFO`, `WARNING`, `ERROR`, and `FATAL` or their numerical equivalents `0`, `1`, " |
| "`2`, and `3`." |
| << std::endl; |
| } |
| |
| } // namespace |
| } // namespace detail |
| } // namespace c10 |