blob: b481efc8a370e49bdff2036afd955d72e82f4ae6 [file] [log] [blame]
// 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