| // Copyright 2017-2020 The Verible Authors. | |
| // | |
| // Licensed 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. | |
| #include "common/util/logging.h" | |
| // TODO(hzeller): avoid being dependent on absl internal | |
| #include <cstdlib> | |
| #include <cstring> | |
| #include <ctime> | |
| #include <string> | |
| #include <unordered_map> | |
| #include "absl/base/internal/cycleclock.h" | |
| #include "absl/base/internal/sysinfo.h" | |
| namespace verible { | |
| void VeribleAddLogSink(VeribleLogSink* sink) { | |
| // LogSink is not implemented. | |
| // If necessary, one can add the log sink support as follows. | |
| // 1. Define a global vector<VeribleLogSink> to keep track of all registered | |
| // VeribleLogSink objects. Protect the global vector with mutex to make it | |
| // thread-safe. | |
| // 2. Add/remove elements from the global vector<VeribleLogSink> in | |
| // VeribleAddLogSink | |
| // and VeribleRemoveLogSink function | |
| // 3. Add logic in LogMessage::GenerateLogMessage() below to dispatch log | |
| // messages to all the registered log sinks. | |
| } | |
| void VeribleRemoveLogSink(VeribleLogSink* sink) { | |
| // LogSink is not implemented. | |
| } | |
| namespace internal { | |
| namespace { | |
| int ParseInteger(const char* str, size_t size) { | |
| // Ideally we would use env_var / safe_strto64, but it is | |
| // hard to use here without pulling in a lot of dependencies, | |
| // so we use std:istringstream instead | |
| std::string integer_str(str, size); | |
| std::istringstream ss(integer_str); | |
| int level = 0; | |
| ss >> level; | |
| return level; | |
| } | |
| // Parse log level (int64_t) from environment variable (char*) | |
| int64_t LogLevelStrToInt(const char* tf_env_var_val) { | |
| if (tf_env_var_val == nullptr) { | |
| return 0; | |
| } | |
| return ParseInteger(tf_env_var_val, strlen(tf_env_var_val)); | |
| } | |
| // Using StringPiece breaks Windows build. | |
| struct StringData { | |
| struct Hasher { | |
| size_t operator()(const StringData& sdata) const { | |
| // For dependency reasons, we cannot use hash.h here. Use DBJHash instead. | |
| size_t hash = 5381; | |
| const char* data = sdata.data; | |
| for (const char* top = data + sdata.size; data < top; ++data) { | |
| hash = ((hash << 5) + hash) + (*data); | |
| } | |
| return hash; | |
| } | |
| }; | |
| StringData() = default; | |
| StringData(const char* data, size_t size) : data(data), size(size) {} | |
| bool operator==(const StringData& rhs) const { | |
| return size == rhs.size && memcmp(data, rhs.data, size) == 0; | |
| } | |
| const char* data = nullptr; | |
| size_t size = 0; | |
| }; | |
| using VmoduleMap = std::unordered_map<StringData, int, StringData::Hasher>; | |
| // Returns a mapping from module name to VLOG level, derived from the | |
| // TF_CPP_VMODULE environment variable; ownership is transferred to the caller. | |
| VmoduleMap* VmodulesMapFromEnv() { | |
| // The value of the env var is supposed to be of the form: | |
| // "foo=1,bar=2,baz=3" | |
| const char* env = getenv("TF_CPP_VMODULE"); | |
| if (env == nullptr) { | |
| // If there is no TF_CPP_VMODULE configuration (most common case), return | |
| // nullptr so that the ShouldVlogModule() API can fast bail out of it. | |
| return nullptr; | |
| } | |
| // The memory returned by getenv() can be invalidated by following getenv() or | |
| // setenv() calls. And since we keep references to it in the VmoduleMap in | |
| // form of StringData objects, make a copy of it. | |
| const char* env_data = strdup(env); | |
| VmoduleMap* result = new VmoduleMap(); | |
| while (true) { | |
| const char* eq = strchr(env_data, '='); | |
| if (eq == nullptr) { | |
| break; | |
| } | |
| const char* after_eq = eq + 1; | |
| // Comma either points at the next comma delimiter, or at a null terminator. | |
| // We check that the integer we parse ends at this delimiter. | |
| const char* comma = strchr(after_eq, ','); | |
| const char* new_env_data; | |
| if (comma == nullptr) { | |
| comma = strchr(after_eq, '\0'); | |
| new_env_data = comma; | |
| } else { | |
| new_env_data = comma + 1; | |
| } | |
| (*result)[StringData(env_data, eq - env_data)] = | |
| ParseInteger(after_eq, comma - after_eq); | |
| env_data = new_env_data; | |
| } | |
| return result; | |
| } | |
| } // namespace | |
| int64_t MinLogLevelFromEnv() { | |
| // We don't want to print logs during fuzzing as that would slow fuzzing down | |
| // by almost 2x. So, if we are in fuzzing mode (not just running a test), we | |
| // return a value so that nothing is actually printed. Since LOG uses >= | |
| // (see ~LogMessage in this file) to see if log messages need to be printed, | |
| // the value we're interested on to disable printing is the maximum severity. | |
| // See also http://llvm.org/docs/LibFuzzer.html#fuzzer-friendly-build-mode | |
| #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION | |
| return verible::NUM_SEVERITIES; | |
| #else | |
| const char* tf_env_var_val = getenv("TF_CPP_MIN_LOG_LEVEL"); | |
| return LogLevelStrToInt(tf_env_var_val); | |
| #endif | |
| } | |
| int64_t MinVLogLevelFromEnv() { | |
| // We don't want to print logs during fuzzing as that would slow fuzzing down | |
| // by almost 2x. So, if we are in fuzzing mode (not just running a test), we | |
| // return a value so that nothing is actually printed. Since VLOG uses <= | |
| // (see VLOG_IS_ON in logging.h) to see if log messages need to be printed, | |
| // the value we're interested on to disable printing is 0. | |
| // See also http://llvm.org/docs/LibFuzzer.html#fuzzer-friendly-build-mode | |
| #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION | |
| return 0; | |
| #else | |
| const char* tf_env_var_val = getenv("TF_CPP_MIN_VLOG_LEVEL"); | |
| return LogLevelStrToInt(tf_env_var_val); | |
| #endif | |
| } | |
| LogMessage::LogMessage(const char* fname, int line, int severity) | |
| : fname_(fname), line_(line), severity_(severity) {} | |
| LogMessage::~LogMessage() { | |
| // Read the min log level once during the first call to logging. | |
| static int64_t min_log_level = MinLogLevelFromEnv(); | |
| if (severity_ >= min_log_level) { | |
| GenerateLogMessage(); | |
| } | |
| } | |
| void LogMessage::GenerateLogMessage() { | |
| fprintf(stderr, "%c %s:%d] %s\n", "IWEF"[severity_], fname_, line_, | |
| str().c_str()); | |
| } | |
| int64_t LogMessage::MinVLogLevel() { | |
| static int64_t min_vlog_level = MinVLogLevelFromEnv(); | |
| return min_vlog_level; | |
| } | |
| bool LogMessage::VmoduleActivated(const char* fname, int level) { | |
| if (level <= MinVLogLevel()) { | |
| return true; | |
| } | |
| static VmoduleMap* vmodules = VmodulesMapFromEnv(); | |
| if (ABSL_PREDICT_TRUE(vmodules == nullptr)) { | |
| return false; | |
| } | |
| const char* last_slash = strrchr(fname, '/'); | |
| const char* module_start = last_slash == nullptr ? fname : last_slash + 1; | |
| const char* dot_after = strchr(module_start, '.'); | |
| const char* module_limit = | |
| dot_after == nullptr ? strchr(fname, '\0') : dot_after; | |
| StringData module(module_start, module_limit - module_start); | |
| auto it = vmodules->find(module); | |
| return it != vmodules->end() && it->second >= level; | |
| } | |
| LogMessageFatal::LogMessageFatal(const char* file, int line) | |
| : LogMessage(file, line, FATAL) {} | |
| LogMessageFatal::~LogMessageFatal() { | |
| // abort() ensures we don't return (we promised we would not via | |
| // ATTRIBUTE_NORETURN). | |
| GenerateLogMessage(); | |
| abort(); | |
| } | |
| void LogString(const char* fname, int line, int severity, | |
| const std::string& message) { | |
| LogMessage(fname, line, severity) << message; | |
| } | |
| template <> | |
| void MakeCheckOpValueString(std::ostream* os, const char& v) { | |
| if (v >= 32 && v <= 126) { | |
| (*os) << "'" << v << "'"; | |
| } else { | |
| (*os) << "char value " << static_cast<short>(v); | |
| } | |
| } | |
| template <> | |
| void MakeCheckOpValueString(std::ostream* os, const signed char& v) { | |
| if (v >= 32 && v <= 126) { | |
| (*os) << "'" << v << "'"; | |
| } else { | |
| (*os) << "signed char value " << static_cast<short>(v); | |
| } | |
| } | |
| template <> | |
| void MakeCheckOpValueString(std::ostream* os, const unsigned char& v) { | |
| if (v >= 32 && v <= 126) { | |
| (*os) << "'" << v << "'"; | |
| } else { | |
| (*os) << "unsigned char value " << static_cast<unsigned short>(v); | |
| } | |
| } | |
| template <> | |
| void MakeCheckOpValueString(std::ostream* os, const std::nullptr_t& p) { | |
| (*os) << "nullptr"; | |
| } | |
| CheckOpMessageBuilder::CheckOpMessageBuilder(const char* exprtext) | |
| : stream_(new std::ostringstream) { | |
| *stream_ << "Check failed: " << exprtext << " ("; | |
| } | |
| CheckOpMessageBuilder::~CheckOpMessageBuilder() { delete stream_; } | |
| std::ostream* CheckOpMessageBuilder::ForVar2() { | |
| *stream_ << " vs. "; | |
| return stream_; | |
| } | |
| std::string* CheckOpMessageBuilder::NewString() { | |
| *stream_ << ")"; | |
| return new std::string(stream_->str()); | |
| } | |
| namespace { | |
| // The following code behaves like AtomicStatsCounter::LossyAdd() for | |
| // speed since it is fine to lose occasional updates. | |
| // Returns old value of *counter. | |
| uint32_t LossyIncrement(std::atomic<uint32_t>* counter) { | |
| const uint32_t value = counter->load(std::memory_order_relaxed); | |
| counter->store(value + 1, std::memory_order_relaxed); | |
| return value; | |
| } | |
| } // namespace | |
| bool LogEveryNState::ShouldLog(int n) { | |
| return n != 0 && (LossyIncrement(&counter_) % n) == 0; | |
| } | |
| bool LogFirstNState::ShouldLog(int n) { | |
| const uint32_t counter_value = counter_.load(std::memory_order_relaxed); | |
| if (counter_value < static_cast<uint32_t>(n)) { | |
| counter_.store(counter_value + 1, std::memory_order_relaxed); | |
| return true; | |
| } | |
| return false; | |
| } | |
| bool LogEveryPow2State::ShouldLog(int ignored) { | |
| const uint32_t new_value = LossyIncrement(&counter_) + 1; | |
| return (new_value & (new_value - 1)) == 0; | |
| } | |
| bool LogEveryNSecState::ShouldLog(double seconds) { | |
| LossyIncrement(&counter_); | |
| const int64_t now_cycles = absl::base_internal::CycleClock::Now(); | |
| int64_t next_cycles = next_log_time_cycles_.load(std::memory_order_relaxed); | |
| do { | |
| if (now_cycles <= next_cycles) return false; | |
| } while (!next_log_time_cycles_.compare_exchange_weak( | |
| next_cycles, | |
| now_cycles + seconds * absl::base_internal::CycleClock::Frequency(), | |
| std::memory_order_relaxed, std::memory_order_relaxed)); | |
| return true; | |
| } | |
| } // namespace internal | |
| } // namespace verible |