sql_utils/base/logging.cc (197 lines of code) (raw):
/*
* Copyright 2023 Google LLC
*
* 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 "sql_utils/base/logging.h"
#include <errno.h>
#include <fcntl.h>
#include <libgen.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <cctype>
#include <cstdio>
#include <cstdlib>
#include <ctime>
#include <sstream>
#include <string>
#include "absl/base/attributes.h"
namespace bigquery_ml_utils_base {
constexpr char kDefaultDirectory[] = "/tmp/";
namespace {
// The logging directory.
ABSL_CONST_INIT std::string *log_file_directory = nullptr;
// The log filename.
ABSL_CONST_INIT std::string *log_basename = nullptr;
// The SQL_VLOG level, only SQL_VLOG with level equal to or below this
// level is logged.
ABSL_CONST_INIT int vlog_level = 0;
const char *GetBasename(const char *file_path) {
const char *slash = strrchr(file_path, '/');
return slash ? slash + 1 : file_path;
}
bool set_log_basename(const std::string &filename) {
if (log_basename || filename.empty()) {
return false;
}
log_basename = new std::string(filename);
return true;
}
std::string get_log_basename() {
if (!log_basename || log_basename->empty()) {
return "sql";
}
return *log_basename;
}
bool EnsureDirectoryExists(const char *path) {
struct stat dirStat;
if (stat(path, &dirStat)) {
if (errno != ENOENT) {
return false;
}
if (mkdir(path, 0766)) {
return false;
}
} else if (!S_ISDIR(dirStat.st_mode)) {
return false;
}
return true;
}
// Sets the log directory, as specified when initialized. This
// is only set once. Any request to reset it will return false.
//
// log_directory: log file directory.
//
// Returns true if and only if the log directory is set successfully.
bool set_log_directory(const std::string &log_directory) {
std::string tmp_directory = log_directory;
if (tmp_directory.empty()) {
tmp_directory = kDefaultDirectory;
}
if (log_file_directory || !EnsureDirectoryExists(tmp_directory.c_str())) {
return false;
}
if (tmp_directory.back() == '/') {
log_file_directory = new std::string(tmp_directory);
} else {
log_file_directory = new std::string(tmp_directory + "/");
}
return true;
}
// Sets the verbosity threshold for SQL_VLOG. A SQL_VLOG command with a
// level greater
// than this will be ignored.
//
// level: verbosity threshold for SQL_VLOG to be set. A SQL_VLOG command with
// level less than or equal to this will be logged.
void set_vlog_level(int level) { vlog_level = level; }
} // namespace
std::string get_log_directory() {
if (!log_file_directory) {
return kDefaultDirectory;
}
return *log_file_directory;
}
int get_vlog_level() { return vlog_level; }
bool InitLogging(const char *directory, const char *file_name, int level) {
set_vlog_level(level);
std::string log_directory = directory ? std::string(directory) : "";
if (!set_log_directory(log_directory)) {
return false;
}
const char *binary_name = GetBasename(file_name);
if (!set_log_basename(binary_name)) {
return false;
}
std::string log_path = get_log_directory() + get_log_basename();
if (access(log_path.c_str(), F_OK) == 0 &&
access(log_path.c_str(), W_OK) != 0) {
return false;
}
return true;
}
CheckOpMessageBuilder::CheckOpMessageBuilder(const char *exprtext)
: stream_(new std::ostringstream) {
*stream_ << exprtext << " (";
}
CheckOpMessageBuilder::~CheckOpMessageBuilder() { delete stream_; }
std::ostream *CheckOpMessageBuilder::ForVar2() {
*stream_ << " vs. ";
return stream_;
}
std::string *CheckOpMessageBuilder::NewString() { // NOLINT
*stream_ << ")";
return new std::string(stream_->str());
}
template <>
void MakeCheckOpValueString(std::ostream *os, const char &v) {
if (v >= 32 && v <= 126) {
(*os) << "'" << v << "'";
} else {
(*os) << "char value " << static_cast<int16_t>(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<int16_t>(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<uint16_t>(v);
}
}
template <>
void MakeCheckOpValueString(std::ostream *os, const std::nullptr_t &v) {
(*os) << "nullptr";
}
namespace logging_internal {
LogMessage::LogMessage(const char *file, int line)
: LogMessage(file, line, absl::LogSeverity::kInfo) {}
LogMessage::LogMessage(const char *file, int line, const std::string &result)
: LogMessage(file, line, absl::LogSeverity::kFatal) {
stream() << "Check failed: " << result << " ";
}
static constexpr const char *LogSeverityNames[4] = {"INFO", "WARNING", "ERROR",
"FATAL"};
LogMessage::LogMessage(const char *file, int line, absl::LogSeverity severity)
: severity_(severity) {
const char *filename = GetBasename(file);
// Write a prefix into the log message, including local date/time, severity
// level, filename, and line number.
struct timespec time_stamp;
clock_gettime(CLOCK_REALTIME, &time_stamp);
constexpr int kTimeMessageSize = 22;
char buffer[kTimeMessageSize];
strftime(buffer, kTimeMessageSize, "%Y-%m-%d %H:%M:%S ",
localtime(&time_stamp.tv_sec));
stream() << buffer;
stream() << LogSeverityNames[static_cast<int>(severity)] << " "
<< filename << " : " << line << " : ";
}
LogMessage::~LogMessage() {
Flush();
// if FATAL occurs, abort.
if (severity_ == absl::LogSeverity::kFatal) {
abort();
}
}
void LogMessage::SendToLog(const std::string &message_text) {
std::string log_path = get_log_directory() + get_log_basename();
FILE *file = fopen(log_path.c_str(), "ab");
if (file) {
if (fprintf(file, "%s", message_text.c_str()) > 0) {
if (message_text.back() != '\n') {
fprintf(file, "\n");
}
} else {
fprintf(stderr, "Failed to write to log file : %s! [%s]\n",
log_path.c_str(), strerror(errno));
}
fclose(file);
} else {
fprintf(stderr, "Failed to open log file : %s! [%s]\n",
log_path.c_str(), strerror(errno));
}
if (severity_ >= absl::LogSeverity::kError) {
fprintf(stderr, "%s\n", message_text.c_str());
fflush(stderr);
}
printf("%s\n", message_text.c_str());
fflush(stdout);
}
void LogMessage::Flush() {
std::string message_text = stream_.str();
SendToLog(message_text);
stream_.clear();
}
LogMessageFatal::~LogMessageFatal() {
Flush();
abort();
}
} // namespace logging_internal
} // namespace bigquery_ml_utils_base